rcqrs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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