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.
Files changed (49) hide show
  1. data/lib/synapse/command.rb +2 -0
  2. data/lib/synapse/command/callbacks/future.rb +50 -0
  3. data/lib/synapse/command/command_callback.rb +0 -47
  4. data/lib/synapse/command/message.rb +1 -1
  5. data/lib/synapse/common.rb +1 -0
  6. data/lib/synapse/common/concurrency/executor.rb +13 -0
  7. data/lib/synapse/common/message.rb +9 -2
  8. data/lib/synapse/common/message_builder.rb +5 -1
  9. data/lib/synapse/configuration.rb +1 -0
  10. data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +2 -8
  11. data/lib/synapse/configuration/component/event_sourcing.rb +3 -3
  12. data/lib/synapse/configuration/component/event_sourcing/repository.rb +19 -2
  13. data/lib/synapse/configuration/component/event_sourcing/{aggregate_snapshot_taker.rb → snapshot/aggregate_taker.rb} +4 -4
  14. data/lib/synapse/configuration/component/event_sourcing/{interval_snapshot_policy.rb → snapshot/interval_policy.rb} +0 -0
  15. data/lib/synapse/configuration/component/mixin/thread_pool.rb +26 -0
  16. data/lib/synapse/domain/message.rb +0 -24
  17. data/lib/synapse/domain/message_builder.rb +0 -9
  18. data/lib/synapse/event_sourcing.rb +1 -1
  19. data/lib/synapse/event_sourcing/aggregate_root.rb +2 -1
  20. data/lib/synapse/event_sourcing/caching.rb +66 -0
  21. data/lib/synapse/event_sourcing/repository.rb +22 -15
  22. data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +8 -9
  23. data/lib/synapse/event_sourcing/snapshot/policy.rb +1 -0
  24. data/lib/synapse/event_sourcing/snapshot/taker.rb +31 -2
  25. data/lib/synapse/repository/repository.rb +18 -4
  26. data/lib/synapse/repository/simple_repository.rb +8 -17
  27. data/lib/synapse/serialization/converter.rb +2 -2
  28. data/lib/synapse/serialization/converter/identity.rb +2 -2
  29. data/lib/synapse/serialization/converter/json.rb +3 -3
  30. data/lib/synapse/serialization/converter/ox.rb +3 -3
  31. data/lib/synapse/serialization/message/metadata.rb +2 -2
  32. data/lib/synapse/serialization/message/serialization_aware.rb +2 -2
  33. data/lib/synapse/serialization/message/serialized_message.rb +7 -24
  34. data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
  35. data/lib/synapse/uow.rb +0 -1
  36. data/lib/synapse/uow/nesting.rb +2 -2
  37. data/lib/synapse/uow/uow.rb +12 -13
  38. data/lib/synapse/version.rb +1 -1
  39. data/test/configuration/component/event_sourcing/repository_test.rb +24 -1
  40. data/test/event_sourcing/caching_test.rb +118 -0
  41. data/test/event_sourcing/repository_test.rb +2 -1
  42. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +46 -16
  43. data/test/repository/locking_test.rb +1 -10
  44. data/test/serialization/serializer/attribute_test.rb +51 -0
  45. data/test/uow/uow_test.rb +9 -12
  46. metadata +12 -9
  47. data/lib/synapse/event_sourcing/storage_listener.rb +0 -34
  48. data/lib/synapse/uow/storage_listener.rb +0 -14
  49. 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(event_store)
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
- # @return [undefined]
21
- def schedule_snapshot(type_identifier, aggregate_id)
22
- stream = @event_store.read_events type_identifier, aggregate_id
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
- snapshot = Domain::DomainEventMessage.build do |builder|
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); end
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
- # Returns the listener that handles aggregate storage
51
+ # Deletes the given aggregate from the underlying storage mechanism
52
52
  #
53
53
  # @abstract
54
- # @return [StorageListener]
55
- def storage_listener; end
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, storage_listener
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
- # @return [StorageListener]
50
- def storage_listener
51
- @storage_listener
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 store(aggregate)
61
- if aggregate.deleted?
62
- aggregate.destroy
63
- else
64
- aggregate.save
65
- end
55
+ def save_aggregate(aggregate)
56
+ aggregate.save
66
57
  end
67
- end # SimpleStorageListener
58
+ end # SimpleRepository
68
59
  end # Repository
69
60
  end
@@ -34,6 +34,6 @@ module Synapse
34
34
  # @param [Object] original
35
35
  # @return [Object]
36
36
  def convert_content(original); end
37
- end
38
- end
37
+ end # Converter
38
+ end # Serialization
39
39
  end
@@ -24,6 +24,6 @@ module Synapse
24
24
  def convert_content(original)
25
25
  original
26
26
  end
27
- end
28
- end
27
+ end # IdentityConverter
28
+ end # Serialization
29
29
  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
@@ -8,6 +8,6 @@ module Synapse
8
8
  def initialize(content, content_type)
9
9
  super(content, content_type, SerializedType.new(Hash.to_s, nil))
10
10
  end
11
- end
12
- end
11
+ end # SerializedMetadata
12
+ end # Serialization
13
13
  end
@@ -12,6 +12,6 @@ module Synapse
12
12
  # @param [Class] expected_type
13
13
  # @return [SerializedObject]
14
14
  def serialize_payload(serializer, expected_type); end
15
- end
16
- end
15
+ end # SerializationAware
16
+ end # Serialization
17
17
  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
@@ -3,6 +3,5 @@ require 'synapse/uow/listener'
3
3
  require 'synapse/uow/listener_collection'
4
4
  require 'synapse/uow/nesting'
5
5
  require 'synapse/uow/provider'
6
- require 'synapse/uow/storage_listener'
7
6
  require 'synapse/uow/transaction_manager'
8
7
  require 'synapse/uow/uow'
@@ -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 # NestableUnitOfWork
267
+ end # OuterCommitUnitOfWorkListener
268
268
  end # UnitOfWork
269
269
  end
@@ -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 listener is used to commit the aggregate to its respective
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 [StorageListener] storage_listener
48
+ # @param [Proc] storage_callback
48
49
  # @return [AggregateRoot]
49
- def register_aggregate(aggregate, event_bus, storage_listener)
50
+ def register_aggregate(aggregate, event_bus, &storage_callback)
50
51
  similar = find_similar_aggregate aggregate
51
- if similar
52
- return similar
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
- aggregate.tap do
56
- aggregate.add_registration_listener do |event|
57
- publish_event event, event_bus
58
- end
58
+ @aggregates.store aggregate, storage_callback
59
59
 
60
- @aggregates.store aggregate, storage_listener
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, storage_listener|
150
- storage_listener.store aggregate
148
+ @aggregates.each_pair do |aggregate, storage_callback|
149
+ storage_callback.call aggregate
151
150
  end
152
151
  @aggregates.clear
153
152
  end