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
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
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,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,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
|