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
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rcqrs.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ rcqrs
2
+ =====
3
+
4
+ cqrs in ruby - see http://martinfowler.com/bliki/CQRS.html for details on cqrs
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/rcqrs.rb ADDED
@@ -0,0 +1,30 @@
1
+ require "rcqrs/version"
2
+
3
+ require "uuid"
4
+ require "active_model"
5
+
6
+ require "rcqrs/repository"
7
+ require "rcqrs/base_event"
8
+ require "rcqrs/aggregate_root_base"
9
+ require "rcqrs/event_collection"
10
+ require "rcqrs/command_source"
11
+ require "rcqrs/base_command_handler"
12
+ require "rcqrs/convention_command_handler"
13
+ require "rcqrs/base_command"
14
+ require "rcqrs/convention_command"
15
+ require "rcqrs/command_not_valid_exception"
16
+ require "rcqrs/denormalizer"
17
+ require "rcqrs/event_bus"
18
+ require "rcqrs/not_an_event_exception"
19
+ require "rcqrs/event_handler"
20
+ require "rcqrs/domain_exception"
21
+ require "rcqrs/event_type_registry"
22
+ require "rcqrs/event_configuration_base"
23
+ require "rcqrs/configuration"
24
+ require "rcqrs/published_event"
25
+
26
+ require "rcqrs/command_executor"
27
+
28
+ module Rcqrs
29
+ # Your code goes here...
30
+ end
@@ -0,0 +1,70 @@
1
+ module Rcqrs
2
+
3
+ class AggregateRootBase
4
+
5
+ def self.handle(eventtype, &handlercode)
6
+ define_method "handle" + eventtype.to_s, &handlercode
7
+ end
8
+
9
+ def initialize id
10
+ @id = id
11
+ end
12
+
13
+ def pending_events
14
+ EventCollection.new @id, events
15
+ end
16
+
17
+ def load_from events
18
+ events.each {|event| handle event}
19
+ end
20
+
21
+ protected
22
+
23
+ def fire event
24
+ unless event.is_a? BaseEvent then
25
+ raise NotAnEventException, "Given event has to inherit from BaseEvent"
26
+ end
27
+ events << event
28
+ handle event
29
+ end
30
+
31
+ private
32
+
33
+ def handle event
34
+ get_handler_method(event).call(event)
35
+ end
36
+
37
+ def get_handler_method event
38
+ methodchecker = Proc.new do |methodSymbol|
39
+ methodname = methodSymbol.to_s
40
+ if is_eventhandlermethod methodname, event.class
41
+ return self.method(methodname)
42
+ end
43
+ end
44
+ self.private_methods.each &methodchecker
45
+ self.methods.each &methodchecker
46
+ return self.method(:unknown_handler)
47
+ end
48
+
49
+ def unknown_handler event
50
+
51
+ end
52
+
53
+ def is_eventhandlermethod(methodname, eventtype)
54
+ methodname.start_with?("handle") && has_event_type_postfix(methodname,eventtype)
55
+ end
56
+
57
+ def has_event_type_postfix(methodname, eventtype)
58
+ methodname[6,methodname.size] == eventtype.name.split("::").last
59
+ end
60
+
61
+ def events
62
+ if (@events == nil)
63
+ @events = Array.new
64
+ end
65
+ return @events
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,22 @@
1
+ module Rcqrs
2
+
3
+ class BaseCommand
4
+ include ActiveModel::Validations
5
+
6
+ def initialize(attributes = {})
7
+ attributes.each do |name, value|
8
+ instance_variable_set("@" + name.to_s, value)
9
+ end
10
+ end
11
+
12
+ def errormessages
13
+ errormessages = Array.new
14
+ errors.each do |attribute, message|
15
+ errormessages << message
16
+ end
17
+ errormessages
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,27 @@
1
+ module Rcqrs
2
+
3
+ class BaseCommandHandler
4
+
5
+ def initialize repository
6
+ @repository = repository
7
+ end
8
+
9
+ def executeCommand command
10
+ if command.respond_to?(:invalid?) && command.invalid?
11
+ if command.respond_to? :errormessages
12
+ raise CommandNotValidException.new(command.errormessages)
13
+ else
14
+ raise CommandNotValidException.new []
15
+ end
16
+ end
17
+ execute command
18
+ end
19
+
20
+ protected
21
+ def repository
22
+ @repository
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,57 @@
1
+ module Rcqrs
2
+
3
+ class BaseEvent
4
+
5
+ attr_reader :data, :published_at
6
+
7
+ def initialize data = {}
8
+ data.each do |k,v|
9
+ raise "Property not defined" unless self.class.has_property(k)
10
+ instance_variable_set("@#{k}",v)
11
+ end
12
+ @data = data
13
+ end
14
+
15
+ def self.property(property_name)
16
+ raise "data and published_at are not allowed property names" if (["data","published_at"].include? property_name.to_s)
17
+ @properties = Array.new unless @properties
18
+ @properties << property_name.to_s
19
+ attr_reader property_name
20
+ end
21
+
22
+ def self.has_property(property)
23
+ @properties.include?(property.to_s)
24
+ end
25
+
26
+ def self.restore_from(data, published_at)
27
+ self.load_from(data, published_at)
28
+ end
29
+
30
+ def self.load_from data, published_at
31
+ event = self.new(data)
32
+ event.instance_variable_set("@published_at",published_at)
33
+ event
34
+ end
35
+
36
+ def store_publish_time
37
+ raise "Event has allready a publish date" if(@published_at != nil)
38
+ @published_at = Time.now
39
+ end
40
+
41
+ def [](index)
42
+ if @data.has_key?(index)
43
+ return @data[index]
44
+ elsif @data.has_key?(index.to_s)
45
+ return @data[index.to_s]
46
+ elsif @data.has_key?(index.to_sym)
47
+ return @data[index.to_sym]
48
+ end
49
+ end
50
+
51
+ def == other
52
+ @data == other.data
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,16 @@
1
+ module Rcqrs
2
+
3
+ class CommandExecutor
4
+ extend Rcqrs::CommandSource
5
+
6
+ def self.eventbus
7
+ if @eventbus == nil
8
+ @eventbus = Rcqrs::Eventbus.new(Configuration.eventstore)
9
+ EventConfigurationBase.register_all_at(@eventbus)
10
+ end
11
+ @eventbus
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,13 @@
1
+ module Rcqrs
2
+
3
+ class CommandNotValidException < Exception
4
+
5
+ attr_accessor :errors
6
+
7
+ def initialize errors
8
+ @errors = errors
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,72 @@
1
+ module Rcqrs
2
+
3
+ module CommandSource
4
+
5
+ def execute(command, *params)
6
+ if command.is_a? Symbol
7
+ commandhandler = get_commandhandler_from_symbol(command)
8
+ commandhandler.executeCommand(*params)
9
+ else
10
+ commandhandler = get_commandhandler_from_command(command)
11
+ commandhandler.executeCommand(command)
12
+ end
13
+ eventbus.commit
14
+ end
15
+
16
+ def get_commandhandler_from_symbol(command_symbol)
17
+ commandhandlerclass = get_handler_class_from_command_name command_symbol.to_s
18
+ commandhandlerclass.new(Repository.new(eventbus))
19
+ end
20
+
21
+ def get_commandhandler_from_command command
22
+ commandhandlerclass = get_commandhandler_class(command)
23
+ commandhandlerclass.new(Repository.new(eventbus))
24
+ end
25
+
26
+ def get_commandhandler_class command
27
+ handler_name = get_handler_class_name(command)
28
+ handlerclass = eval(handler_name)
29
+ checkIfInheritsFromBaseHandler handlerclass
30
+ return handlerclass
31
+ rescue NameError => e
32
+ raise CommandHandlerNotFoundError, "Could not find Commandhandler #{handler_name}, root cause: #{e.to_s}"
33
+ end
34
+
35
+ def get_handler_class_name command
36
+ if(command.is_a? ConventionCommand)
37
+ return ConventionCommandHandler.name
38
+ else
39
+ return get_handler_class_from_command_name(command.class.name)
40
+ end
41
+ end
42
+
43
+ def get_handler_class_from_command_name command_name
44
+ handler_name = String.new
45
+ command_name.to_s.split("_").each do |command_name_part|
46
+ handler_name << capitalize_only_first(command_name_part)
47
+ end
48
+ handler_name << "Handler"
49
+ return handler_name
50
+ end
51
+
52
+ def capitalize_only_first phrase
53
+ phrase[0].upcase + phrase[1..-1]
54
+ end
55
+
56
+ def checkIfInheritsFromBaseHandler handlerclass
57
+ unless handlerclass < BaseCommandHandler
58
+ raise CommandHandlerDoesNotInheritFromBaseError, "#{handlerclass.name} does not inherit from BaseCommandHandler"
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+
65
+
66
+ class Rcqrs::CommandHandlerNotFoundError < Exception
67
+ end
68
+
69
+ class Rcqrs::CommandHandlerDoesNotInheritFromBaseError < Exception
70
+ end
71
+
72
+ end
@@ -0,0 +1,15 @@
1
+ module Rcqrs
2
+ class Configuration
3
+
4
+ class << self
5
+
6
+ attr_accessor :eventstore, :readmodel_database
7
+
8
+ def configure
9
+ yield self
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module Rcqrs
2
+
3
+ class ConventionCommand < Rcqrs::BaseCommand
4
+
5
+ attr_reader :data, :aggregate_id
6
+
7
+ def self.aggregate_id_attribute name
8
+ define_method(:aggregate_id) do
9
+ instance_variable_get("@" + name.to_s)
10
+ end
11
+ end
12
+
13
+ def self.aggregate aggregate
14
+ define_method(:aggregate) do
15
+ return aggregate
16
+ end
17
+ end
18
+
19
+ def initialize(attributes = {})
20
+ super
21
+ attributes.shift
22
+ @data = attributes
23
+ end
24
+
25
+ def == other
26
+ other.class == self.class && other.aggregate_id == self.aggregate_id && other.data == self.data
27
+ end
28
+
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,33 @@
1
+ module Rcqrs
2
+
3
+ class ConventionCommandHandler < Rcqrs::BaseCommandHandler
4
+
5
+ def execute command
6
+ account = repository.load(command.aggregate,command.aggregate_id)
7
+ methodname = get_method_name_from_command command
8
+ if(command.data.empty?)
9
+ account.send(methodname)
10
+ else
11
+ account.send(methodname, command.data)
12
+ end
13
+ repository.save(account)
14
+ end
15
+
16
+ def get_method_name_from_command command
17
+ words = command_name(command).split(%r{[A-Z]})
18
+ first_letters = command_name(command).scan(%r{[A-Z]})
19
+ result = String.new
20
+ words.shift
21
+ 0.upto(words.size-2) do |i|
22
+ result << first_letters[i].downcase + words[i] + "_"
23
+ end
24
+ result[0..-2]
25
+ end
26
+
27
+ def command_name command
28
+ command.class.name.split("::").last
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,79 @@
1
+ module Rcqrs
2
+
3
+ module Denormalizer
4
+
5
+
6
+ attr_accessor :model
7
+
8
+ def map to_map
9
+ if to_map.is_a? Hash
10
+ return HashMapper.new to_map
11
+ end
12
+ return AttributeMapper.new to_map
13
+ end
14
+
15
+ def method_missing(name, *args,&block)
16
+ if(Configuration.readmodel_database.respond_to?(name))
17
+ if(@model)
18
+ Configuration.readmodel_database.send(name,model,*args,&block)
19
+ else
20
+ Configuration.readmodel_database.send(name,*args,&block)
21
+ end
22
+ else
23
+ super
24
+ end
25
+ end
26
+
27
+ class AttributeMapper
28
+
29
+ def initialize attribute_specification
30
+ @attribute_specification = attribute_specification
31
+ end
32
+
33
+ def from source
34
+ @source = source
35
+ self
36
+ end
37
+
38
+ def to target
39
+ @attribute_specification.each do |attribute_name|
40
+ if(target.is_a? Hash)
41
+ target[attribute_name] = @source.send(attribute_name)
42
+ else
43
+ target.send(attribute_name.to_s + "=",@source.send(attribute_name))
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ class HashMapper
51
+
52
+ def initialize hash_to_map
53
+ @hash_to_map = hash_to_map
54
+ end
55
+
56
+ def only keys
57
+ @hash_to_map = @hash_to_map.select {|key,value| keys.count{|k| k.to_s == key.to_s} > 0}
58
+ self
59
+ end
60
+
61
+ def to target
62
+ @hash_to_map.each do |key,value|
63
+ if(target.is_a? Hash)
64
+ if(target.has_key? key.to_sym)
65
+ target[key.to_sym] = value
66
+ else
67
+ target[key] = value
68
+ end
69
+ else
70
+ target.send(key.to_s + "=", value)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end