synapse-core 0.5.4 → 0.5.5
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/lib/synapse/command/async_command_bus.rb +3 -5
- data/lib/synapse/common.rb +0 -1
- data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +3 -2
- data/lib/synapse/configuration/component/{mixin → shared}/thread_pool.rb +10 -8
- data/lib/synapse/configuration/dependent.rb +2 -2
- data/lib/synapse/configuration/ext.rb +58 -29
- data/lib/synapse/configuration.rb +1 -1
- data/lib/synapse/domain/aggregate_root.rb +43 -19
- data/lib/synapse/domain/errors.rb +9 -0
- data/lib/synapse/domain/event_container.rb +3 -3
- data/lib/synapse/domain/message.rb +2 -2
- data/lib/synapse/domain/message_builder.rb +2 -2
- data/lib/synapse/domain/simple_stream.rb +39 -0
- data/lib/synapse/domain/stream.rb +2 -41
- data/lib/synapse/domain.rb +3 -0
- data/lib/synapse/event_sourcing/aggregate_root.rb +102 -2
- data/lib/synapse/event_sourcing/caching.rb +1 -1
- data/lib/synapse/event_sourcing/entity.rb +2 -2
- data/lib/synapse/event_sourcing/member.rb +3 -13
- data/lib/synapse/event_sourcing/repository.rb +9 -2
- data/lib/synapse/event_sourcing/snapshot/taker.rb +2 -2
- data/lib/synapse/mapping/mapping.rb +2 -2
- data/lib/synapse/process_manager/simple_process_manager.rb +1 -0
- data/lib/synapse/rails/injection_helper.rb +23 -0
- data/lib/synapse/railtie.rb +17 -0
- data/lib/synapse/repository/lock_manager.rb +3 -3
- data/lib/synapse/uow/listener.rb +2 -2
- data/lib/synapse/uow/listener_collection.rb +7 -51
- data/lib/synapse/version.rb +1 -1
- data/lib/synapse-core.rb +1 -0
- data/lib/synapse.rb +3 -0
- data/test/command/async_command_bus_test.rb +8 -11
- data/test/configuration/component/command_bus/async_command_bus_test.rb +7 -6
- data/test/configuration/component/serialization/serializer_test.rb +2 -2
- data/test/configuration/fixtures/dependent.rb +1 -1
- data/test/rails/injection_helper_test.rb +27 -0
- data/test/serialization/converter/ox_test.rb +2 -2
- data/test/serialization/serializer/oj_test.rb +1 -1
- data/test/serialization/serializer/ox_test.rb +1 -1
- data/test/test_helper.rb +8 -9
- data/test/uow/uow_test.rb +2 -2
- metadata +221 -222
- data/lib/synapse/common/concurrency/executor.rb +0 -13
- data/test/support/countdown_latch.rb +0 -18
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'thread/pool'
|
2
|
-
|
3
1
|
module Synapse
|
4
2
|
module Command
|
5
3
|
# Command bus that uses a thread pool to asynchronously execute commands, invoking the given
|
@@ -7,8 +5,8 @@ module Synapse
|
|
7
5
|
#
|
8
6
|
# @todo Look into non-blocking circular buffers or LMAX Disruptor
|
9
7
|
class AsynchronousCommandBus < SimpleCommandBus
|
10
|
-
# Pool of
|
11
|
-
# @return [
|
8
|
+
# Pool of worker threads that dispatch commands from a queue
|
9
|
+
# @return [Contender::Pool::ThreadPoolExecutor]
|
12
10
|
attr_accessor :thread_pool
|
13
11
|
|
14
12
|
# @api public
|
@@ -16,7 +14,7 @@ module Synapse
|
|
16
14
|
# @param [CommandCallback] callback
|
17
15
|
# @return [undefined]
|
18
16
|
def dispatch_with_callback(command, callback)
|
19
|
-
@thread_pool.
|
17
|
+
@thread_pool.execute do
|
20
18
|
super command, callback
|
21
19
|
end
|
22
20
|
end
|
data/lib/synapse/common.rb
CHANGED
@@ -3,7 +3,6 @@ require 'synapse/common/identifier'
|
|
3
3
|
require 'synapse/common/message'
|
4
4
|
require 'synapse/common/message_builder'
|
5
5
|
|
6
|
-
require 'synapse/common/concurrency/executor'
|
7
6
|
require 'synapse/common/concurrency/identifier_lock'
|
8
7
|
require 'synapse/common/concurrency/public_lock'
|
9
8
|
|
@@ -3,13 +3,14 @@ module Synapse
|
|
3
3
|
# Definition builder used to build an asynchronous command bus
|
4
4
|
#
|
5
5
|
# @see SimpleCommandBusDefinitionBuilder For additional options
|
6
|
+
# @see Contender::Pool::ThreadPoolExecutor For pool options
|
6
7
|
#
|
7
8
|
# @example The minimum possible effort to build an asynchronous command bus
|
8
9
|
# async_command_bus
|
9
10
|
#
|
10
11
|
# @example Create an asynchronous command bus with a custom thread count
|
11
12
|
# async_command_bus do
|
12
|
-
#
|
13
|
+
# use_pool_options size: 2
|
13
14
|
# end
|
14
15
|
class AsynchronousCommandBusDefinitionBuilder < SimpleCommandBusDefinitionBuilder
|
15
16
|
include ThreadPoolDefinitionBuilder
|
@@ -19,7 +20,7 @@ module Synapse
|
|
19
20
|
# @return [undefined]
|
20
21
|
def populate_defaults
|
21
22
|
super
|
22
|
-
|
23
|
+
use_pool_options size: 4
|
23
24
|
end
|
24
25
|
|
25
26
|
# @param [UnitOfWorkFactory] unit_factory
|
@@ -1,25 +1,27 @@
|
|
1
1
|
module Synapse
|
2
2
|
module Configuration
|
3
3
|
# Mixin for a definition builder that creates a service that is backed by a thread pool
|
4
|
+
# @see Contender::Pool::ThreadPoolExecutor For pool options
|
4
5
|
module ThreadPoolDefinitionBuilder
|
5
6
|
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
# Sets the
|
8
|
+
# Sets the options for the thread pool
|
8
9
|
#
|
9
|
-
# @param [
|
10
|
-
# @param [Integer] max_threads
|
10
|
+
# @param [Hash] pool_options
|
11
11
|
# @return [undefined]
|
12
|
-
def
|
13
|
-
@
|
14
|
-
@max_threads = max_threads
|
12
|
+
def use_pool_options(pool_options)
|
13
|
+
@pool_options = pool_options
|
15
14
|
end
|
16
15
|
|
17
16
|
protected
|
18
17
|
|
19
18
|
# Creates a thread pool with the configured options
|
20
|
-
# @return [
|
19
|
+
# @return [Contender::Pool::ThreadPoolExecutor]
|
21
20
|
def create_thread_pool
|
22
|
-
|
21
|
+
pool = Contender::Pool::ThreadPoolExecutor.new @pool_options
|
22
|
+
pool.start
|
23
|
+
|
24
|
+
pool
|
23
25
|
end
|
24
26
|
end # ThreadPoolDefinitionBuilder
|
25
27
|
end # Configuration
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Synapse
|
2
2
|
module Configuration
|
3
|
-
# Mixin for an object defines its dependencies so that they can be auto-injected by the
|
3
|
+
# Mixin for an object defines its dependencies so that they can be auto-injected by the
|
4
4
|
# service container after it has been instantiated
|
5
5
|
#
|
6
6
|
# Note that this only supports setter injection
|
@@ -20,7 +20,7 @@ module Synapse
|
|
20
20
|
def depends_on(service, *args)
|
21
21
|
options = args.extract_options!
|
22
22
|
|
23
|
-
attribute = options[:
|
23
|
+
attribute = options[:as] || service
|
24
24
|
attr_accessor attribute
|
25
25
|
|
26
26
|
self.dependencies[service] = attribute
|
@@ -1,35 +1,64 @@
|
|
1
1
|
module Synapse
|
2
|
-
|
3
|
-
|
2
|
+
class << self
|
3
|
+
# @return [Configuration::Container]
|
4
|
+
attr_accessor :container
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
# @return [Configuration::ContainerBuilder]
|
7
|
+
attr_accessor :container_builder
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
9
|
+
# Initializes the service container and the container builder
|
10
|
+
#
|
11
|
+
# The given block is executed in the container of the container builder. Factory blocks are
|
12
|
+
# always deferred until the service is needed to build another service or is manually
|
13
|
+
# requested from the container.
|
14
|
+
#
|
15
|
+
# This method can be called multiple times without losing the state of the container.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# Synapse.build do
|
19
|
+
# definition :account_projection do
|
20
|
+
# tag :event_listener, :projection
|
21
|
+
# use_factory do
|
22
|
+
# Bank::Projections::AccountProjection.new
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# @see Configuration::ContainerBuilder#build_with
|
28
|
+
# @param [Proc] block
|
29
|
+
# @return [undefined]
|
30
|
+
def build(&block)
|
31
|
+
self.container ||= Configuration::Container.new
|
32
|
+
self.container_builder ||= Configuration::ContainerBuilder.new self.container
|
32
33
|
|
33
|
-
|
34
|
+
self.container_builder.build_with(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Initializes the service container and the container builder and applies simple defaults for
|
38
|
+
# getting started with a single node setup
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Synapse.build_with_defaults do
|
42
|
+
# # Setup things like command handlers, event listeners and repositories
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @see Configuration::ContainerBuilder#build_with
|
46
|
+
# @param [Proc] block
|
47
|
+
# @return [undefined]
|
48
|
+
def build_with_defaults(&block)
|
49
|
+
build do
|
50
|
+
converter_factory
|
51
|
+
serializer
|
52
|
+
upcaster_chain
|
53
|
+
|
54
|
+
unit_factory
|
55
|
+
simple_command_bus
|
56
|
+
gateway
|
57
|
+
|
58
|
+
simple_event_bus
|
59
|
+
end
|
60
|
+
|
61
|
+
build &block
|
62
|
+
end
|
34
63
|
end
|
35
64
|
end
|
@@ -5,7 +5,7 @@ require 'synapse/configuration/definition_builder'
|
|
5
5
|
require 'synapse/configuration/dependent'
|
6
6
|
require 'synapse/configuration/ext'
|
7
7
|
|
8
|
-
require 'synapse/configuration/component/
|
8
|
+
require 'synapse/configuration/component/shared/thread_pool'
|
9
9
|
require 'synapse/configuration/component/command_bus'
|
10
10
|
require 'synapse/configuration/component/event_bus'
|
11
11
|
# Has to be loaded before event sourcing
|
@@ -1,6 +1,22 @@
|
|
1
1
|
module Synapse
|
2
2
|
module Domain
|
3
|
-
# Mixin module for a basic aggregate root
|
3
|
+
# Mixin module for a basic aggregate root that is not event-sourced
|
4
|
+
#
|
5
|
+
# The persistence mechanism is left up to the aggregate root that uses this mixin. Any sort of
|
6
|
+
# ORM can be used to persist aggregates.
|
7
|
+
#
|
8
|
+
# If optimistic locking is used, the ORM must increment the version field before saving.
|
9
|
+
#
|
10
|
+
# class Order
|
11
|
+
# include Synapse::Domain::AggregateRoot
|
12
|
+
# include MongoMapper::Document
|
13
|
+
#
|
14
|
+
# key :version, Integer
|
15
|
+
#
|
16
|
+
# before_save { self.version += 1 }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @see Repository::SimpleRepository
|
4
20
|
module AggregateRoot
|
5
21
|
# @return [Boolean] True if this aggregate has been marked for deletion
|
6
22
|
attr_reader :deleted
|
@@ -14,6 +30,8 @@ module Synapse
|
|
14
30
|
attr_reader :version
|
15
31
|
|
16
32
|
# Marks this aggregate as committed by a repository
|
33
|
+
#
|
34
|
+
# @api public
|
17
35
|
# @return [undefined]
|
18
36
|
def mark_committed
|
19
37
|
if @event_container
|
@@ -23,6 +41,8 @@ module Synapse
|
|
23
41
|
end
|
24
42
|
|
25
43
|
# Returns the number of uncommitted events published by this aggregate
|
44
|
+
#
|
45
|
+
# @api public
|
26
46
|
# @return [Integer]
|
27
47
|
def uncommitted_event_count
|
28
48
|
unless @event_container
|
@@ -33,6 +53,8 @@ module Synapse
|
|
33
53
|
end
|
34
54
|
|
35
55
|
# Returns a domain event strema containing any uncommitted events published by this aggregate
|
56
|
+
#
|
57
|
+
# @api public
|
36
58
|
# @return [DomainEventStream]
|
37
59
|
def uncommitted_events
|
38
60
|
unless @event_container
|
@@ -47,7 +69,8 @@ module Synapse
|
|
47
69
|
# If an event registration listener is added after events have already been registered, it
|
48
70
|
# will still get a change to process the uncommitted events in this aggregate.
|
49
71
|
#
|
50
|
-
# @
|
72
|
+
# @api public
|
73
|
+
# @param [Proc] listener
|
51
74
|
# @return [undefined]
|
52
75
|
def add_registration_listener(&listener)
|
53
76
|
event_container.add_registration_listener listener
|
@@ -57,10 +80,13 @@ module Synapse
|
|
57
80
|
|
58
81
|
# Publishes a domain event with the given payload and optional metadata
|
59
82
|
#
|
83
|
+
# Before any events are published, the aggregate identifier must be set.
|
84
|
+
#
|
60
85
|
# @api public
|
86
|
+
# @raise [AggregateIdentifierNotInitializedError] If identifier not set
|
61
87
|
# @param [Object] payload Payload of the message; the actual event object
|
62
88
|
# @param [Hash] metadata Metadata associated with the event
|
63
|
-
# @return [DomainEventMessage] The event that will be
|
89
|
+
# @return [DomainEventMessage] The event that will be published
|
64
90
|
def publish_event(payload, metadata = nil)
|
65
91
|
event_container.register_event payload, metadata
|
66
92
|
end
|
@@ -73,18 +99,9 @@ module Synapse
|
|
73
99
|
@deleted = true
|
74
100
|
end
|
75
101
|
|
76
|
-
# Initializes the event container with the given sequence number
|
77
|
-
#
|
78
|
-
# @param [Integer] last_sequence_number
|
79
|
-
# The sequence number of the last committed event for this aggregate
|
80
|
-
#
|
81
|
-
# @return [undefined]
|
82
|
-
def initialize_event_container(last_sequence_number)
|
83
|
-
event_container.initialize_sequence_number last_sequence_number
|
84
|
-
@last_sequence_number = last_sequence_number >= 0 ? last_sequence_number : nil
|
85
|
-
end
|
86
|
-
|
87
102
|
# Returns the sequence number of the last committed event
|
103
|
+
#
|
104
|
+
# @api public
|
88
105
|
# @return [Integer]
|
89
106
|
def last_committed_sequence_number
|
90
107
|
unless @event_container
|
@@ -96,6 +113,16 @@ module Synapse
|
|
96
113
|
|
97
114
|
private
|
98
115
|
|
116
|
+
# Initializes the event container with the given sequence number
|
117
|
+
#
|
118
|
+
# @param [Integer] last_sequence_number
|
119
|
+
# The sequence number of the last committed event for this aggregate
|
120
|
+
# @return [undefined]
|
121
|
+
def initialize_event_container(last_sequence_number)
|
122
|
+
event_container.initialize_sequence_number last_sequence_number
|
123
|
+
@last_sequence_number = last_sequence_number >= 0 ? last_sequence_number : nil
|
124
|
+
end
|
125
|
+
|
99
126
|
# Initializes the uncommitted event container for this aggregate, if not already
|
100
127
|
#
|
101
128
|
# @raise [AggregateIdentifierNotInitializedError] If identifier not set
|
@@ -112,9 +139,6 @@ module Synapse
|
|
112
139
|
|
113
140
|
@event_container
|
114
141
|
end
|
115
|
-
end
|
116
|
-
|
117
|
-
# Raised when an event is published but the aggregate identifier is not set
|
118
|
-
class AggregateIdentifierNotInitializedError < NonTransientError; end
|
119
|
-
end
|
142
|
+
end # AggregateRoot
|
143
|
+
end # Domain
|
120
144
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Domain
|
3
|
+
# Raised when an event is published but the aggregate identifier is not set
|
4
|
+
class AggregateIdentifierNotInitializedError < NonTransientError; end
|
5
|
+
|
6
|
+
# Raised when the end of a domain event stream has been reached
|
7
|
+
class EndOfStreamError < NonTransientError; end
|
8
|
+
end
|
9
|
+
end
|
@@ -56,7 +56,7 @@ module Synapse
|
|
56
56
|
# If the listener is added after events have already registered with the container, it will
|
57
57
|
# be called with a backlog of events to process.
|
58
58
|
#
|
59
|
-
# @param [
|
59
|
+
# @param [Proc] listener
|
60
60
|
# @return [undefined]
|
61
61
|
def add_registration_listener(listener)
|
62
62
|
@listeners.push listener
|
@@ -122,6 +122,6 @@ module Synapse
|
|
122
122
|
def next_sequence_number
|
123
123
|
last_sequence_number ? last_sequence_number.next : 0
|
124
124
|
end
|
125
|
-
end
|
126
|
-
end
|
125
|
+
end # EventContainer
|
126
|
+
end # Domain
|
127
127
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Domain
|
3
|
+
# Implementation of a domain event stream that holds a stream of events in memory
|
4
|
+
class SimpleDomainEventStream < DomainEventStream
|
5
|
+
def initialize(*events)
|
6
|
+
@events = events.flatten
|
7
|
+
@next_index = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if the end of the stream has been reached
|
11
|
+
# @return [Boolean]
|
12
|
+
def end?
|
13
|
+
@next_index >= @events.size
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the next event in the stream and moves the stream's pointer forward
|
17
|
+
#
|
18
|
+
# @raise [EndOfStreamError] If the end of the stream has been reached
|
19
|
+
# @return [DomainEventMessage]
|
20
|
+
def next_event
|
21
|
+
assert_valid
|
22
|
+
|
23
|
+
event = @events.at @next_index
|
24
|
+
@next_index += 1
|
25
|
+
|
26
|
+
event
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the next event in the stream without moving the stream's pointer forward
|
30
|
+
#
|
31
|
+
# @raise [EndOfStreamError] If the end of the stream has been reached
|
32
|
+
# @return [DomainEventMessage]
|
33
|
+
def peek
|
34
|
+
assert_valid
|
35
|
+
@events.at @next_index
|
36
|
+
end
|
37
|
+
end # SimpleDomainEventStream
|
38
|
+
end # Domain
|
39
|
+
end
|
@@ -64,45 +64,6 @@ module Synapse
|
|
64
64
|
raise EndOfStreamError
|
65
65
|
end
|
66
66
|
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Raised when the end of a domain event stream has been reached
|
70
|
-
class EndOfStreamError < NonTransientError; end
|
71
|
-
|
72
|
-
# Implementation of a domain event stream that holds a stream of events in memory
|
73
|
-
class SimpleDomainEventStream < DomainEventStream
|
74
|
-
def initialize(*events)
|
75
|
-
@events = events.flatten
|
76
|
-
@next_index = 0
|
77
|
-
end
|
78
|
-
|
79
|
-
# Returns true if the end of the stream has been reached
|
80
|
-
# @return [Boolean]
|
81
|
-
def end?
|
82
|
-
@next_index >= @events.size
|
83
|
-
end
|
84
|
-
|
85
|
-
# Returns the next event in the stream and moves the stream's pointer forward
|
86
|
-
#
|
87
|
-
# @raise [EndOfStreamError] If the end of the stream has been reached
|
88
|
-
# @return [DomainEventMessage]
|
89
|
-
def next_event
|
90
|
-
assert_valid
|
91
|
-
|
92
|
-
event = @events.at @next_index
|
93
|
-
@next_index += 1
|
94
|
-
|
95
|
-
event
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns the next event in the stream without moving the stream's pointer forward
|
99
|
-
#
|
100
|
-
# @raise [EndOfStreamError] If the end of the stream has been reached
|
101
|
-
# @return [DomainEventMessage]
|
102
|
-
def peek
|
103
|
-
assert_valid
|
104
|
-
@events.at @next_index
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
67
|
+
end # DomainEventStream
|
68
|
+
end # Domain
|
108
69
|
end
|
data/lib/synapse/domain.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'synapse/domain/errors'
|
2
|
+
|
1
3
|
require 'synapse/domain/aggregate_root'
|
2
4
|
require 'synapse/domain/event_container'
|
3
5
|
require 'synapse/domain/message'
|
4
6
|
require 'synapse/domain/message_builder'
|
5
7
|
require 'synapse/domain/stream'
|
8
|
+
require 'synapse/domain/simple_stream'
|
@@ -1,6 +1,106 @@
|
|
1
1
|
module Synapse
|
2
2
|
module EventSourcing
|
3
3
|
# Mixin for the root entity of an aggregate that is initialized from a historical event stream
|
4
|
+
#
|
5
|
+
# = Handling events
|
6
|
+
#
|
7
|
+
# For ease of use, the event mapping DSL is included in event-sourced aggregates. This includes
|
8
|
+
# both the aggregate root and any entities that are a member of the aggregate.
|
9
|
+
#
|
10
|
+
# This mapping DSL can be used to map handlers for events that have been applied to the
|
11
|
+
# aggregate
|
12
|
+
#
|
13
|
+
# class Order
|
14
|
+
# include Synapse::EventSourcing::AggregateRoot
|
15
|
+
#
|
16
|
+
# def add_item(sku, quantity, unit_price)
|
17
|
+
# # Do business logic validation here
|
18
|
+
# apply(new ItemsAddedToOrder(id, sku, quantity, unit_price))
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# map_event ItemsAddedToOrder do |event|
|
22
|
+
# @value = @value + (event.quantity * event.unit_price)
|
23
|
+
# @line_items.add(OrderLineItem.new(sku, quantity, unit_price))
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# Events applied to the aggregate root are cascaded down to any child entities of the aggregate
|
28
|
+
# root. Events applied to a child entity are actually applied to the aggregate root's event
|
29
|
+
# container, and are then cascaded down to the child entities.
|
30
|
+
#
|
31
|
+
# = Initialization
|
32
|
+
#
|
33
|
+
# An aggregate can be initialized in three ways:
|
34
|
+
#
|
35
|
+
# - Created by the caller using new, usually to create a new aggregate
|
36
|
+
# - Allocated, then initialized from an event stream
|
37
|
+
# - Deserialized from a snapshot
|
38
|
+
#
|
39
|
+
# Because of the different ways the aggregate can be created, it is necessary to separate out
|
40
|
+
# the logic needed to create data structures required by the aggregate to function.
|
41
|
+
#
|
42
|
+
# Ruby does not support method overloading, so there is a hook provided to do any logic
|
43
|
+
# necessary to instantiate the aggregate, such as creating collections.
|
44
|
+
#
|
45
|
+
# class Order
|
46
|
+
# include Synapse::EventSourcing::AggregateRoot
|
47
|
+
#
|
48
|
+
# def initialize(id, max_value)
|
49
|
+
# pre_initialize
|
50
|
+
# apply(OrderCreated.new(id, max_value))
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# protected
|
54
|
+
#
|
55
|
+
# def pre_initialize
|
56
|
+
# @line_items = Set.new
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# Instead of using the pre-initialization hook, you can also do lazy initialization.
|
61
|
+
#
|
62
|
+
# class Order
|
63
|
+
# include Synapse::EventSourcing::AggregateRoot
|
64
|
+
#
|
65
|
+
# def line_items
|
66
|
+
# @line_items ||= Set.new
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# = Snapshots
|
71
|
+
#
|
72
|
+
# When the event stream for an aggregate becomes large, it can put a drain on the application
|
73
|
+
# to have to load the entire stream of an aggregate just to perform a single operation upon
|
74
|
+
# it.
|
75
|
+
#
|
76
|
+
# To solve this issue, Synapse supports snapshotting aggregates. The built-in way to snapshot
|
77
|
+
# is simply to serialize the aggregate root, since it contains entire state of the aggregate.
|
78
|
+
#
|
79
|
+
# When using a marshalling serializer, like the built-in Ruby marshaller or ones like Oj and
|
80
|
+
# Ox, no work needs to be done to prepare the aggregate for serialization. However, when using
|
81
|
+
# the attribute serializer, you have to treat snapshots as mementos.
|
82
|
+
#
|
83
|
+
# class Order
|
84
|
+
# include Synapse::EventSourcing::AggregateRoot
|
85
|
+
#
|
86
|
+
# def attributes
|
87
|
+
# line_items = @line_items.map { |li| li.attributes }
|
88
|
+
# { id: @id, value: @value }
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# # Note that this is called after #allocate
|
92
|
+
# def attributes=(attributes)
|
93
|
+
# @id = attributes[:id]
|
94
|
+
# @value = attributes[:value]
|
95
|
+
# # Yeah, it's pretty ugly to support this
|
96
|
+
# @line_items = attributes[:line_items].map do |line_item|
|
97
|
+
# OrderLineItem.allocate.tap { |li| li.attributes = line_item }
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# It would be nice to have something like XStream to make serialization easier, but as far as
|
103
|
+
# I can tell, there's nothing even close to it for Ruby.
|
4
104
|
module AggregateRoot
|
5
105
|
extend ActiveSupport::Concern
|
6
106
|
include Domain::AggregateRoot
|
@@ -119,6 +219,6 @@ module Synapse
|
|
119
219
|
entity.handle_aggregate_event event
|
120
220
|
end
|
121
221
|
end
|
122
|
-
end
|
123
|
-
end
|
222
|
+
end # AggregateRoot
|
223
|
+
end # EventSourcing
|
124
224
|
end
|
@@ -28,7 +28,7 @@ module Synapse
|
|
28
28
|
if aggregate.nil?
|
29
29
|
aggregate = super aggregate_id, expected_version
|
30
30
|
elsif aggregate.deleted?
|
31
|
-
raise AggregateDeletedError
|
31
|
+
raise AggregateDeletedError.new type_identifier, aggregate_id
|
32
32
|
end
|
33
33
|
|
34
34
|
register_listener CacheClearingUnitOfWorkListener.new aggregate_id, @cache
|
@@ -2,17 +2,16 @@ module Synapse
|
|
2
2
|
module EventSourcing
|
3
3
|
# Base mixin for a member of an aggregate which has its state mutated by events that are
|
4
4
|
# applied to the aggregate
|
5
|
+
#
|
6
|
+
# @see AggregateRoot
|
7
|
+
# @see Entity
|
5
8
|
module Member
|
6
9
|
extend ActiveSupport::Concern
|
7
10
|
|
8
11
|
included do
|
9
|
-
# @return [Mapper::Mapping]
|
10
|
-
class_attribute :command_mapper
|
11
|
-
|
12
12
|
# @return [Mapper::Mapping]
|
13
13
|
class_attribute :event_mapper
|
14
14
|
|
15
|
-
self.command_mapper = Mapping::Mapper.new false
|
16
15
|
self.event_mapper = Mapping::Mapper.new true
|
17
16
|
end
|
18
17
|
|
@@ -33,15 +32,6 @@ module Synapse
|
|
33
32
|
@child_entities ||= Set.new
|
34
33
|
end
|
35
34
|
|
36
|
-
# @see Mapper#map
|
37
|
-
# @param [Class] type
|
38
|
-
# @param [Object...] args
|
39
|
-
# @param [Proc] block
|
40
|
-
# @return [undefined]
|
41
|
-
def map_command(type, *args, &block)
|
42
|
-
commnad_mapper.map type, *args, &block
|
43
|
-
end
|
44
|
-
|
45
35
|
# @see Mapper#map
|
46
36
|
# @param [Class] type
|
47
37
|
# @param [Object...] args
|