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.
- 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
|
+
|