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.
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