euston 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,3 +1,2 @@
1
1
  source :rubygems
2
2
  gemspec
3
-
data/euston.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'euston'
3
- s.version = '1.0.1'
4
- s.date = '2011-09-27'
3
+ s.version = '1.1.0'
4
+ s.date = '2011-10-03'
5
5
  s.platform = RUBY_PLATFORM.to_s == 'java' ? 'java' : Gem::Platform::RUBY
6
6
  s.authors = ['Lee Henson', 'Guy Boertje']
7
7
  s.email = ['lee.m.henson@gmail.com', 'guyboertje@gmail.com']
@@ -17,10 +17,16 @@ Gem::Specification.new do |s|
17
17
  lib/euston.rb
18
18
  lib/euston/aggregate_command_map.rb
19
19
  lib/euston/aggregate_root.rb
20
+ lib/euston/aggregate_root_dsl_methods.rb
21
+ lib/euston/aggregate_root_private_method_names.rb
22
+ lib/euston/command.rb
20
23
  lib/euston/command_bus.rb
21
24
  lib/euston/command_handler.rb
25
+ lib/euston/command_handler_private_method_names.rb
22
26
  lib/euston/command_headers.rb
27
+ lib/euston/event.rb
23
28
  lib/euston/event_handler.rb
29
+ lib/euston/event_handler_private_method_names.rb
24
30
  lib/euston/event_headers.rb
25
31
  lib/euston/null_logger.rb
26
32
  lib/euston/repository.rb
@@ -35,10 +41,10 @@ Gem::Specification.new do |s|
35
41
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
36
42
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
37
43
 
44
+ s.add_dependency 'activemodel', '~> 3.0.9'
38
45
  s.add_dependency 'activesupport', '~> 3.0.9'
39
- s.add_dependency 'euston-eventstore', '~> 1.0.0'
40
- s.add_dependency 'require_all', '~> 1.2.0'
46
+
41
47
  s.add_development_dependency 'fuubar', '~> 0.0.0'
42
48
  s.add_development_dependency 'rspec', '~> 2.6.0'
43
49
  s.add_development_dependency 'uuid', '~> 2.3.0'
44
- end
50
+ end
@@ -1,50 +1,22 @@
1
1
  module Euston
2
2
  module AggregateRoot
3
3
  extend ActiveSupport::Concern
4
+ include Euston::AggregateRootPrivateMethodNames
5
+ include Euston::AggregateRootDslMethods
6
+ include Euston::EventHandler
4
7
 
5
8
  module ClassMethods
6
- def applies event, version, &consumer
7
- define_method "__consume__#{event}__v#{version}" do |*args| instance_exec *args, &consumer end
8
- end
9
-
10
- def consumes *arguments, &consumer #*args is an array of symbols plus an optional options hash at the end
11
- commands, options = [], {}
12
- while (arg = arguments.shift) do
13
- commands << arg if arg.is_a?(Symbol)
14
- options = arg if arg.is_a?(Hash)
15
- end
16
- commands.each do |command|
17
- define_method "__consume__#{command}" do |*args| instance_exec *args, &consumer end
18
-
19
- map_command :map_command_as_aggregate_method, self, command, options
20
- end
21
- end
22
-
23
- def created_by command, options = {}, &consumer
24
- define_method "__consume__#{command}" do |*args| instance_exec *args, &consumer end
25
-
26
- map_command :map_command_as_aggregate_constructor, self, command, options
27
- end
28
-
29
- def hydrate(stream)
9
+ def hydrate stream, snapshot = nil
30
10
  instance = self.new
31
- instance.send :reconstitute_from_history, stream
11
+ instance.send :apply_snapshot, snapshot unless snapshot.nil?
12
+ instance.send :apply_stream, stream
32
13
  instance
33
14
  end
34
-
35
- private
36
-
37
- def map_command(entry_point, type, command, opts)
38
- id = opts.has_key?(:id) ? opts[:id] : :id
39
- to_i = opts.key?(:to_i) ? opts[:to_i] : []
40
-
41
- Euston::AggregateCommandMap.send entry_point, type, command, id, to_i
42
- end
43
15
  end
44
16
 
45
17
  module InstanceMethods
46
18
  def initialize aggregate_id = nil
47
- @aggregate_id = aggregate_id unless aggregate_id.nil?
19
+ @aggregate_id = aggregate_id
48
20
  end
49
21
 
50
22
  attr_reader :aggregate_id
@@ -76,6 +48,19 @@ module Euston
76
48
  self
77
49
  end
78
50
 
51
+ def take_snapshot
52
+ methods = self.class.instance_methods
53
+ regex = self.class.take_snapshot_regexp
54
+ methods = methods.map { |m| regex.match m }.compact
55
+
56
+ raise "You tried to take a snapshot of #{self.class.name} but no snapshot method was found." if methods.empty?
57
+
58
+ version = methods.map { |m| m[1].to_i }.sort.last
59
+ name = self.class.take_snapshot_method_name version
60
+
61
+ { :version => version, :payload => send(name) }
62
+ end
63
+
79
64
  def replay_event(headers, event)
80
65
  headers = Euston::EventHeaders.from_hash(headers) if headers.is_a?(Hash)
81
66
  command = headers.command
@@ -92,7 +77,7 @@ module Euston
92
77
  protected
93
78
 
94
79
  def apply_event(type, version, body = {})
95
- event = Euston::EventStore::EventMessage.new(body.is_a?(Hash) ? body : body.marshal_dump)
80
+ event = Euston::Event.new(body.is_a?(OpenStruct) ? body.marshal_dump : body)
96
81
  event.headers.merge! :id => Euston.uuid.generate,
97
82
  :type => type,
98
83
  :version => version,
@@ -106,32 +91,48 @@ module Euston
106
91
  uncommitted_events << event
107
92
  end
108
93
 
109
- def handle_command(headers, command)
110
- name = "__consume__#{headers.type}"
111
- method(name).call OpenStruct.new(command).freeze
112
- end
94
+ def apply_snapshot snapshot
95
+ if !snapshot.nil?
96
+ version = snapshot.headers[:version]
97
+ raise "Trying to load a snapshot of aggregate #{self.class.name} but it does not have a load_snapshot method for version #{version}!" unless respond_to? self.class.load_snapshot_method_name(version)
113
98
 
114
- def handle_event(headers, event)
115
- name = "__consume__#{headers.type}__v#{headers.version}"
116
- if respond_to? name.to_sym
117
- method(name).call OpenStruct.new(event).freeze
118
- else
119
- raise "Couldn't find an event handler for #{headers.type} (v#{headers.version}) on #{self.class}. Did you forget an 'applies' block?"
99
+ name = self.class.load_snapshot_method_name version
100
+ self.send name, snapshot.payload
120
101
  end
121
102
  end
122
103
 
123
- def reconstitute_from_history(stream)
104
+ def apply_stream stream
105
+ @aggregate_id = stream.stream_id
106
+
124
107
  events = stream.committed_events
125
108
  return if events.empty?
126
109
 
127
110
  raise "This aggregate cannot apply a historical event stream because it is not empty." unless uncommitted_events.empty? && initial_version == 0
128
111
 
129
- @aggregate_id = stream.stream_id
130
-
131
112
  events.each_with_index do |event, i|
132
113
  replay_event Euston::EventHeaders.from_hash(event.headers), event.body
133
114
  end
134
115
  end
116
+
117
+ def handle_command headers, command
118
+ deliver_message headers, command, :consumes_method_name, 'a command', "a 'consumes' block"
119
+ end
120
+
121
+ def handle_event headers, event
122
+ deliver_message headers, event, :applies_method_name, 'an event', "an 'applies' block"
123
+ end
124
+
125
+ private
126
+
127
+ def deliver_message headers, message, name_method, message_kind, expected_block_kind
128
+ name = self.class.send name_method, headers.type, headers.version
129
+
130
+ if respond_to? name.to_sym
131
+ method(name).call OpenStruct.new(message).freeze
132
+ else
133
+ raise "Couldn't deliver #{message_kind} (#{headers.type} v#{headers.version}) to #{self.class}. Did you forget #{expected_block_kind}?"
134
+ end
135
+ end
135
136
  end
136
137
  end
137
138
  end
@@ -0,0 +1,54 @@
1
+ module Euston
2
+ module AggregateRootDslMethods
3
+ extend ActiveSupport::Concern
4
+ include Euston::AggregateRootPrivateMethodNames
5
+
6
+ module ClassMethods
7
+ def applies event, version = 1, &block
8
+ define_private_method applies_method_name(event, version), &block
9
+ end
10
+
11
+ def consumes *arguments, &block #*args is an array of symbols plus an optional options hash at the end
12
+ commands, options = [], {}
13
+
14
+ while (arg = arguments.shift) do
15
+ commands << { :name => arg, :version => 1 } if arg.is_a?(Symbol)
16
+ commands.last[:version] = arg if arg.is_a?(Integer)
17
+ options = arg if arg.is_a?(Hash)
18
+ end
19
+
20
+ commands.each do |command|
21
+ define_private_method consumes_method_name(command[:name], command[:version]), &block
22
+ map_command :map_command_as_aggregate_method, self, command[:name], options
23
+ end
24
+ end
25
+
26
+ def created_by command, version = 1, options = {}, &block
27
+ define_method consumes_method_name(command, version), &block
28
+
29
+ map_command :map_command_as_aggregate_constructor, self, command, options
30
+ end
31
+
32
+ def load_snapshot version, &block
33
+ define_private_method load_snapshot_method_name(version), &block
34
+ end
35
+
36
+ def take_snapshot version, &block
37
+ define_private_method take_snapshot_method_name(version), &block
38
+ end
39
+
40
+ private
41
+
42
+ def define_private_method name, &block
43
+ define_method name do |*args| instance_exec *args, &block end
44
+ end
45
+
46
+ def map_command(entry_point, type, command, opts)
47
+ id = opts.has_key?(:id) ? opts[:id] : :id
48
+ to_i = opts.key?(:to_i) ? opts[:to_i] : []
49
+
50
+ Euston::AggregateCommandMap.send entry_point, type, command, id, to_i
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ module Euston
2
+ module AggregateRootPrivateMethodNames
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def applies_method_name event, version
7
+ "__apply__#{event}__v#{version}__"
8
+ end
9
+
10
+ def consumes_method_name command, version
11
+ "__consume__#{command}__v#{version}__"
12
+ end
13
+
14
+ def id_from_event_method_name type, version
15
+ "__id_from_event_#{type}__v#{version}__"
16
+ end
17
+
18
+ def load_snapshot_method_name version
19
+ "__load_snapshot__v#{version}__"
20
+ end
21
+
22
+ def take_snapshot_method_name version
23
+ "__take_snapshot__v#{version}__"
24
+ end
25
+
26
+ def take_snapshot_regexp
27
+ /__take_snapshot__v(\d+)__/
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ module Euston
2
+ class Command
3
+ include ActiveModel::Validations
4
+
5
+ def initialize body
6
+ @headers = { :id => Uuid.generate,
7
+ :type => self.class.to_s.split('::').pop.underscore.to_sym }
8
+ @body = body
9
+ end
10
+
11
+ def read_attribute_for_validation key
12
+ @body[key]
13
+ end
14
+
15
+ def to_hash
16
+ { :headers => @headers.merge(:version => version), :body => @body }
17
+ end
18
+
19
+ def id
20
+ @headers[:id]
21
+ end
22
+
23
+ def version
24
+ 1
25
+ end
26
+ end
27
+ end
@@ -1,10 +1,11 @@
1
1
  module Euston
2
2
  module CommandHandler
3
3
  extend ActiveSupport::Concern
4
+ include Euston::CommandHandlerPrivateMethodNames
4
5
 
5
6
  module ClassMethods
6
7
  def version number, &consumer
7
- define_method "__version__#{number}" do |*args|
8
+ define_method command_handler_method_name(number) do |*args|
8
9
  if block_given?
9
10
  instance_exec *args, &consumer
10
11
  else
@@ -22,4 +23,4 @@ module Euston
22
23
  end
23
24
  end
24
25
  end
25
- end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Euston
2
+ module CommandHandlerPrivateMethodNames
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def command_handler_method_name version
7
+ "__version__#{version}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -21,5 +21,9 @@ module Euston
21
21
  def self.from_hash hash
22
22
  self.new hash[:id], hash[:type].to_sym, hash[:version], ( hash[:log_completion] || false )
23
23
  end
24
+
25
+ def to_s
26
+ "#{id} #{type} (v#{version})"
27
+ end
24
28
  end
25
29
  end
@@ -0,0 +1,18 @@
1
+ module Euston
2
+ class Event
3
+ def initialize data = {}
4
+ if (data.keys & ['body', 'headers']).size == 2
5
+ @body, @headers = data.values_at 'body', 'headers'
6
+ else
7
+ @headers = {}
8
+ @body = data
9
+ end
10
+ end
11
+
12
+ attr_reader :headers, :body
13
+
14
+ def to_hash
15
+ { :headers => @headers, :body => @body }
16
+ end
17
+ end
18
+ end
@@ -1,13 +1,26 @@
1
1
  module Euston
2
2
  module EventHandler
3
3
  extend ActiveSupport::Concern
4
+ include Euston::EventHandlerPrivateMethodNames
4
5
 
5
6
  module ClassMethods
6
- def consumes type, version, &consumer
7
- define_method "__event_handler__#{type}__#{version}" do |*args|
7
+ def subscribes type, version = 1, opts = nil, &consumer
8
+ if self.include? Euston::AggregateRoot
9
+ opts = opts || { :id => :id }
10
+
11
+ self.class.send :define_method, id_from_event_method_name(type, version) do |event|
12
+ if opts[:id].respond_to? :call
13
+ opts[:id].call event
14
+ else
15
+ event[opts[:id]]
16
+ end
17
+ end
18
+ end
19
+
20
+ define_method event_handler_method_name(type, version) do |*args|
8
21
  instance_exec *args, &consumer
9
22
  end
10
23
  end
11
24
  end
12
25
  end
13
- end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Euston
2
+ module EventHandlerPrivateMethodNames
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def id_from_event_method_name type, version
7
+ "__id_from_event_#{type}__v#{version}__"
8
+ end
9
+
10
+ def event_handler_method_name type, version
11
+ "__event_handler__#{type}__#{version}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,18 +3,8 @@ module Euston
3
3
  class << self
4
4
  attr_accessor :event_store
5
5
 
6
- def find type, id
7
- stream = event_store.open_stream :stream_id => id
8
- return nil if stream.committed_events.empty?
9
-
10
- type.hydrate stream
11
- end
12
-
13
- def save aggregate
14
- stream = event_store.open_stream :stream_id => aggregate.aggregate_id
15
- aggregate.uncommitted_events.each { |e| stream << e }
16
- stream.commit_changes Euston.uuid.generate
17
- end
6
+ # def find(type, id) mixed in by event store implementation
7
+ # def save(aggregate) mixed in by event store implementation
18
8
  end
19
9
  end
20
- end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module Euston
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/euston.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'active_support/concern'
2
- require 'require_all'
2
+ require 'active_model'
3
3
  require 'ostruct'
4
4
 
5
5
  module Euston
@@ -21,5 +21,18 @@ end
21
21
 
22
22
  Euston.uuid = Uuid
23
23
 
24
- require 'euston-eventstore'
25
- require_rel 'euston'
24
+ require 'euston/aggregate_command_map'
25
+ require 'euston/aggregate_root_private_method_names'
26
+ require 'euston/aggregate_root_dsl_methods'
27
+ require 'euston/command'
28
+ require 'euston/command_bus'
29
+ require 'euston/command_handler_private_method_names'
30
+ require 'euston/command_handler'
31
+ require 'euston/command_headers'
32
+ require 'euston/event'
33
+ require 'euston/event_handler_private_method_names'
34
+ require 'euston/event_handler'
35
+ require 'euston/event_headers'
36
+ require 'euston/null_logger'
37
+ require 'euston/aggregate_root'
38
+ require 'euston/repository'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: euston
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-09-27 00:00:00.000000000 Z
13
+ date: 2011-10-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: activesupport
17
- requirement: &72535020 !ruby/object:Gem::Requirement
16
+ name: activemodel
17
+ requirement: &80875570 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,32 +22,21 @@ dependencies:
22
22
  version: 3.0.9
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *72535020
26
- - !ruby/object:Gem::Dependency
27
- name: euston-eventstore
28
- requirement: &72534700 !ruby/object:Gem::Requirement
29
- none: false
30
- requirements:
31
- - - ~>
32
- - !ruby/object:Gem::Version
33
- version: 1.0.0
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: *72534700
25
+ version_requirements: *80875570
37
26
  - !ruby/object:Gem::Dependency
38
- name: require_all
39
- requirement: &72534290 !ruby/object:Gem::Requirement
27
+ name: activesupport
28
+ requirement: &80874680 !ruby/object:Gem::Requirement
40
29
  none: false
41
30
  requirements:
42
31
  - - ~>
43
32
  - !ruby/object:Gem::Version
44
- version: 1.2.0
33
+ version: 3.0.9
45
34
  type: :runtime
46
35
  prerelease: false
47
- version_requirements: *72534290
36
+ version_requirements: *80874680
48
37
  - !ruby/object:Gem::Dependency
49
38
  name: fuubar
50
- requirement: &72534050 !ruby/object:Gem::Requirement
39
+ requirement: &80874230 !ruby/object:Gem::Requirement
51
40
  none: false
52
41
  requirements:
53
42
  - - ~>
@@ -55,10 +44,10 @@ dependencies:
55
44
  version: 0.0.0
56
45
  type: :development
57
46
  prerelease: false
58
- version_requirements: *72534050
47
+ version_requirements: *80874230
59
48
  - !ruby/object:Gem::Dependency
60
49
  name: rspec
61
- requirement: &72533780 !ruby/object:Gem::Requirement
50
+ requirement: &80873750 !ruby/object:Gem::Requirement
62
51
  none: false
63
52
  requirements:
64
53
  - - ~>
@@ -66,10 +55,10 @@ dependencies:
66
55
  version: 2.6.0
67
56
  type: :development
68
57
  prerelease: false
69
- version_requirements: *72533780
58
+ version_requirements: *80873750
70
59
  - !ruby/object:Gem::Dependency
71
60
  name: uuid
72
- requirement: &72533470 !ruby/object:Gem::Requirement
61
+ requirement: &80873120 !ruby/object:Gem::Requirement
73
62
  none: false
74
63
  requirements:
75
64
  - - ~>
@@ -77,7 +66,7 @@ dependencies:
77
66
  version: 2.3.0
78
67
  type: :development
79
68
  prerelease: false
80
- version_requirements: *72533470
69
+ version_requirements: *80873120
81
70
  description: ''
82
71
  email:
83
72
  - lee.m.henson@gmail.com
@@ -92,10 +81,16 @@ files:
92
81
  - lib/euston.rb
93
82
  - lib/euston/aggregate_command_map.rb
94
83
  - lib/euston/aggregate_root.rb
84
+ - lib/euston/aggregate_root_dsl_methods.rb
85
+ - lib/euston/aggregate_root_private_method_names.rb
86
+ - lib/euston/command.rb
95
87
  - lib/euston/command_bus.rb
96
88
  - lib/euston/command_handler.rb
89
+ - lib/euston/command_handler_private_method_names.rb
97
90
  - lib/euston/command_headers.rb
91
+ - lib/euston/event.rb
98
92
  - lib/euston/event_handler.rb
93
+ - lib/euston/event_handler_private_method_names.rb
99
94
  - lib/euston/event_headers.rb
100
95
  - lib/euston/null_logger.rb
101
96
  - lib/euston/repository.rb