synapse-core 0.5.3 → 0.5.4
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.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
|