synapse-core 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/synapse/command.rb +2 -0
- data/lib/synapse/command/callbacks/future.rb +50 -0
- data/lib/synapse/command/command_callback.rb +0 -47
- data/lib/synapse/command/message.rb +1 -1
- data/lib/synapse/common.rb +1 -0
- data/lib/synapse/common/concurrency/executor.rb +13 -0
- data/lib/synapse/common/message.rb +9 -2
- data/lib/synapse/common/message_builder.rb +5 -1
- data/lib/synapse/configuration.rb +1 -0
- data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +2 -8
- data/lib/synapse/configuration/component/event_sourcing.rb +3 -3
- data/lib/synapse/configuration/component/event_sourcing/repository.rb +19 -2
- data/lib/synapse/configuration/component/event_sourcing/{aggregate_snapshot_taker.rb → snapshot/aggregate_taker.rb} +4 -4
- data/lib/synapse/configuration/component/event_sourcing/{interval_snapshot_policy.rb → snapshot/interval_policy.rb} +0 -0
- data/lib/synapse/configuration/component/mixin/thread_pool.rb +26 -0
- data/lib/synapse/domain/message.rb +0 -24
- data/lib/synapse/domain/message_builder.rb +0 -9
- data/lib/synapse/event_sourcing.rb +1 -1
- data/lib/synapse/event_sourcing/aggregate_root.rb +2 -1
- data/lib/synapse/event_sourcing/caching.rb +66 -0
- data/lib/synapse/event_sourcing/repository.rb +22 -15
- data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +8 -9
- data/lib/synapse/event_sourcing/snapshot/policy.rb +1 -0
- data/lib/synapse/event_sourcing/snapshot/taker.rb +31 -2
- data/lib/synapse/repository/repository.rb +18 -4
- data/lib/synapse/repository/simple_repository.rb +8 -17
- data/lib/synapse/serialization/converter.rb +2 -2
- data/lib/synapse/serialization/converter/identity.rb +2 -2
- data/lib/synapse/serialization/converter/json.rb +3 -3
- data/lib/synapse/serialization/converter/ox.rb +3 -3
- data/lib/synapse/serialization/message/metadata.rb +2 -2
- data/lib/synapse/serialization/message/serialization_aware.rb +2 -2
- data/lib/synapse/serialization/message/serialized_message.rb +7 -24
- data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
- data/lib/synapse/uow.rb +0 -1
- data/lib/synapse/uow/nesting.rb +2 -2
- data/lib/synapse/uow/uow.rb +12 -13
- data/lib/synapse/version.rb +1 -1
- data/test/configuration/component/event_sourcing/repository_test.rb +24 -1
- data/test/event_sourcing/caching_test.rb +118 -0
- data/test/event_sourcing/repository_test.rb +2 -1
- data/test/event_sourcing/snapshot/aggregate_taker_test.rb +46 -16
- data/test/repository/locking_test.rb +1 -10
- data/test/serialization/serializer/attribute_test.rb +51 -0
- data/test/uow/uow_test.rb +9 -12
- metadata +12 -9
- data/lib/synapse/event_sourcing/storage_listener.rb +0 -34
- data/lib/synapse/uow/storage_listener.rb +0 -14
- data/test/event_sourcing/storage_listener_test.rb +0 -81
@@ -31,16 +31,6 @@ module Synapse
|
|
31
31
|
@aggregate_factory = aggregate_factory
|
32
32
|
@event_store = event_store
|
33
33
|
@stream_decorators = Array.new
|
34
|
-
@storage_listener =
|
35
|
-
EventSourcedStorageListener.new @event_store, @lock_manager, @stream_decorators, type_identifier
|
36
|
-
end
|
37
|
-
|
38
|
-
# Appends a stream decorator onto the end of the list of stream decorators
|
39
|
-
#
|
40
|
-
# @param [EventStreamDecorator] stream_decorator
|
41
|
-
# @return [undefined]
|
42
|
-
def add_stream_decorator(stream_decorator)
|
43
|
-
@stream_decorators.push stream_decorator
|
44
34
|
end
|
45
35
|
|
46
36
|
protected
|
@@ -93,16 +83,33 @@ module Synapse
|
|
93
83
|
end
|
94
84
|
end
|
95
85
|
|
86
|
+
# @param [AggregateRoot] aggregate
|
87
|
+
# @return [undefined]
|
88
|
+
def delete_aggregate(aggregate)
|
89
|
+
save_aggregate aggregate
|
90
|
+
end
|
91
|
+
|
92
|
+
# @param [AggregateRoot] aggregate
|
93
|
+
# @return [undefined]
|
94
|
+
def save_aggregate(aggregate)
|
95
|
+
if aggregate.version and !@lock_manager.validate_lock aggregate
|
96
|
+
raise Repository::ConflictingModificationError
|
97
|
+
end
|
98
|
+
|
99
|
+
stream = aggregate.uncommitted_events
|
100
|
+
@stream_decorators.reverse_each do |decorator|
|
101
|
+
stream = decorator.decorate_for_append type_identifier, aggregate, stream
|
102
|
+
end
|
103
|
+
|
104
|
+
@event_store.append_events type_identifier, stream
|
105
|
+
aggregate.mark_committed
|
106
|
+
end
|
107
|
+
|
96
108
|
# @return [Class]
|
97
109
|
def aggregate_type
|
98
110
|
@aggregate_factory.aggregate_type
|
99
111
|
end
|
100
112
|
|
101
|
-
# @return [StorageListener]
|
102
|
-
def storage_listener
|
103
|
-
@storage_listener
|
104
|
-
end
|
105
|
-
|
106
113
|
# @return [String]
|
107
114
|
def type_identifier
|
108
115
|
@aggregate_factory.type_identifier
|
@@ -2,11 +2,10 @@ module Synapse
|
|
2
2
|
module EventSourcing
|
3
3
|
# Snapshot taker that uses the actual aggregate and its state to create a snapshot event
|
4
4
|
class AggregateSnapshotTaker < SnapshotTaker
|
5
|
-
# @param [SnapshotEventStore] event_store
|
6
5
|
# @return [undefined]
|
7
|
-
def initialize
|
6
|
+
def initialize
|
7
|
+
super
|
8
8
|
@aggregate_factories = Hash.new
|
9
|
-
@event_store = event_store
|
10
9
|
end
|
11
10
|
|
12
11
|
# @param [AggregateFactory] factory
|
@@ -15,23 +14,23 @@ module Synapse
|
|
15
14
|
@aggregate_factories.store factory.type_identifier, factory
|
16
15
|
end
|
17
16
|
|
17
|
+
protected
|
18
|
+
|
18
19
|
# @param [String] type_identifier
|
19
20
|
# @param [Object] aggregate_id
|
20
|
-
# @
|
21
|
-
|
22
|
-
|
21
|
+
# @param [DomainEventStream] stream
|
22
|
+
# @return [DomainEventMessage]
|
23
|
+
def create_snapshot(type_identifier, aggregate_id, stream)
|
23
24
|
factory = @aggregate_factories.fetch type_identifier
|
24
25
|
|
25
26
|
aggregate = factory.create_aggregate aggregate_id, stream.peek
|
26
27
|
aggregate.initialize_from_stream stream
|
27
28
|
|
28
|
-
|
29
|
+
Domain::DomainEventMessage.build do |builder|
|
29
30
|
builder.payload = aggregate
|
30
31
|
builder.aggregate_id = aggregate.id
|
31
32
|
builder.sequence_number = aggregate.version
|
32
33
|
end
|
33
|
-
|
34
|
-
@event_store.append_snapshot_event type_identifier, snapshot
|
35
34
|
end
|
36
35
|
end # AggregateSnapshotTaker
|
37
36
|
end # EventSourcing
|
@@ -12,6 +12,7 @@ module Synapse
|
|
12
12
|
# Snapshot policy that takes a snapshot if the number of events committed in an aggregate since
|
13
13
|
# the last snapshot goes over the configured threshold
|
14
14
|
class IntervalSnapshotPolicy < SnapshotPolicy
|
15
|
+
# @param [Integer] threshold
|
15
16
|
# @return [undefined]
|
16
17
|
def initialize(threshold)
|
17
18
|
@threshold = threshold
|
@@ -7,14 +7,43 @@ module Synapse
|
|
7
7
|
#
|
8
8
|
# @abstract
|
9
9
|
class SnapshotTaker
|
10
|
+
# @return [SnapshotEventStore]
|
11
|
+
attr_accessor :event_store
|
12
|
+
|
13
|
+
# @return [Executor]
|
14
|
+
attr_accessor :executor
|
15
|
+
|
16
|
+
# @return [undefined]
|
17
|
+
def initialize
|
18
|
+
@executor = DirectExecutor.new
|
19
|
+
end
|
20
|
+
|
10
21
|
# Schedules a snapshot to be taken for an aggregate of the given type and with the given
|
11
22
|
# identifier
|
12
23
|
#
|
13
|
-
# @abstract
|
14
24
|
# @param [String] type_identifier
|
15
25
|
# @param [Object] aggregate_id
|
16
26
|
# @return [undefined]
|
17
|
-
def schedule_snapshot(type_identifier, aggregate_id)
|
27
|
+
def schedule_snapshot(type_identifier, aggregate_id)
|
28
|
+
@executor.execute do
|
29
|
+
stream = @event_store.read_events type_identifier, aggregate_id
|
30
|
+
first_sequence_number = stream.peek.sequence_number
|
31
|
+
snapshot = create_snapshot type_identifier, aggregate_id, stream
|
32
|
+
|
33
|
+
if snapshot and snapshot.sequence_number > first_sequence_number
|
34
|
+
@event_store.append_snapshot_event type_identifier, snapshot
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# @abstract
|
42
|
+
# @param [String] type_identifier
|
43
|
+
# @param [Object] aggregate_id
|
44
|
+
# @param [DomainEventStream] stream
|
45
|
+
# @return [DomainEventMessage]
|
46
|
+
def create_snapshot(type_identifier, aggregate_id, stream); end
|
18
47
|
end # SnapshotTaker
|
19
48
|
end # EventSourcing
|
20
49
|
end
|
@@ -48,11 +48,19 @@ module Synapse
|
|
48
48
|
# @return [Class]
|
49
49
|
def aggregate_type; end
|
50
50
|
|
51
|
-
#
|
51
|
+
# Deletes the given aggregate from the underlying storage mechanism
|
52
52
|
#
|
53
53
|
# @abstract
|
54
|
-
# @
|
55
|
-
|
54
|
+
# @param [AggregateRoot] aggregate
|
55
|
+
# @return [undefined]
|
56
|
+
def delete_aggregate(aggregate); end
|
57
|
+
|
58
|
+
# Saves the given aggregate using the underlying storage mechanism
|
59
|
+
#
|
60
|
+
# @abstract
|
61
|
+
# @param [AggregateRoot] aggregate
|
62
|
+
# @return [undefined]
|
63
|
+
def save_aggregate(aggregate); end
|
56
64
|
|
57
65
|
# Asserts that an aggregate being added is compatible with this repository and is newly
|
58
66
|
# created
|
@@ -89,7 +97,13 @@ module Synapse
|
|
89
97
|
# @param [AggregateRoot] aggregate
|
90
98
|
# @return [undefined]
|
91
99
|
def register_aggregate(aggregate)
|
92
|
-
current_unit.register_aggregate aggregate, @event_bus
|
100
|
+
current_unit.register_aggregate aggregate, @event_bus do |aggregate|
|
101
|
+
if aggregate.deleted?
|
102
|
+
delete_aggregate aggregate
|
103
|
+
else
|
104
|
+
save_aggregate aggregate
|
105
|
+
end
|
106
|
+
end
|
93
107
|
end
|
94
108
|
|
95
109
|
# Registers the given unit of work listener with the current unit of work
|
@@ -14,9 +14,7 @@ module Synapse
|
|
14
14
|
# @return [undefined]
|
15
15
|
def initialize(lock_manager, aggregate_type)
|
16
16
|
super lock_manager
|
17
|
-
|
18
17
|
@aggregate_type = aggregate_type
|
19
|
-
@storage_listener = SimpleStorageListener.new
|
20
18
|
end
|
21
19
|
|
22
20
|
protected
|
@@ -29,7 +27,7 @@ module Synapse
|
|
29
27
|
# @param [Integer] expected_version
|
30
28
|
# @return [AggregateRoot]
|
31
29
|
def perform_load(aggregate_id, expected_version)
|
32
|
-
# Most ORMs that I can think of use #find like this -- no need for orm_adapter or anything
|
30
|
+
# Most ORMs that I can think of use #find like this -- no need for orm_adapter or anything
|
33
31
|
# crazy like that
|
34
32
|
aggregate = @aggregate_type.find aggregate_id
|
35
33
|
aggregate.tap do
|
@@ -46,24 +44,17 @@ module Synapse
|
|
46
44
|
@aggregate_type
|
47
45
|
end
|
48
46
|
|
49
|
-
# @
|
50
|
-
|
51
|
-
|
47
|
+
# @param [AggregateRoot] aggregate
|
48
|
+
# @return [undefined]
|
49
|
+
def delete_aggregate(aggregate)
|
50
|
+
aggregate.destroy
|
52
51
|
end
|
53
|
-
end # SimpleRepository
|
54
52
|
|
55
|
-
# Storage listener that simply calls #save on the aggregate, unless it has been marked for
|
56
|
-
# deletion. In that case, then the #destroy method is called instead.
|
57
|
-
class SimpleStorageListener < UnitOfWork::StorageListener
|
58
53
|
# @param [AggregateRoot] aggregate
|
59
54
|
# @return [undefined]
|
60
|
-
def
|
61
|
-
|
62
|
-
aggregate.destroy
|
63
|
-
else
|
64
|
-
aggregate.save
|
65
|
-
end
|
55
|
+
def save_aggregate(aggregate)
|
56
|
+
aggregate.save
|
66
57
|
end
|
67
|
-
end #
|
58
|
+
end # SimpleRepository
|
68
59
|
end # Repository
|
69
60
|
end
|
@@ -13,7 +13,7 @@ module Synapse
|
|
13
13
|
def convert_content(original)
|
14
14
|
JSON.parse original, @options
|
15
15
|
end
|
16
|
-
end
|
16
|
+
end # JsonToObjectConverter
|
17
17
|
|
18
18
|
# Converter that converts a Ruby data structure into a JSON string
|
19
19
|
class ObjectToJsonConverter
|
@@ -26,6 +26,6 @@ module Synapse
|
|
26
26
|
def convert_content(original)
|
27
27
|
JSON.generate original, @options
|
28
28
|
end
|
29
|
-
end
|
30
|
-
end
|
29
|
+
end # ObjectToJsonConverter
|
30
|
+
end # Serialization
|
31
31
|
end
|
@@ -13,7 +13,7 @@ module Synapse
|
|
13
13
|
def convert_content(original)
|
14
14
|
Ox.dump original, @options
|
15
15
|
end
|
16
|
-
end
|
16
|
+
end # OxDocumentToXmlConverter
|
17
17
|
|
18
18
|
# Converter that converts an XML string into an Ox document
|
19
19
|
class XmlToOxDocumentConverter
|
@@ -26,6 +26,6 @@ module Synapse
|
|
26
26
|
def convert_content(original)
|
27
27
|
Ox.load original, @options
|
28
28
|
end
|
29
|
-
end
|
30
|
-
end
|
29
|
+
end # XmlToOxDocumentConverter
|
30
|
+
end # Serialization
|
31
31
|
end
|
@@ -13,14 +13,19 @@ module Synapse
|
|
13
13
|
# @return [LazyObject]
|
14
14
|
attr_reader :serialized_payload
|
15
15
|
|
16
|
+
# @return [Time]
|
17
|
+
attr_reader :timestamp
|
18
|
+
|
16
19
|
# @param [String] id
|
17
20
|
# @param [LazyObject] metadata
|
18
21
|
# @param [LazyObject] payload
|
22
|
+
# @param [Time] timestamp
|
19
23
|
# @return [undefined]
|
20
|
-
def initialize(id, metadata, payload)
|
24
|
+
def initialize(id, metadata, payload, timestamp)
|
21
25
|
@id = id
|
22
26
|
@serialized_metadata = metadata
|
23
27
|
@serialized_payload = payload
|
28
|
+
@timestamp = timestamp
|
24
29
|
end
|
25
30
|
|
26
31
|
# @return [Hash] The deserialized metadata for this message
|
@@ -106,6 +111,7 @@ module Synapse
|
|
106
111
|
builder.id = @id
|
107
112
|
builder.metadata = DeserializedObject.new metadata
|
108
113
|
builder.payload = @serialized_payload
|
114
|
+
builder.timestamp = @timestamp
|
109
115
|
end
|
110
116
|
|
111
117
|
private
|
@@ -126,33 +132,10 @@ module Synapse
|
|
126
132
|
|
127
133
|
# Serialized representation of an event message
|
128
134
|
class SerializedEventMessage < SerializedMessage
|
129
|
-
# @return [Time]
|
130
|
-
attr_reader :timestamp
|
131
|
-
|
132
|
-
# @param [String] id
|
133
|
-
# @param [LazyObject] metadata
|
134
|
-
# @param [LazyObject] payload
|
135
|
-
# @param [Time] timestamp
|
136
|
-
# @return [undefined]
|
137
|
-
def initialize(id, metadata, payload, timestamp)
|
138
|
-
super id, metadata, payload
|
139
|
-
@timestamp = timestamp
|
140
|
-
end
|
141
|
-
|
142
135
|
# @return [Class]
|
143
136
|
def self.builder
|
144
137
|
SerializedEventMessageBuilder
|
145
138
|
end
|
146
|
-
|
147
|
-
protected
|
148
|
-
|
149
|
-
# @param [SerializedEventMessageBuilder] builder
|
150
|
-
# @param [Hash] metadata
|
151
|
-
# @return [undefined]
|
152
|
-
def build_duplicate(builder, metadata)
|
153
|
-
super
|
154
|
-
builder.timestamp = @timestamp
|
155
|
-
end
|
156
139
|
end # SerializedEventMessage
|
157
140
|
|
158
141
|
# Serialized representation of a domain event message
|
@@ -11,6 +11,9 @@ module Synapse
|
|
11
11
|
# @return [LazyObject]
|
12
12
|
attr_accessor :payload
|
13
13
|
|
14
|
+
# @return [Time]
|
15
|
+
attr_accessor :timestamp
|
16
|
+
|
14
17
|
def self.build
|
15
18
|
builder = self.new
|
16
19
|
|
@@ -20,15 +23,12 @@ module Synapse
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def build
|
23
|
-
SerializedMessage.new @id, @metadata, @payload
|
26
|
+
SerializedMessage.new @id, @metadata, @payload, @timestamp
|
24
27
|
end
|
25
28
|
end # SerializedMessageBuilder
|
26
29
|
|
27
30
|
# Message builder capable of producing SerializedEventMessage instances
|
28
31
|
class SerializedEventMessageBuilder < SerializedMessageBuilder
|
29
|
-
# @return [Time]
|
30
|
-
attr_accessor :timestamp
|
31
|
-
|
32
32
|
# @return [SerializedEventMessage]
|
33
33
|
def build
|
34
34
|
SerializedEventMessage.new @id, @metadata, @payload, @timestamp
|
data/lib/synapse/uow.rb
CHANGED
data/lib/synapse/uow/nesting.rb
CHANGED
@@ -225,7 +225,7 @@ module Synapse
|
|
225
225
|
def stop
|
226
226
|
@started = false
|
227
227
|
end
|
228
|
-
end
|
228
|
+
end # NestableUnitOfWork
|
229
229
|
|
230
230
|
# Listener that allows a nested unit of work to properly operate within in a unit of
|
231
231
|
# work that is not aware of nesting
|
@@ -264,6 +264,6 @@ module Synapse
|
|
264
264
|
def on_cleanup(outer_unit)
|
265
265
|
@inner_unit.perform_cleanup
|
266
266
|
end
|
267
|
-
end #
|
267
|
+
end # OuterCommitUnitOfWorkListener
|
268
268
|
end # UnitOfWork
|
269
269
|
end
|
data/lib/synapse/uow/uow.rb
CHANGED
@@ -34,7 +34,7 @@ module Synapse
|
|
34
34
|
# This unit of work adds an event listener to the aggregate so that any events generated
|
35
35
|
# are published to the given event bus when this unit of work is committed.
|
36
36
|
#
|
37
|
-
# The provided storage
|
37
|
+
# The provided storage callback is used to commit the aggregate to its respective
|
38
38
|
# underlying storage mechanism.
|
39
39
|
#
|
40
40
|
# If there is already an aggregate registered with this unit of work of the same type
|
@@ -42,23 +42,22 @@ module Synapse
|
|
42
42
|
# aggregate.
|
43
43
|
#
|
44
44
|
# @api public
|
45
|
+
# @yield [AggregateRoot] Deferred until the aggregate is stored
|
45
46
|
# @param [AggregateRoot] aggregate
|
46
47
|
# @param [EventBus] event_bus
|
47
|
-
# @param [
|
48
|
+
# @param [Proc] storage_callback
|
48
49
|
# @return [AggregateRoot]
|
49
|
-
def register_aggregate(aggregate, event_bus,
|
50
|
+
def register_aggregate(aggregate, event_bus, &storage_callback)
|
50
51
|
similar = find_similar_aggregate aggregate
|
51
|
-
if similar
|
52
|
-
|
52
|
+
return similar if similar
|
53
|
+
|
54
|
+
aggregate.add_registration_listener do |event|
|
55
|
+
publish_event event, event_bus
|
53
56
|
end
|
54
57
|
|
55
|
-
|
56
|
-
aggregate.add_registration_listener do |event|
|
57
|
-
publish_event event, event_bus
|
58
|
-
end
|
58
|
+
@aggregates.store aggregate, storage_callback
|
59
59
|
|
60
|
-
|
61
|
-
end
|
60
|
+
aggregate
|
62
61
|
end
|
63
62
|
|
64
63
|
# Buffers an event for publication to the given event bus until this unit of work is
|
@@ -146,8 +145,8 @@ module Synapse
|
|
146
145
|
|
147
146
|
# @return [undefined]
|
148
147
|
def store_aggregates
|
149
|
-
@aggregates.each_pair do |aggregate,
|
150
|
-
|
148
|
+
@aggregates.each_pair do |aggregate, storage_callback|
|
149
|
+
storage_callback.call aggregate
|
151
150
|
end
|
152
151
|
@aggregates.clear
|
153
152
|
end
|