synapse-core 0.1.2
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.rb +351 -0
- data/lib/synapse/command/command_bus.rb +45 -0
- data/lib/synapse/command/command_callback.rb +18 -0
- data/lib/synapse/command/command_filter.rb +17 -0
- data/lib/synapse/command/command_handler.rb +13 -0
- data/lib/synapse/command/dispatch_interceptor.rb +16 -0
- data/lib/synapse/command/duplication.rb +43 -0
- data/lib/synapse/command/errors.rb +27 -0
- data/lib/synapse/command/filters/validation.rb +32 -0
- data/lib/synapse/command/gateway.rb +34 -0
- data/lib/synapse/command/interceptor_chain.rb +31 -0
- data/lib/synapse/command/interceptors/serialization.rb +35 -0
- data/lib/synapse/command/message.rb +19 -0
- data/lib/synapse/command/rollback_policy.rb +22 -0
- data/lib/synapse/command/simple_command_bus.rb +138 -0
- data/lib/synapse/command/wiring.rb +47 -0
- data/lib/synapse/domain/aggregate_root.rb +121 -0
- data/lib/synapse/domain/event_container.rb +127 -0
- data/lib/synapse/domain/message.rb +82 -0
- data/lib/synapse/domain/message_builder.rb +34 -0
- data/lib/synapse/domain/stream.rb +108 -0
- data/lib/synapse/duplication.rb +60 -0
- data/lib/synapse/errors.rb +13 -0
- data/lib/synapse/event_bus/event_bus.rb +40 -0
- data/lib/synapse/event_bus/event_listener.rb +16 -0
- data/lib/synapse/event_bus/event_listener_proxy.rb +12 -0
- data/lib/synapse/event_bus/simple_event_bus.rb +69 -0
- data/lib/synapse/event_bus/wiring.rb +23 -0
- data/lib/synapse/event_sourcing/aggregate_factory.rb +69 -0
- data/lib/synapse/event_sourcing/aggregate_root.rb +104 -0
- data/lib/synapse/event_sourcing/conflict_resolver.rb +80 -0
- data/lib/synapse/event_sourcing/entity.rb +64 -0
- data/lib/synapse/event_sourcing/member.rb +72 -0
- data/lib/synapse/event_sourcing/repository.rb +119 -0
- data/lib/synapse/event_sourcing/snapshot/count_stream.rb +86 -0
- data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +91 -0
- data/lib/synapse/event_sourcing/snapshot/taker.rb +73 -0
- data/lib/synapse/event_sourcing/storage_listener.rb +34 -0
- data/lib/synapse/event_sourcing/stream_decorator.rb +25 -0
- data/lib/synapse/event_store/errors.rb +16 -0
- data/lib/synapse/event_store/event_store.rb +43 -0
- data/lib/synapse/event_store/in_memory.rb +59 -0
- data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
- data/lib/synapse/event_store/mongo/event_store.rb +86 -0
- data/lib/synapse/event_store/mongo/per_commit_strategy.rb +253 -0
- data/lib/synapse/event_store/mongo/per_event_strategy.rb +143 -0
- data/lib/synapse/event_store/mongo/storage_strategy.rb +113 -0
- data/lib/synapse/event_store/mongo/template.rb +73 -0
- data/lib/synapse/identifier.rb +23 -0
- data/lib/synapse/message.rb +101 -0
- data/lib/synapse/message_builder.rb +38 -0
- data/lib/synapse/process_manager/correlation.rb +32 -0
- data/lib/synapse/process_manager/correlation_resolver.rb +14 -0
- data/lib/synapse/process_manager/correlation_set.rb +58 -0
- data/lib/synapse/process_manager/process.rb +71 -0
- data/lib/synapse/repository/errors.rb +26 -0
- data/lib/synapse/repository/lock_manager.rb +40 -0
- data/lib/synapse/repository/locking.rb +97 -0
- data/lib/synapse/repository/pessimistic_lock_manager.rb +61 -0
- data/lib/synapse/repository/repository.rb +109 -0
- data/lib/synapse/serialization/converter.rb +39 -0
- data/lib/synapse/serialization/converter/chain.rb +45 -0
- data/lib/synapse/serialization/converter/factory.rb +68 -0
- data/lib/synapse/serialization/converter/identity.rb +29 -0
- data/lib/synapse/serialization/converter/json.rb +31 -0
- data/lib/synapse/serialization/converter/ox.rb +31 -0
- data/lib/synapse/serialization/errors.rb +12 -0
- data/lib/synapse/serialization/lazy_object.rb +61 -0
- data/lib/synapse/serialization/message/data.rb +25 -0
- data/lib/synapse/serialization/message/metadata.rb +13 -0
- data/lib/synapse/serialization/message/serialization_aware.rb +17 -0
- data/lib/synapse/serialization/message/serialization_aware_message.rb +66 -0
- data/lib/synapse/serialization/message/serialized_message.rb +201 -0
- data/lib/synapse/serialization/message/serialized_message_builder.rb +64 -0
- data/lib/synapse/serialization/message/serialized_object_cache.rb +50 -0
- data/lib/synapse/serialization/message/serializer.rb +47 -0
- data/lib/synapse/serialization/revision_resolver.rb +30 -0
- data/lib/synapse/serialization/serialized_object.rb +37 -0
- data/lib/synapse/serialization/serialized_type.rb +31 -0
- data/lib/synapse/serialization/serializer.rb +98 -0
- data/lib/synapse/serialization/serializer/marshal.rb +32 -0
- data/lib/synapse/serialization/serializer/oj.rb +34 -0
- data/lib/synapse/serialization/serializer/ox.rb +31 -0
- data/lib/synapse/uow/factory.rb +28 -0
- data/lib/synapse/uow/listener.rb +79 -0
- data/lib/synapse/uow/listener_collection.rb +93 -0
- data/lib/synapse/uow/nesting.rb +262 -0
- data/lib/synapse/uow/provider.rb +71 -0
- data/lib/synapse/uow/storage_listener.rb +14 -0
- data/lib/synapse/uow/transaction_manager.rb +27 -0
- data/lib/synapse/uow/uow.rb +178 -0
- data/lib/synapse/upcasting/chain.rb +78 -0
- data/lib/synapse/upcasting/context.rb +58 -0
- data/lib/synapse/upcasting/data.rb +30 -0
- data/lib/synapse/upcasting/single_upcaster.rb +57 -0
- data/lib/synapse/upcasting/upcaster.rb +55 -0
- data/lib/synapse/version.rb +3 -0
- data/lib/synapse/wiring/message_wiring.rb +41 -0
- data/lib/synapse/wiring/wire.rb +55 -0
- data/lib/synapse/wiring/wire_registry.rb +61 -0
- data/test/command/duplication_test.rb +54 -0
- data/test/command/gateway_test.rb +25 -0
- data/test/command/interceptor_chain_test.rb +26 -0
- data/test/command/serialization_test.rb +37 -0
- data/test/command/simple_command_bus_test.rb +141 -0
- data/test/command/validation_test.rb +42 -0
- data/test/command/wiring_test.rb +73 -0
- data/test/domain/aggregate_root_test.rb +57 -0
- data/test/domain/fixtures.rb +31 -0
- data/test/domain/message_test.rb +61 -0
- data/test/domain/stream_test.rb +35 -0
- data/test/duplication_test.rb +40 -0
- data/test/event_bus/wiring_test.rb +46 -0
- data/test/event_sourcing/aggregate_factory_test.rb +28 -0
- data/test/event_sourcing/aggregate_root_test.rb +76 -0
- data/test/event_sourcing/entity_test.rb +34 -0
- data/test/event_sourcing/fixtures.rb +85 -0
- data/test/event_sourcing/repository_test.rb +102 -0
- data/test/event_sourcing/snapshot/aggregate_taker_test.rb +39 -0
- data/test/event_sourcing/snapshot/deferred_taker_test.rb +19 -0
- data/test/event_sourcing/snapshot/integration_test.rb +65 -0
- data/test/event_sourcing/storage_listener_test.rb +77 -0
- data/test/event_store/in_memory_test.rb +47 -0
- data/test/process_manager/correlation_set_test.rb +49 -0
- data/test/process_manager/correlation_test.rb +24 -0
- data/test/process_manager/process_test.rb +52 -0
- data/test/repository/locking_test.rb +101 -0
- data/test/serialization/converter/factory_test.rb +33 -0
- data/test/serialization/converter/identity_test.rb +17 -0
- data/test/serialization/converter/json_test.rb +31 -0
- data/test/serialization/converter/ox_test.rb +40 -0
- data/test/serialization/fixtures.rb +17 -0
- data/test/serialization/lazy_object_test.rb +32 -0
- data/test/serialization/message/metadata_test.rb +19 -0
- data/test/serialization/message/serialization_aware_message_test.rb +88 -0
- data/test/serialization/message/serialized_message_builder_test.rb +41 -0
- data/test/serialization/message/serialized_message_test.rb +140 -0
- data/test/serialization/message/serializer_test.rb +50 -0
- data/test/serialization/revision_resolver_test.rb +12 -0
- data/test/serialization/serialized_object_test.rb +36 -0
- data/test/serialization/serialized_type_test.rb +27 -0
- data/test/serialization/serializer/marshal_test.rb +22 -0
- data/test/serialization/serializer/oj_test.rb +24 -0
- data/test/serialization/serializer/ox_test.rb +36 -0
- data/test/serialization/serializer_test.rb +20 -0
- data/test/test_helper.rb +19 -0
- data/test/uow/factory_test.rb +23 -0
- data/test/uow/outer_commit_listener_test.rb +50 -0
- data/test/uow/provider_test.rb +70 -0
- data/test/uow/uow_test.rb +337 -0
- data/test/upcasting/chain_test.rb +29 -0
- data/test/upcasting/fixtures.rb +66 -0
- data/test/wiring/wire_registry_test.rb +60 -0
- data/test/wiring/wire_test.rb +51 -0
- metadata +263 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module UnitOfWork
|
|
3
|
+
# Partial implementation of a unit of work that can be nested
|
|
4
|
+
#
|
|
5
|
+
# This implementation provides common actions that will be needed for nestable units
|
|
6
|
+
# of work, such as registration with the unit of work provider and nested commit
|
|
7
|
+
# and rollback operations
|
|
8
|
+
#
|
|
9
|
+
# @abstract
|
|
10
|
+
class NestableUnitOfWork
|
|
11
|
+
# @param [UnitOfWorkProvider] provider
|
|
12
|
+
# @return [undefined]
|
|
13
|
+
def initialize(provider)
|
|
14
|
+
@inner_units = Array.new
|
|
15
|
+
@provider = provider
|
|
16
|
+
@started = false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Commits this unit of work
|
|
20
|
+
#
|
|
21
|
+
# All reigstered aggregates that have not been registered as stored are saved in their
|
|
22
|
+
# respective repositories, buffered events are sent to their respective event buses, and
|
|
23
|
+
# all registered listeners are notified of the commit.
|
|
24
|
+
#
|
|
25
|
+
# After the commit (successful or not), the unit of work is unregistered and cleans up any
|
|
26
|
+
# resources it acquired. The effectively means that a rollback is done if the unit of work
|
|
27
|
+
# failed to commit.
|
|
28
|
+
#
|
|
29
|
+
# @raise [RuntimeError] If unit of work hasn't been started yet
|
|
30
|
+
# @return [undefined]
|
|
31
|
+
def commit
|
|
32
|
+
unless started?
|
|
33
|
+
raise 'Unit of work has not been started yet'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
notify_prepare_commit
|
|
38
|
+
store_aggregates
|
|
39
|
+
|
|
40
|
+
unless @outer_unit
|
|
41
|
+
perform_commit
|
|
42
|
+
stop
|
|
43
|
+
perform_cleanup
|
|
44
|
+
end
|
|
45
|
+
rescue => cause
|
|
46
|
+
perform_rollback cause
|
|
47
|
+
stop
|
|
48
|
+
|
|
49
|
+
unless @outer_unit
|
|
50
|
+
perform_cleanup
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
raise cause
|
|
54
|
+
ensure
|
|
55
|
+
clear
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Clears this unit of work of any buffered changes
|
|
60
|
+
#
|
|
61
|
+
# Any buffered events and registered aggregates are discarded and any registered unit of work
|
|
62
|
+
# listeners are notified of the rollback.
|
|
63
|
+
#
|
|
64
|
+
# @param [Error] cause
|
|
65
|
+
# @return [undefined]
|
|
66
|
+
def rollback(cause = nil)
|
|
67
|
+
begin
|
|
68
|
+
if started?
|
|
69
|
+
@inner_units.each do |inner_unit|
|
|
70
|
+
@provider.push inner_unit
|
|
71
|
+
inner_unit.rollback cause
|
|
72
|
+
end
|
|
73
|
+
perform_rollback cause
|
|
74
|
+
end
|
|
75
|
+
ensure
|
|
76
|
+
finalize_rollback
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Starts the unit of work, preparing it for aggregate registration
|
|
81
|
+
#
|
|
82
|
+
# @raise [RuntimeError] If unit of work has already been started
|
|
83
|
+
# @return [undefined]
|
|
84
|
+
def start
|
|
85
|
+
if started?
|
|
86
|
+
raise 'Unit of work has already been started'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
perform_start
|
|
90
|
+
|
|
91
|
+
if @provider.started?
|
|
92
|
+
# This is a nested unit of work
|
|
93
|
+
@outer_unit = @provider.current
|
|
94
|
+
|
|
95
|
+
if NestableUnitOfWork === @outer_unit
|
|
96
|
+
@outer_unit.register_inner_unit self
|
|
97
|
+
else
|
|
98
|
+
# Outer unit is not aware of inner units, hook in with a listener
|
|
99
|
+
@outer_unit.register_listener OuterCommitUnitOfWorkListener.new self, @provider
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
@provider.push self
|
|
104
|
+
@started = true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns true if this unit of work has been started
|
|
108
|
+
# @return [Boolean]
|
|
109
|
+
def started?
|
|
110
|
+
@started
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
protected
|
|
114
|
+
|
|
115
|
+
# Executes logic required to commit this unit of work
|
|
116
|
+
#
|
|
117
|
+
# @abstract
|
|
118
|
+
# @return [undefined]
|
|
119
|
+
def perform_commit; end
|
|
120
|
+
|
|
121
|
+
# Executes logic required to rollback this unit of work
|
|
122
|
+
#
|
|
123
|
+
# @abstract
|
|
124
|
+
# @param [Error] cause
|
|
125
|
+
# @return [undefined]
|
|
126
|
+
def perform_rollback(cause = nil); end
|
|
127
|
+
|
|
128
|
+
# Notifies listeners that this unit of work is cleaning up
|
|
129
|
+
#
|
|
130
|
+
# @abstract
|
|
131
|
+
# @return [undefined]
|
|
132
|
+
def notify_cleanup; end
|
|
133
|
+
|
|
134
|
+
# Notifies listeners that this unit of work is preparing to be committed
|
|
135
|
+
#
|
|
136
|
+
# @abstract
|
|
137
|
+
# @return [undefined]
|
|
138
|
+
def notify_prepare_commit; end
|
|
139
|
+
|
|
140
|
+
# Executes logic required when starting this unit of work
|
|
141
|
+
#
|
|
142
|
+
# @abstract
|
|
143
|
+
# @return [undefined]
|
|
144
|
+
def perform_start; end
|
|
145
|
+
|
|
146
|
+
# Storages aggregates registered with this unit of work
|
|
147
|
+
#
|
|
148
|
+
# @abstract
|
|
149
|
+
# @return [undefined]
|
|
150
|
+
def store_aggregates; end
|
|
151
|
+
|
|
152
|
+
# Commits all registered inner units of work. This should be invoked after events have been
|
|
153
|
+
# dispatched and before any listeners are notified of the commit.
|
|
154
|
+
#
|
|
155
|
+
# @return [undefined]
|
|
156
|
+
def commit_inner_units
|
|
157
|
+
@inner_units.each do |inner_unit|
|
|
158
|
+
if inner_unit.started?
|
|
159
|
+
inner_unit.perform_inner_commit
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Registers a unit of work nested in this unit of work
|
|
165
|
+
#
|
|
166
|
+
# @private
|
|
167
|
+
# @param [UnitOfWork] inner_unit
|
|
168
|
+
# @return [undefined]
|
|
169
|
+
def register_inner_unit(inner_unit)
|
|
170
|
+
@inner_units.push inner_unit
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Executes logic required to clean up this unit of work
|
|
174
|
+
#
|
|
175
|
+
# @private
|
|
176
|
+
# @return [undefined]
|
|
177
|
+
def perform_cleanup
|
|
178
|
+
@inner_units.each do |inner_unit|
|
|
179
|
+
inner_unit.perform_cleanup
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
notify_cleanup
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Commits this unit of work as an inner unit of work
|
|
186
|
+
#
|
|
187
|
+
# @private
|
|
188
|
+
# @return [undefined]
|
|
189
|
+
def perform_inner_commit
|
|
190
|
+
@provider.push self
|
|
191
|
+
|
|
192
|
+
begin
|
|
193
|
+
perform_commit
|
|
194
|
+
rescue => cause
|
|
195
|
+
perform_rollback cause
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
clear
|
|
199
|
+
stop
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private
|
|
203
|
+
|
|
204
|
+
# @return [undefined]
|
|
205
|
+
def finalize_rollback
|
|
206
|
+
unless @outer_unit
|
|
207
|
+
perform_cleanup
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
clear
|
|
211
|
+
stop
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @return [undefined]
|
|
215
|
+
def clear
|
|
216
|
+
@provider.clear self
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# @return [undefined]
|
|
220
|
+
def stop
|
|
221
|
+
@started = false
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Listener that allows a nested unit of work to properly operate within in a unit of
|
|
226
|
+
# work that is not aware of nesting
|
|
227
|
+
class OuterCommitUnitOfWorkListener < UnitOfWorkListener
|
|
228
|
+
# @param [UnitOfWork] inner_unit
|
|
229
|
+
# @param [UnitOfWorkProvider] provider
|
|
230
|
+
# @return [undefined]
|
|
231
|
+
def initialize(inner_unit, provider)
|
|
232
|
+
@inner_unit = inner_unit
|
|
233
|
+
@provider = provider
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# @param [UnitOfWork] outer_unit
|
|
237
|
+
# @return [undefined]
|
|
238
|
+
def after_commit(outer_unit)
|
|
239
|
+
@inner_unit.perform_inner_commit
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# @param [UnitOfWork] outer_unit
|
|
243
|
+
# @param [Error] cause
|
|
244
|
+
# @return [undefined]
|
|
245
|
+
def on_rollback(outer_unit, cause = nil)
|
|
246
|
+
@provider.push @inner_unit
|
|
247
|
+
|
|
248
|
+
begin
|
|
249
|
+
@inner_unit.perform_rollback cause
|
|
250
|
+
ensure
|
|
251
|
+
@provider.clear @inner_unit
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# @param [UnitOfWork] outer_unit
|
|
256
|
+
# @return [undefined]
|
|
257
|
+
def on_cleanup(outer_unit)
|
|
258
|
+
@inner_unit.perform_cleanup
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module UnitOfWork
|
|
3
|
+
# Entry point for components to access units of work. Components managing transactional
|
|
4
|
+
# boundaries can register and clear unit of work instances.
|
|
5
|
+
class UnitOfWorkProvider
|
|
6
|
+
def initialize
|
|
7
|
+
@threads = Hash.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Clears the given unit of work from this provider
|
|
11
|
+
#
|
|
12
|
+
# If the given unit of work is not known to the provider, or it is not the active unit
|
|
13
|
+
# of work, then this method will raise an exception.
|
|
14
|
+
#
|
|
15
|
+
# @param [UnitOfWork] unit
|
|
16
|
+
# @return [undefined]
|
|
17
|
+
def clear(unit)
|
|
18
|
+
unless stack.last == unit
|
|
19
|
+
raise ArgumentError, 'The given unit of work is not the active unit of work'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
stack.pop
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Commits the current unit of work
|
|
26
|
+
# @return [undefined]
|
|
27
|
+
def commit
|
|
28
|
+
current.commit
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the current unit of work if one is set
|
|
32
|
+
#
|
|
33
|
+
# @raise [RuntimeError] If no unit of work is active
|
|
34
|
+
# @return [UnitOfWork]
|
|
35
|
+
def current
|
|
36
|
+
if stack.empty?
|
|
37
|
+
raise 'No unit of work is active'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
stack.last
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Pushes the given unit of work onto the top of the stack, making it the active unit of work
|
|
45
|
+
#
|
|
46
|
+
# If there are other units of work bound to this provider, they will be held until the given
|
|
47
|
+
# unit of work is cleared.
|
|
48
|
+
#
|
|
49
|
+
# @param [UnitOfWork] unit
|
|
50
|
+
# @return [undefined]
|
|
51
|
+
def push(unit)
|
|
52
|
+
stack.push unit
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns true if there is an active unit of work
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def started?
|
|
58
|
+
!stack.empty?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# @return [Array<UnitOfWork>]
|
|
64
|
+
def stack
|
|
65
|
+
@threads.fetch Thread.current
|
|
66
|
+
rescue KeyError
|
|
67
|
+
@threads.store Thread.current, Array.new
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module UnitOfWork
|
|
3
|
+
# Represents a mechanism for a unit of work to commit an aggregate to an underlying store
|
|
4
|
+
# @abstract
|
|
5
|
+
class StorageListener
|
|
6
|
+
# Commits the given aggregate to the underlying storage mechanism
|
|
7
|
+
#
|
|
8
|
+
# @abstract
|
|
9
|
+
# @param [AggregateRoot] aggregate
|
|
10
|
+
# @return [undefined]
|
|
11
|
+
def store(aggregate); end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module UnitOfWork
|
|
3
|
+
# Represents a mechanism for a unit of work to integrate with an underlying transaction
|
|
4
|
+
# management system
|
|
5
|
+
#
|
|
6
|
+
# @abstract
|
|
7
|
+
class TransactionManager
|
|
8
|
+
# Creates and returns a transaction for use by the unit of work
|
|
9
|
+
#
|
|
10
|
+
# @abstract
|
|
11
|
+
# @return [Object]
|
|
12
|
+
def start; end
|
|
13
|
+
|
|
14
|
+
# Commits the given transaction
|
|
15
|
+
#
|
|
16
|
+
# @param [Object] transaction
|
|
17
|
+
# @return [undefined]
|
|
18
|
+
def commit(transaction); end
|
|
19
|
+
|
|
20
|
+
# Rolls back the given transaction
|
|
21
|
+
#
|
|
22
|
+
# @param [Object] transaction
|
|
23
|
+
# @return [undefined]
|
|
24
|
+
def rollback(transaction); end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module UnitOfWork
|
|
3
|
+
# Default implementation of a unit of work
|
|
4
|
+
class UnitOfWork < NestableUnitOfWork
|
|
5
|
+
# @param [UnitOfWorkProvider] provider
|
|
6
|
+
# @return [undefined]
|
|
7
|
+
def initialize(provider)
|
|
8
|
+
super
|
|
9
|
+
|
|
10
|
+
@aggregates = Hash.new
|
|
11
|
+
@events = Hash.new
|
|
12
|
+
@listeners = UnitOfWorkListenerCollection.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns true if this unit of work is bound to a transaction
|
|
16
|
+
# @return [Boolean]
|
|
17
|
+
def transactional?
|
|
18
|
+
!!@transaction_manager
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Registers a listener that is notified of state changes in this unit of work
|
|
22
|
+
#
|
|
23
|
+
# @param [UnitOfWorkListener] listener
|
|
24
|
+
# @return [undefined]
|
|
25
|
+
def register_listener(listener)
|
|
26
|
+
@listeners.push listener
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Registers an aggregate with this unit of work
|
|
30
|
+
#
|
|
31
|
+
# This unit of work adds an event listener to the aggregate so that any events generated
|
|
32
|
+
# are published to the given event bus when this unit of work is committed.
|
|
33
|
+
#
|
|
34
|
+
# The provided storage listener is used to commit the aggregate to its respective
|
|
35
|
+
# underlying storage mechanism.
|
|
36
|
+
#
|
|
37
|
+
# If there is already an aggregate registered with this unit of work of the same type
|
|
38
|
+
# and with the same identifier, that aggregate will be returned instead of the given
|
|
39
|
+
# aggregate.
|
|
40
|
+
#
|
|
41
|
+
# @param [AggregateRoot] aggregate
|
|
42
|
+
# @param [EventBus] event_bus
|
|
43
|
+
# @param [StorageListener] storage_listener
|
|
44
|
+
# @return [AggregateRoot]
|
|
45
|
+
def register_aggregate(aggregate, event_bus, storage_listener)
|
|
46
|
+
similar = find_similar_aggregate aggregate
|
|
47
|
+
if similar
|
|
48
|
+
return similar
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
aggregate.add_registration_listener do |event|
|
|
52
|
+
publish_event event, event_bus
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@aggregates.store aggregate, storage_listener
|
|
56
|
+
|
|
57
|
+
aggregate
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Buffers an event for publication to the given event bus until this unit of work is
|
|
61
|
+
# committed
|
|
62
|
+
#
|
|
63
|
+
# @param [EventMessage] event
|
|
64
|
+
# @param [EventBus] event_bus
|
|
65
|
+
# @return [EventMessage] The event that will be published to the event bus
|
|
66
|
+
def publish_event(event, event_bus)
|
|
67
|
+
event = @listeners.on_event_registered self, event
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
events = @events.fetch event_bus
|
|
71
|
+
rescue KeyError
|
|
72
|
+
events = @events.store event_bus, Array.new
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
events.push event
|
|
76
|
+
event
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Sets the transaction manager that will be used by this unit of work
|
|
80
|
+
#
|
|
81
|
+
# @raise [RuntimeError] If unit of work has been started
|
|
82
|
+
# @param [TransactionManager] transaction_manager
|
|
83
|
+
# @return [undefined]
|
|
84
|
+
def transaction_manager=(transaction_manager)
|
|
85
|
+
if started?
|
|
86
|
+
raise 'Transaction manager not permitted to change after unit of work has started'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@transaction_manager = transaction_manager
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
protected
|
|
93
|
+
|
|
94
|
+
# @return [undefined]
|
|
95
|
+
def perform_commit
|
|
96
|
+
publish_events
|
|
97
|
+
commit_inner_units
|
|
98
|
+
|
|
99
|
+
if transactional?
|
|
100
|
+
@listeners.on_prepare_transaction_commit self, @transaction
|
|
101
|
+
@transaction_manager.commit @transaction
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
@listeners.after_commit self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @param [Error] cause
|
|
108
|
+
# @return [undefined]
|
|
109
|
+
def perform_rollback(cause = nil)
|
|
110
|
+
@aggregates.clear
|
|
111
|
+
@events.clear
|
|
112
|
+
|
|
113
|
+
begin
|
|
114
|
+
if @transaction
|
|
115
|
+
@transaction_manager.rollback @transaction
|
|
116
|
+
end
|
|
117
|
+
ensure
|
|
118
|
+
@listeners.on_rollback self, cause
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @return [undefined]
|
|
123
|
+
def notify_cleanup
|
|
124
|
+
@listeners.on_cleanup self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [undefined]
|
|
128
|
+
def notify_prepare_commit
|
|
129
|
+
@listeners.on_prepare_commit self, @aggregates.keys, @events
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @return [undefined]
|
|
133
|
+
def perform_start
|
|
134
|
+
if transactional?
|
|
135
|
+
@transaction = @transaction_manager.start
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@listeners.on_start self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @return [undefined]
|
|
142
|
+
def store_aggregates
|
|
143
|
+
@aggregates.each_pair do |aggregate, storage_listener|
|
|
144
|
+
storage_listener.store aggregate
|
|
145
|
+
end
|
|
146
|
+
@aggregates.clear
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
# Checks if an aggregate of the same type and identifier as the given aggregate has been
|
|
152
|
+
# previously registered with this unit work. If one is found, it is returned.
|
|
153
|
+
#
|
|
154
|
+
# @param [AggregateRoot] aggregate
|
|
155
|
+
# @return [AggregateRoot] Returns nil if no similar aggregate was found
|
|
156
|
+
def find_similar_aggregate(aggregate)
|
|
157
|
+
@aggregates.each_key do |candidate|
|
|
158
|
+
if aggregate.class === candidate and aggregate.id == candidate.id
|
|
159
|
+
return candidate
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
return
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Continually publishes all buffered events to their respective event buses until all
|
|
167
|
+
# events have been published
|
|
168
|
+
def publish_events
|
|
169
|
+
until @events.empty?
|
|
170
|
+
@events.keys.each do |event_bus|
|
|
171
|
+
events = @events.delete event_bus
|
|
172
|
+
event_bus.publish events
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|