rcqrs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +4 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/README.md +4 -0
  5. data/Rakefile +1 -0
  6. data/lib/rcqrs.rb +30 -0
  7. data/lib/rcqrs/aggregate_root_base.rb +70 -0
  8. data/lib/rcqrs/base_command.rb +22 -0
  9. data/lib/rcqrs/base_command_handler.rb +27 -0
  10. data/lib/rcqrs/base_event.rb +57 -0
  11. data/lib/rcqrs/command_executor.rb +16 -0
  12. data/lib/rcqrs/command_not_valid_exception.rb +13 -0
  13. data/lib/rcqrs/command_source.rb +72 -0
  14. data/lib/rcqrs/configuration.rb +15 -0
  15. data/lib/rcqrs/convention_command.rb +32 -0
  16. data/lib/rcqrs/convention_command_handler.rb +33 -0
  17. data/lib/rcqrs/denormalizer.rb +79 -0
  18. data/lib/rcqrs/domain_exception.rb +11 -0
  19. data/lib/rcqrs/event_bus.rb +59 -0
  20. data/lib/rcqrs/event_collection.rb +18 -0
  21. data/lib/rcqrs/event_configuration_base.rb +43 -0
  22. data/lib/rcqrs/event_handler.rb +55 -0
  23. data/lib/rcqrs/event_type_registry.rb +16 -0
  24. data/lib/rcqrs/not_an_event_exception.rb +7 -0
  25. data/lib/rcqrs/published_event.rb +15 -0
  26. data/lib/rcqrs/repository.rb +27 -0
  27. data/lib/rcqrs/unknown_event_exception.rb +3 -0
  28. data/lib/rcqrs/version.rb +3 -0
  29. data/rcqrs.gemspec +29 -0
  30. data/spec/aggregate_root_base_spec.rb +69 -0
  31. data/spec/base_commandhandler_spec.rb +38 -0
  32. data/spec/base_event_spec.rb +26 -0
  33. data/spec/command_executor_spec.rb +12 -0
  34. data/spec/command_handler_spec.rb +40 -0
  35. data/spec/configuration_spec.rb +13 -0
  36. data/spec/convention_command_spec.rb +33 -0
  37. data/spec/denormalizer_spec.rb +107 -0
  38. data/spec/eventbus_spec.rb +109 -0
  39. data/spec/eventhandler_spec.rb +74 -0
  40. data/spec/repository_spec.rb +75 -0
  41. data/spec/ricko.rb +181 -0
  42. data/spec/spec_helper.rb +29 -0
  43. data/spec/test_aggregate.rb +36 -0
  44. data/spec/test_command_handler.rb +33 -0
  45. data/spec/test_command_source.rb +4 -0
  46. data/spec/test_event.rb +13 -0
  47. data/spec/test_event_bus.rb +18 -0
  48. metadata +154 -0
@@ -0,0 +1,12 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::CommandExecutor do
4
+
5
+ let(:subject){Rcqrs::CommandExecutor}
6
+
7
+ it "reads the eventstore from the configuration" do
8
+ Rcqrs::Configuration.eventstore = "eventstore"
9
+ subject.eventbus.instance_eval{|bus| bus.instance_variable_get(:@eventstore).should == "eventstore"}
10
+ end
11
+
12
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::CommandSource do
4
+
5
+ before do
6
+ @commandSource = TestCommandSource.new
7
+ end
8
+
9
+ it "should find the appropiate command handler and call it" do
10
+ @commandSource.execute TestCommand.new("Parameter")
11
+ TestCommandHandler.was_called_with.should == "Parameter"
12
+ end
13
+
14
+ it "should raise NameError when commandhandler not found" do
15
+ expect {@commandSource.execute NonExistingCommand.new("Param")}.to raise_error(Rcqrs::CommandHandlerNotFoundError)
16
+ end
17
+
18
+ it "should raise an Exception if the CommandHandler does not inherit from BaseCommandHandler" do
19
+ expect {@commandSource.execute DoesNotInheritFromBase.new("Param")}.to raise_error(Rcqrs::CommandHandlerDoesNotInheritFromBaseError)
20
+ end
21
+
22
+ it "should provide the CommandHandler a repository" do
23
+ @commandSource.execute TestCommand.new "param"
24
+ TestCommandHandler.repository.should_not == nil
25
+ end
26
+
27
+ it "should execute the command handler in a transaction for the eventstore" do
28
+ @commandSource.execute TestCommand.new "param"
29
+ TestEventBus.transaction_executed.should == true
30
+ end
31
+
32
+ end
33
+
34
+ class DoesNotInheritFromBase
35
+
36
+ end
37
+
38
+ class DoesNotInheritFromBaseHandler
39
+
40
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::Configuration do
4
+ let(:subject) {Rcqrs::Configuration}
5
+
6
+ it "allows to configure a eventstore" do
7
+ subject.configure do |config|
8
+ subject.eventstore = "eventstore"
9
+ end
10
+ subject.eventstore.should == "eventstore"
11
+ end
12
+
13
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+
4
+ describe Rcqrs::ConventionCommandHandler do
5
+
6
+ let(:comand_source) {TestCommandSource.new}
7
+
8
+ it "should execute the method on the aggregate defined by the command name" do
9
+ comand_source.execute(DummyNamespace::TestConventionCommand.new(parameter:"parameter"))
10
+ DummyNamespace::ConventionAggregate.called.should == true
11
+ end
12
+
13
+ end
14
+
15
+ module DummyNamespace
16
+
17
+ class ConventionAggregate < Rcqrs::AggregateRootBase
18
+
19
+ def test_convention
20
+ @@called = true
21
+ end
22
+
23
+ def self.called
24
+ @@called
25
+ end
26
+ end
27
+
28
+ class TestConventionCommand < Rcqrs::ConventionCommand
29
+ aggregate DummyNamespace::ConventionAggregate
30
+ attr_reader :parameter
31
+ end
32
+
33
+ end
@@ -0,0 +1,107 @@
1
+ describe Rcqrs::Denormalizer do
2
+
3
+ before do
4
+ @denormalizer = Object.new
5
+ @denormalizer.extend Rcqrs::Denormalizer
6
+ end
7
+
8
+ describe "mapping of hashes" do
9
+
10
+ before do
11
+ @target = mockup
12
+ end
13
+
14
+ it "should allow to map hash values named by symbols to corresponding attributes of target" do
15
+ @denormalizer.map({name: "thename", age: 21}).to(@target)
16
+ @target.message(:name=).with("thename").should_be_received
17
+ @target.message(:age=).with(21).should_be_received
18
+ end
19
+
20
+ it "should allow to map hash values named by strings to corresponding attributes of the target" do
21
+ @denormalizer.map({"name" => "thename", "age" => 21}).to(@target)
22
+ @target.message(:name=).with("thename").should_be_received
23
+ @target.message(:age=).with(21).should_be_received
24
+ end
25
+
26
+ it "should allow to reduce the mapped values to specified keys" do
27
+ @denormalizer.map({"name" => "thename", "age" => 21}).only([:name]).to(@target)
28
+ @target.message(:name=).with("thename").should_be_received
29
+ @target.message(:age=).with(21).times(0).should_be_received
30
+ end
31
+
32
+ it "should allow to map values to another hash" do
33
+ hash_target = Hash.new
34
+ @denormalizer.map({name: "thename", age: 21}).to(hash_target)
35
+ hash_target.should == {name: "thename", age: 21}
36
+ end
37
+
38
+ end
39
+
40
+
41
+ describe "with an object as source" do
42
+
43
+ before do
44
+ class Source; attr_accessor :name, :age; end
45
+ @source = Source.new
46
+ @target = mockup
47
+ end
48
+
49
+ it "should allow to map attributes specified by name to an target object" do
50
+ @source.name = "Bernd"
51
+ @source.age = 534
52
+ @denormalizer.map([:name,:age]).from(@source).to(@target)
53
+ @target.message(:name=).with("Bernd").should_be_received
54
+ @target.message(:age=).with(534).should_be_received
55
+ end
56
+
57
+ it "should only map those attributes that are specified" do
58
+ @source.name = "Bernd"
59
+ @denormalizer.map([:name]).from(@source).to(@target)
60
+ @target.message(:name=).with("Bernd").should_be_received
61
+ @target.message(:age=).never.should_be_received
62
+ end
63
+
64
+ it "should allow to map to a hash" do
65
+ target_hash = Hash.new
66
+ @source.name = "Bernd"
67
+ @source.age = 534
68
+ @denormalizer.map([:name,:age]).from(@source).to(target_hash)
69
+ target_hash.should == {name: "Bernd", age: 534}
70
+ end
71
+
72
+ end
73
+
74
+
75
+ context "database access" do
76
+
77
+ class DummyReadmodelDatabase
78
+ def save(modelname,data)
79
+ end
80
+ end
81
+
82
+ before do
83
+ @read_model_db = stub(DummyReadmodelDatabase)
84
+ Rcqrs::Configuration.readmodel_database = @read_model_db
85
+ end
86
+
87
+ it "redirect all methodcalls to the readmodel_db if the Denormalizer doesnt define it" do
88
+ @read_model_db.stub!(:save)
89
+ @denormalizer.save(:model_name,:params)
90
+ @read_model_db.should have_received(:save).with(:model_name,:params)
91
+ end
92
+
93
+ it "if a modelname is specified it is added to the parameterlist of the methodcall" do
94
+ @denormalizer.model = :model_name
95
+ @read_model_db.stub!(:save)
96
+ @denormalizer.save(:params)
97
+ @read_model_db.should have_received(:save).with(:model_name, :params)
98
+ end
99
+
100
+ it "if the readmodel_db doesnt define it normal method missing should raise" do
101
+ expect{@denormalizer.update(:model_name, "id",{attr:"val"})}.to raise_error
102
+ end
103
+
104
+ end
105
+
106
+
107
+ end
@@ -0,0 +1,109 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::Eventbus do
4
+
5
+ before do
6
+ @id = UUID.generate
7
+ @mockEventStore = mockup
8
+ @bus = Rcqrs::Eventbus.new @mockEventStore
9
+ @eventHandler = mockup
10
+ @bus.register(EventbusTestevent, @eventHandler)
11
+ @anotherHandler = mockup
12
+ @bus.register(EventbusTestevent, @anotherHandler)
13
+ @testevent = EventbusTestevent.new "Hello happened"
14
+ end
15
+
16
+ it "publishes Events to multiple Eventhandlers" do
17
+ @bus.publish @id, @testevent
18
+ @bus.commit
19
+ @eventHandler.message(:handle).times(1).with(@id, @testevent).should_be_received
20
+ @anotherHandler.message(:handle).times(1).with(@id, @testevent).should_be_received
21
+ end
22
+
23
+ it "flushes the events after commiting" do
24
+ @bus.publish @id, @testevent
25
+ @bus.commit
26
+ @bus.commit
27
+ @eventHandler.message(:handle).times(1).with(@id, @testevent).should_be_received
28
+ @anotherHandler.message(:handle).times(1).with(@id, @testevent).should_be_received
29
+ end
30
+
31
+ it "stores the current time as publish time when commiting in the event" do
32
+ @bus.publish @id, @testevent
33
+ @bus.commit
34
+ @testevent.published_at.should be_kind_of Time
35
+ end
36
+
37
+ it "shouldnt be able to change the publish time after a commit" do
38
+ @bus.publish @id, @testevent
39
+ @bus.commit
40
+ expect{@testevent.store_publish_time}.to raise_error "Event has allready a publish date"
41
+ end
42
+
43
+ it "ignores double registrations of eventhandlers" do
44
+ @bus.register(EventbusTestevent, @eventHandler)
45
+ @bus.publish @id, @testevent
46
+ @bus.commit
47
+ @eventHandler.message(:handle).times(1).with(@id, @testevent).should_be_received
48
+ end
49
+
50
+ it "should only publish events to a eventhandler that is interested in events of that type" do
51
+ yetAnotherHandler = mockup
52
+ @bus.register(EventbusOtherEvent, yetAnotherHandler)
53
+ @bus.publish @id, @testevent
54
+ @bus.commit
55
+ yetAnotherHandler.message(:handle).times(0).should_be_received
56
+ end
57
+
58
+ it "events are only published after a commit" do
59
+ @bus.publish @id, @testevent
60
+ @eventHandler.message(:handle).times(0).should_be_received
61
+ @anotherHandler.message(:handle).times(0).should_be_received
62
+ end
63
+
64
+ it "should save the events in a eventstore during commit" do
65
+ @bus.publish @id, @testevent
66
+ @bus.commit
67
+ @mockEventStore.message(:store).once.with([Rcqrs::PublishedEvent.new(@id, @testevent)]).should_be_received
68
+ end
69
+
70
+ it "should reject events not inherited from base event" do
71
+ expect{ @bus.publish(@id, "no event") }.to raise_error Rcqrs::NotAnEventException
72
+ end
73
+
74
+ it "should allow to load previous events" do
75
+ eventstore = Object.new
76
+ def eventstore.load_events aggregate_id; "events"; end
77
+ bus = Rcqrs::Eventbus.new eventstore
78
+ bus.load_events(@id).should == "events"
79
+ end
80
+
81
+ end
82
+
83
+ describe "Eventbus without eventhandler" do
84
+
85
+ it "should not throw an error and just save the events" do
86
+ mockEventStore = mockup
87
+ bus = Rcqrs::Eventbus.new mockEventStore
88
+ id = UUID.generate
89
+ testevent = EventbusTestevent.new "Hello happened"
90
+ bus.publish id, testevent
91
+ bus.commit
92
+ mockEventStore.message(:store).once.with([Rcqrs::PublishedEvent.new(id, testevent)]).should_be_received
93
+ end
94
+
95
+ end
96
+
97
+ class EventbusTestevent < Rcqrs::BaseEvent
98
+ attr_accessor :data
99
+
100
+ def initialize data
101
+ @data = data
102
+ end
103
+ end
104
+
105
+ class EventbusOtherEvent < Rcqrs::BaseEvent
106
+ def initialize
107
+
108
+ end
109
+ end
@@ -0,0 +1,74 @@
1
+ describe Rcqrs::EventHandler do
2
+
3
+ before do
4
+ @handler = DummyEventhandler
5
+ @event = TestEventModule::TestEvent.new
6
+ end
7
+
8
+ it "should find the appropriate eventhandler method by the eventname" do
9
+ @handler.handle("aggregate_id",@event)
10
+ @handler.test_event_handled.should == true
11
+ end
12
+
13
+ it "should allow to handle multiple events" do
14
+ class AnotherTestEvent < Rcqrs::BaseEvent; end
15
+ @handler.handle("another_id",AnotherTestEvent.new);
16
+ @handler.another_test_event_handled.should == true
17
+ end
18
+
19
+ it "should allow to get the names of all handled events" do
20
+ class ReadingHandledEventsTestHandler
21
+ include Rcqrs::EventHandler
22
+ def self.handle_test_event; end
23
+ end
24
+ ReadingHandledEventsTestHandler.handled_events.should == ["TestEvent"]
25
+ end
26
+
27
+ it "should allow to define a eventhandler via a class macro" do
28
+ @handler.handle("id",TestEventModule::ClassMacroEvent.new)
29
+ @handler.class_macro_event_handled.should == true
30
+ end
31
+
32
+ it "should automaticly register the eventhandler in the eventconfiguration" do
33
+ bus = DummyEventBus.new
34
+ Rcqrs::EventConfigurationBase.register_all_at bus
35
+ bus.config.should == {TestEventModule::ClassMacroEvent => DummyEventhandler}
36
+ end
37
+
38
+ end
39
+
40
+ class DummyEventBus
41
+
42
+ attr_reader :config
43
+
44
+ def initialize
45
+ @config = Hash.new
46
+ end
47
+
48
+ def register eventtype,eventhandler
49
+ @config[eventtype] = eventhandler
50
+ end
51
+ end
52
+
53
+ class DummyEventhandler
54
+ include Rcqrs::EventHandler
55
+
56
+ namespace TestEventModule
57
+
58
+ class << self
59
+ attr_reader :test_event_handled, :another_test_event_handled, :class_macro_event_handled
60
+ end
61
+
62
+ def self.handle_test_event aggregate_id, event
63
+ @test_event_handled = true
64
+ end
65
+
66
+ def self.handle_another_test_event aggregate_id, event
67
+ @another_test_event_handled = true
68
+ end
69
+
70
+ handler :class_macro_event do |aggregate_id, event|
71
+ @class_macro_event_handled = true
72
+ end
73
+
74
+ end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::Repository, "when Aggregates are loaded" do
4
+
5
+ before do
6
+ @aggregate_id = UUID.generate
7
+ @mockEventStore = mock("EventStore")
8
+ @events = [TestEventModule::TestEvent.new,TestEventModule::TestEvent.new]
9
+ @mockEventStore.should_receive(:load_events).with(@aggregate_id).and_return(@events)
10
+ @repo = Rcqrs::Repository.new @mockEventStore
11
+ end
12
+
13
+ it "should return an instance of the given Aggregate-Type to load" do
14
+ loaded_aggregate = @repo.load TestAggregateModule::TestAggregate, @aggregate_id
15
+ loaded_aggregate.should be_an_instance_of TestAggregateModule::TestAggregate
16
+ end
17
+
18
+ it "the returned aggregate should be initialized with stored events" do
19
+ loaded_aggregate = @repo.load TestAggregateModule::TestAggregate, @aggregate_id
20
+ loaded_aggregate.handledEvents[0].should == @events[0]
21
+ loaded_aggregate.handledEvents[1].should == @events[1]
22
+ end
23
+
24
+ it "should only load the events for the aggregateroot with the given id" do
25
+ otherEvents = [TestEventModule::TestEvent.new]
26
+ @mockEventStore.stub!(:load_events).and_return(otherEvents)
27
+ loaded_aggregate = @repo.load TestAggregateModule::TestAggregate, @aggregate_id
28
+ loaded_aggregate.handledEvents.size.should == 2
29
+ end
30
+
31
+ end
32
+
33
+
34
+ describe Rcqrs::Repository, "when Aggregateroots are saved" do
35
+
36
+ before do
37
+ @aggregate_id = UUID.generate
38
+ @testaggregate = TestAggregateModule::TestAggregate.new @aggregate_id
39
+ @mockEventStore = mock("Eventbus")
40
+ @repo = Rcqrs::Repository.new @mockEventStore
41
+ end
42
+
43
+ it "should save all the pending events of the aggregateroot" do
44
+ @mockEventStore.should_receive(:publish).with(@aggregate_id, an_instance_of(TestEventModule::TestEvent))
45
+ @testaggregate.doSomething
46
+ @repo.save @testaggregate
47
+ end
48
+
49
+ end
50
+
51
+ describe Rcqrs::Repository do
52
+
53
+ before do
54
+ @aggregate_id = UUID.generate
55
+ @testaggregate = TestAggregateModule::TestAggregate.new @aggregate_id
56
+ @mockEventStore = mock("Eventbus")
57
+ @repo = Rcqrs::Repository.new @mockEventStore
58
+ end
59
+
60
+ it "should allow to fire domainevents directly" do
61
+ repoTestEvent = RepoTestEvent.new
62
+ @mockEventStore.should_receive(:publish).with(@aggregate_id,repoTestEvent)
63
+ @repo.fire(@aggregate_id, repoTestEvent)
64
+ end
65
+
66
+ it "should reject events not inherited from base event" do
67
+ expect{ @repo.fire(@aggregate_id,"no Event") }.to raise_error(Rcqrs::NotAnEventException)
68
+ end
69
+
70
+ end
71
+
72
+ class RepoTestEvent < Rcqrs::BaseEvent
73
+
74
+ end
75
+