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