euston 1.0.1 → 1.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/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