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,11 @@
1
+ module Rcqrs
2
+
3
+ class DomainException < Exception
4
+
5
+ def initialize reason
6
+ @reason = reason
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,59 @@
1
+ module Rcqrs
2
+
3
+ class Eventbus
4
+
5
+ def initialize eventstore
6
+ @eventstore = eventstore
7
+ @eventhandlers = Hash.new
8
+ @published_events = Array.new
9
+ end
10
+
11
+ def load_events aggregate_id
12
+ @eventstore.load_events(aggregate_id)
13
+ end
14
+
15
+ def register eventtype, eventhandler
16
+ @eventhandlers[eventtype] = Array.new unless @eventhandlers.has_key? eventtype
17
+ unless @eventhandlers[eventtype].include? eventhandler
18
+ @eventhandlers[eventtype] << eventhandler
19
+ end
20
+ end
21
+
22
+ def publish id, event
23
+ unless event.is_a? BaseEvent then
24
+ raise NotAnEventException, "Given event has to inherit from BaseEvent"
25
+ end
26
+ @published_events << PublishedEvent.new(id,event)
27
+ end
28
+
29
+ def commit
30
+ store_published_time_for_events
31
+ save_events
32
+ publish_events
33
+ flush
34
+ end
35
+
36
+ private
37
+
38
+ def store_published_time_for_events
39
+ @published_events.each{|published_event| published_event.event.store_publish_time}
40
+ end
41
+
42
+ def save_events
43
+ @eventstore.store(@published_events)
44
+ end
45
+
46
+ def publish_events
47
+ @published_events.each do |event_tuple|
48
+ if @eventhandlers[event_tuple.event.class] != nil
49
+ @eventhandlers[event_tuple.event.class].each {|handler| handler.handle event_tuple.aggregate_id, event_tuple.event }
50
+ end
51
+ end
52
+ end
53
+
54
+ def flush
55
+ @published_events = Array.new
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,18 @@
1
+ module Rcqrs
2
+
3
+ class EventCollection
4
+
5
+ def initialize aggregateroot_id, events
6
+ @events = events
7
+ @aggregateroot_id = aggregateroot_id
8
+ end
9
+
10
+ def commit_to eventstore
11
+ @events.each do |event|
12
+ eventstore.publish @aggregateroot_id, event
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,43 @@
1
+ module Rcqrs
2
+
3
+ class EventConfigurationBase
4
+
5
+ @configs = Array.new
6
+
7
+ def self.register_handler(handler)
8
+ handler.handled_events.each do |event_name|
9
+ handle(event_name).with(handler)
10
+ end
11
+ end
12
+
13
+ def self.register_all_at(eventbus)
14
+ if(@configs != nil)
15
+ @configs.each do |config|
16
+ eventbus.register config.eventtype, config.eventhandler
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.handle(eventtype)
22
+ @configs = Array.new if (@configs == nil)
23
+ config = EventConfig.new(eval(eventtype.to_s))
24
+ @configs << config
25
+ config
26
+ end
27
+
28
+ end
29
+
30
+ class EventConfig
31
+
32
+ attr_reader :eventtype, :eventhandler
33
+
34
+ def initialize eventtype
35
+ @eventtype = eventtype
36
+ end
37
+
38
+ def with eventhandler
39
+ @eventhandler = eventhandler
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,55 @@
1
+ module Rcqrs
2
+
3
+ module EventHandler
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def handled_events
12
+ methods.select {|m| m.to_s.match(/^handle_/)}.map{|m| convert_to_event_name(m[6..-1])}
13
+ end
14
+
15
+ def convert_to_event_name method
16
+ full_event_name(method)
17
+ end
18
+
19
+ def handle aggregate_id, event
20
+ methodname = handler_method_name(event)
21
+ send(methodname, aggregate_id,event)
22
+ end
23
+
24
+ def handler_method_name(event)
25
+ "handle" + event.class.name.split("::").last.gsub(/[A-Z]/){|s| "_" + s.downcase}
26
+ end
27
+
28
+ def namespace namespace
29
+ @namespace = namespace.to_s + "::"
30
+ end
31
+
32
+ def handler(event, &handlercode)
33
+ create_handler_method(event,handlercode)
34
+ EventConfigurationBase.handle(full_event_name(event)).with(self)
35
+ end
36
+
37
+ def full_event_name event_name
38
+ (@namespace || "") + symbol_to_event_name(event_name)
39
+ end
40
+
41
+ def symbol_to_event_name event
42
+ name = event.to_s.gsub(/_[a-z]/){|s| s[1].upcase}
43
+ name[0] = name[0].upcase
44
+ name
45
+ end
46
+
47
+ def create_handler_method(event,handlercode)
48
+ itself = (class << self; self; end)
49
+ itself.instance_eval do
50
+ define_method "handle_"+event.to_s.gsub(/[A-Z]/){|s| "_" + s.downcase}, &handlercode
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ module Rcqrs
2
+
3
+ class EventTypeRegistry
4
+
5
+ @events = Hash.new
6
+
7
+ def self.register name, type
8
+ @events[name] = type
9
+ end
10
+
11
+ def self.get_type_for name
12
+ @events[name]
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,7 @@
1
+ module Rcqrs
2
+
3
+ class NotAnEventException < Exception
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,15 @@
1
+ module Rcqrs
2
+
3
+ class PublishedEvent
4
+ attr_accessor :aggregate_id, :event
5
+
6
+ def initialize aggregate_id, event
7
+ @aggregate_id, @event = aggregate_id, event
8
+ end
9
+
10
+ def ==(other)
11
+ @aggregate_id == other.aggregate_id && @event == other.event
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,27 @@
1
+ module Rcqrs
2
+ class Repository
3
+
4
+ def initialize event_store
5
+ @event_store = event_store
6
+ end
7
+
8
+ def load(aggregate_class, aggregate_id)
9
+ aggregate = aggregate_class.new aggregate_id
10
+ events = @event_store.load_events(aggregate_id)
11
+ aggregate.load_from(events)
12
+ aggregate
13
+ end
14
+
15
+ def save aggregate_root
16
+ aggregate_root.pending_events.commit_to @event_store
17
+ end
18
+
19
+ def fire aggregate_id, event
20
+ unless (event.is_a? BaseEvent) then
21
+ raise NotAnEventException, "Given event has to inherit from BaseEvent"
22
+ end
23
+ @event_store.publish(aggregate_id,event)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ class UnknownEventException < Exception
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ module Rcqrs
2
+ VERSION = "0.1.0"
3
+ end
data/rcqrs.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rcqrs/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rcqrs"
7
+ s.version = Rcqrs::VERSION
8
+ s.authors = ["Tim de Putter"]
9
+ s.email = ["tim.de.putter83@googlemail.com"]
10
+ s.summary = %q{Framework to provide cqrs + eventsourcing functionality in ruby.}
11
+ s.description = %q{Framework to provide cqrs + eventsourcing functionality in ruby.}
12
+
13
+ s.rubyforge_project = "rcqrs"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_runtime_dependency "rest-client"
22
+
23
+ s.add_development_dependency "rspec"
24
+ s.add_development_dependency "rspec-spies"
25
+ s.add_development_dependency "activemodel"
26
+ s.add_runtime_dependency "uuid"
27
+
28
+
29
+ end
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::AggregateRootBase do
4
+
5
+ before do
6
+ @idgenerator = UUID.new
7
+ @id = @idgenerator.generate
8
+ @testAggregate = TestAggregateModule::TestAggregate.new @id
9
+ @mockEventStore = mockup
10
+ @events = [TestEventModule::TestEvent.new, TestEventModule::TestEvent.new]
11
+ end
12
+
13
+ it "should handle Events" do
14
+ @testAggregate.doSomething
15
+ @testAggregate.handledEvents.size.should == 1
16
+ end
17
+
18
+ it "should events when there is no eventhandler method" do
19
+ class UnknownEvent < Rcqrs::BaseEvent; end
20
+ @testAggregate.load_from [TestEventModule::TestEvent.new,UnknownEvent.new]
21
+ end
22
+
23
+ it "should collect Events in a EventCollection" do
24
+ @testAggregate.doSomething
25
+ @testAggregate.pending_events.should be_an_instance_of(Rcqrs::EventCollection)
26
+ end
27
+
28
+ it "should have recorded one event to be committed" do
29
+ @testAggregate.doSomething
30
+ @testAggregate.pending_events.commit_to @mockEventStore
31
+ @mockEventStore.message(:publish).with(any, any(TestEventModule::TestEvent)).should_be_received
32
+ end
33
+
34
+ it "should send its uuid to eventstore to be able to assoziate the events with the aggregate" do
35
+ @testAggregate.doSomething
36
+ @testAggregate.pending_events.commit_to @mockEventStore
37
+ @mockEventStore.message(:publish).with(@id, any()).should_be_received
38
+ end
39
+
40
+ it "should be able to load from existing events in the eventstore" do
41
+ @testAggregate.load_from @events
42
+ @testAggregate.handledEvents[0] == @events[0]
43
+ @testAggregate.handledEvents[1] == @events[1]
44
+ end
45
+
46
+ it "should only pass the new events to the eventstore, not the ones loaded previously" do
47
+ @testAggregate.load_from @events
48
+ @testAggregate.doSomething
49
+ @testAggregate.pending_events.commit_to @mockEventStore
50
+ @mockEventStore.message(:publish).with(@id,any(TestEventModule::TestEvent)).should_be_received
51
+ end
52
+
53
+ it "events not inherited from BaseEvent are not allowed to be fired" do
54
+ expect {@testAggregate.doSomethingStupid}.to raise_error(Rcqrs::NotAnEventException)
55
+ end
56
+
57
+ it "should allow to define eventhandlers via a class macro" do
58
+ @testAggregate.handleAnotherTestEvent TestEventModule::AnotherTestEvent.new
59
+ @testAggregate.handledEvents[0].class.should == TestEventModule::AnotherTestEvent
60
+ end
61
+
62
+ it "should handle events with eventhandlers defined by class macros" do
63
+ @testAggregate.do_something_else
64
+ @testAggregate.handledEvents[0].class.should == TestEventModule::AnotherTestEvent
65
+ end
66
+
67
+ end
68
+
69
+
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Rcqrs::BaseCommandHandler do
4
+
5
+ it "should call execute on inherited class when executeCommand is called" do
6
+ commandHandler = TestCommandHandler.new nil
7
+ commandHandler.executeCommand TestCommand.new("param")
8
+ commandHandler.execute_called?.should == true
9
+ end
10
+
11
+ it "should validate the given command if it provides validation" do
12
+ commandHandler = TestCommandHandler.new nil
13
+ testCommand = TestCommand.new("param")
14
+ def testCommand.invalid?; true; end
15
+ expect {commandHandler.executeCommand testCommand}.to raise_error Rcqrs::CommandNotValidException
16
+ end
17
+
18
+ it "the validation does not fail the command is executed" do
19
+ commandHandler = TestCommandHandler.new nil
20
+ testCommand = TestCommand.new("param")
21
+ def testCommand.invalid?; false; end
22
+ commandHandler.executeCommand testCommand
23
+ commandHandler.execute_called?.should == true
24
+ end
25
+
26
+ it "should put all validationerrors in the Exception if the validation fails" do
27
+ begin
28
+ commandHandler = TestCommandHandler.new nil
29
+ testCommand = TestCommand.new("param")
30
+ def testCommand.invalid?; true; end
31
+ def testCommand.errormessages; ["Error1", "Error2"]; end
32
+ commandHandler.executeCommand testCommand
33
+ rescue Rcqrs::CommandNotValidException => exception
34
+ exception.errors.should == ["Error1", "Error2"]
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,26 @@
1
+ describe Rcqrs::BaseEvent do
2
+
3
+ class TestEvent < Rcqrs::BaseEvent
4
+ property :name
5
+ end
6
+
7
+ subject{TestEvent}
8
+
9
+ it "should allow to define properties on an event which can be initalized in the constructor" do
10
+ TestEvent.new(name: "Timbo").name.should == "Timbo"
11
+ end
12
+
13
+ it "should allow to use strings as keys" do
14
+ TestEvent.new("name" => "Timbo").name.should == "Timbo"
15
+ end
16
+
17
+ it "should reject properties not defined" do
18
+ lambda {TestEvent.new(age: 23)}.should raise_error
19
+ lambda {TestEvent.new("age" => 23)}.should raise_error
20
+ end
21
+
22
+ it "should not allow to define allready used methods as properties" do
23
+ lambda {class TestEvent; property :data ;end}.should raise_error
24
+ end
25
+ end
26
+