rcqrs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/lib/rcqrs.rb +30 -0
- data/lib/rcqrs/aggregate_root_base.rb +70 -0
- data/lib/rcqrs/base_command.rb +22 -0
- data/lib/rcqrs/base_command_handler.rb +27 -0
- data/lib/rcqrs/base_event.rb +57 -0
- data/lib/rcqrs/command_executor.rb +16 -0
- data/lib/rcqrs/command_not_valid_exception.rb +13 -0
- data/lib/rcqrs/command_source.rb +72 -0
- data/lib/rcqrs/configuration.rb +15 -0
- data/lib/rcqrs/convention_command.rb +32 -0
- data/lib/rcqrs/convention_command_handler.rb +33 -0
- data/lib/rcqrs/denormalizer.rb +79 -0
- data/lib/rcqrs/domain_exception.rb +11 -0
- data/lib/rcqrs/event_bus.rb +59 -0
- data/lib/rcqrs/event_collection.rb +18 -0
- data/lib/rcqrs/event_configuration_base.rb +43 -0
- data/lib/rcqrs/event_handler.rb +55 -0
- data/lib/rcqrs/event_type_registry.rb +16 -0
- data/lib/rcqrs/not_an_event_exception.rb +7 -0
- data/lib/rcqrs/published_event.rb +15 -0
- data/lib/rcqrs/repository.rb +27 -0
- data/lib/rcqrs/unknown_event_exception.rb +3 -0
- data/lib/rcqrs/version.rb +3 -0
- data/rcqrs.gemspec +29 -0
- data/spec/aggregate_root_base_spec.rb +69 -0
- data/spec/base_commandhandler_spec.rb +38 -0
- data/spec/base_event_spec.rb +26 -0
- data/spec/command_executor_spec.rb +12 -0
- data/spec/command_handler_spec.rb +40 -0
- data/spec/configuration_spec.rb +13 -0
- data/spec/convention_command_spec.rb +33 -0
- data/spec/denormalizer_spec.rb +107 -0
- data/spec/eventbus_spec.rb +109 -0
- data/spec/eventhandler_spec.rb +74 -0
- data/spec/repository_spec.rb +75 -0
- data/spec/ricko.rb +181 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/test_aggregate.rb +36 -0
- data/spec/test_command_handler.rb +33 -0
- data/spec/test_command_source.rb +4 -0
- data/spec/test_event.rb +13 -0
- data/spec/test_event_bus.rb +18 -0
- 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
|
+
|