rcqrs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+