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.
Files changed (155) hide show
  1. data/lib/synapse.rb +351 -0
  2. data/lib/synapse/command/command_bus.rb +45 -0
  3. data/lib/synapse/command/command_callback.rb +18 -0
  4. data/lib/synapse/command/command_filter.rb +17 -0
  5. data/lib/synapse/command/command_handler.rb +13 -0
  6. data/lib/synapse/command/dispatch_interceptor.rb +16 -0
  7. data/lib/synapse/command/duplication.rb +43 -0
  8. data/lib/synapse/command/errors.rb +27 -0
  9. data/lib/synapse/command/filters/validation.rb +32 -0
  10. data/lib/synapse/command/gateway.rb +34 -0
  11. data/lib/synapse/command/interceptor_chain.rb +31 -0
  12. data/lib/synapse/command/interceptors/serialization.rb +35 -0
  13. data/lib/synapse/command/message.rb +19 -0
  14. data/lib/synapse/command/rollback_policy.rb +22 -0
  15. data/lib/synapse/command/simple_command_bus.rb +138 -0
  16. data/lib/synapse/command/wiring.rb +47 -0
  17. data/lib/synapse/domain/aggregate_root.rb +121 -0
  18. data/lib/synapse/domain/event_container.rb +127 -0
  19. data/lib/synapse/domain/message.rb +82 -0
  20. data/lib/synapse/domain/message_builder.rb +34 -0
  21. data/lib/synapse/domain/stream.rb +108 -0
  22. data/lib/synapse/duplication.rb +60 -0
  23. data/lib/synapse/errors.rb +13 -0
  24. data/lib/synapse/event_bus/event_bus.rb +40 -0
  25. data/lib/synapse/event_bus/event_listener.rb +16 -0
  26. data/lib/synapse/event_bus/event_listener_proxy.rb +12 -0
  27. data/lib/synapse/event_bus/simple_event_bus.rb +69 -0
  28. data/lib/synapse/event_bus/wiring.rb +23 -0
  29. data/lib/synapse/event_sourcing/aggregate_factory.rb +69 -0
  30. data/lib/synapse/event_sourcing/aggregate_root.rb +104 -0
  31. data/lib/synapse/event_sourcing/conflict_resolver.rb +80 -0
  32. data/lib/synapse/event_sourcing/entity.rb +64 -0
  33. data/lib/synapse/event_sourcing/member.rb +72 -0
  34. data/lib/synapse/event_sourcing/repository.rb +119 -0
  35. data/lib/synapse/event_sourcing/snapshot/count_stream.rb +86 -0
  36. data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +91 -0
  37. data/lib/synapse/event_sourcing/snapshot/taker.rb +73 -0
  38. data/lib/synapse/event_sourcing/storage_listener.rb +34 -0
  39. data/lib/synapse/event_sourcing/stream_decorator.rb +25 -0
  40. data/lib/synapse/event_store/errors.rb +16 -0
  41. data/lib/synapse/event_store/event_store.rb +43 -0
  42. data/lib/synapse/event_store/in_memory.rb +59 -0
  43. data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
  44. data/lib/synapse/event_store/mongo/event_store.rb +86 -0
  45. data/lib/synapse/event_store/mongo/per_commit_strategy.rb +253 -0
  46. data/lib/synapse/event_store/mongo/per_event_strategy.rb +143 -0
  47. data/lib/synapse/event_store/mongo/storage_strategy.rb +113 -0
  48. data/lib/synapse/event_store/mongo/template.rb +73 -0
  49. data/lib/synapse/identifier.rb +23 -0
  50. data/lib/synapse/message.rb +101 -0
  51. data/lib/synapse/message_builder.rb +38 -0
  52. data/lib/synapse/process_manager/correlation.rb +32 -0
  53. data/lib/synapse/process_manager/correlation_resolver.rb +14 -0
  54. data/lib/synapse/process_manager/correlation_set.rb +58 -0
  55. data/lib/synapse/process_manager/process.rb +71 -0
  56. data/lib/synapse/repository/errors.rb +26 -0
  57. data/lib/synapse/repository/lock_manager.rb +40 -0
  58. data/lib/synapse/repository/locking.rb +97 -0
  59. data/lib/synapse/repository/pessimistic_lock_manager.rb +61 -0
  60. data/lib/synapse/repository/repository.rb +109 -0
  61. data/lib/synapse/serialization/converter.rb +39 -0
  62. data/lib/synapse/serialization/converter/chain.rb +45 -0
  63. data/lib/synapse/serialization/converter/factory.rb +68 -0
  64. data/lib/synapse/serialization/converter/identity.rb +29 -0
  65. data/lib/synapse/serialization/converter/json.rb +31 -0
  66. data/lib/synapse/serialization/converter/ox.rb +31 -0
  67. data/lib/synapse/serialization/errors.rb +12 -0
  68. data/lib/synapse/serialization/lazy_object.rb +61 -0
  69. data/lib/synapse/serialization/message/data.rb +25 -0
  70. data/lib/synapse/serialization/message/metadata.rb +13 -0
  71. data/lib/synapse/serialization/message/serialization_aware.rb +17 -0
  72. data/lib/synapse/serialization/message/serialization_aware_message.rb +66 -0
  73. data/lib/synapse/serialization/message/serialized_message.rb +201 -0
  74. data/lib/synapse/serialization/message/serialized_message_builder.rb +64 -0
  75. data/lib/synapse/serialization/message/serialized_object_cache.rb +50 -0
  76. data/lib/synapse/serialization/message/serializer.rb +47 -0
  77. data/lib/synapse/serialization/revision_resolver.rb +30 -0
  78. data/lib/synapse/serialization/serialized_object.rb +37 -0
  79. data/lib/synapse/serialization/serialized_type.rb +31 -0
  80. data/lib/synapse/serialization/serializer.rb +98 -0
  81. data/lib/synapse/serialization/serializer/marshal.rb +32 -0
  82. data/lib/synapse/serialization/serializer/oj.rb +34 -0
  83. data/lib/synapse/serialization/serializer/ox.rb +31 -0
  84. data/lib/synapse/uow/factory.rb +28 -0
  85. data/lib/synapse/uow/listener.rb +79 -0
  86. data/lib/synapse/uow/listener_collection.rb +93 -0
  87. data/lib/synapse/uow/nesting.rb +262 -0
  88. data/lib/synapse/uow/provider.rb +71 -0
  89. data/lib/synapse/uow/storage_listener.rb +14 -0
  90. data/lib/synapse/uow/transaction_manager.rb +27 -0
  91. data/lib/synapse/uow/uow.rb +178 -0
  92. data/lib/synapse/upcasting/chain.rb +78 -0
  93. data/lib/synapse/upcasting/context.rb +58 -0
  94. data/lib/synapse/upcasting/data.rb +30 -0
  95. data/lib/synapse/upcasting/single_upcaster.rb +57 -0
  96. data/lib/synapse/upcasting/upcaster.rb +55 -0
  97. data/lib/synapse/version.rb +3 -0
  98. data/lib/synapse/wiring/message_wiring.rb +41 -0
  99. data/lib/synapse/wiring/wire.rb +55 -0
  100. data/lib/synapse/wiring/wire_registry.rb +61 -0
  101. data/test/command/duplication_test.rb +54 -0
  102. data/test/command/gateway_test.rb +25 -0
  103. data/test/command/interceptor_chain_test.rb +26 -0
  104. data/test/command/serialization_test.rb +37 -0
  105. data/test/command/simple_command_bus_test.rb +141 -0
  106. data/test/command/validation_test.rb +42 -0
  107. data/test/command/wiring_test.rb +73 -0
  108. data/test/domain/aggregate_root_test.rb +57 -0
  109. data/test/domain/fixtures.rb +31 -0
  110. data/test/domain/message_test.rb +61 -0
  111. data/test/domain/stream_test.rb +35 -0
  112. data/test/duplication_test.rb +40 -0
  113. data/test/event_bus/wiring_test.rb +46 -0
  114. data/test/event_sourcing/aggregate_factory_test.rb +28 -0
  115. data/test/event_sourcing/aggregate_root_test.rb +76 -0
  116. data/test/event_sourcing/entity_test.rb +34 -0
  117. data/test/event_sourcing/fixtures.rb +85 -0
  118. data/test/event_sourcing/repository_test.rb +102 -0
  119. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +39 -0
  120. data/test/event_sourcing/snapshot/deferred_taker_test.rb +19 -0
  121. data/test/event_sourcing/snapshot/integration_test.rb +65 -0
  122. data/test/event_sourcing/storage_listener_test.rb +77 -0
  123. data/test/event_store/in_memory_test.rb +47 -0
  124. data/test/process_manager/correlation_set_test.rb +49 -0
  125. data/test/process_manager/correlation_test.rb +24 -0
  126. data/test/process_manager/process_test.rb +52 -0
  127. data/test/repository/locking_test.rb +101 -0
  128. data/test/serialization/converter/factory_test.rb +33 -0
  129. data/test/serialization/converter/identity_test.rb +17 -0
  130. data/test/serialization/converter/json_test.rb +31 -0
  131. data/test/serialization/converter/ox_test.rb +40 -0
  132. data/test/serialization/fixtures.rb +17 -0
  133. data/test/serialization/lazy_object_test.rb +32 -0
  134. data/test/serialization/message/metadata_test.rb +19 -0
  135. data/test/serialization/message/serialization_aware_message_test.rb +88 -0
  136. data/test/serialization/message/serialized_message_builder_test.rb +41 -0
  137. data/test/serialization/message/serialized_message_test.rb +140 -0
  138. data/test/serialization/message/serializer_test.rb +50 -0
  139. data/test/serialization/revision_resolver_test.rb +12 -0
  140. data/test/serialization/serialized_object_test.rb +36 -0
  141. data/test/serialization/serialized_type_test.rb +27 -0
  142. data/test/serialization/serializer/marshal_test.rb +22 -0
  143. data/test/serialization/serializer/oj_test.rb +24 -0
  144. data/test/serialization/serializer/ox_test.rb +36 -0
  145. data/test/serialization/serializer_test.rb +20 -0
  146. data/test/test_helper.rb +19 -0
  147. data/test/uow/factory_test.rb +23 -0
  148. data/test/uow/outer_commit_listener_test.rb +50 -0
  149. data/test/uow/provider_test.rb +70 -0
  150. data/test/uow/uow_test.rb +337 -0
  151. data/test/upcasting/chain_test.rb +29 -0
  152. data/test/upcasting/fixtures.rb +66 -0
  153. data/test/wiring/wire_registry_test.rb +60 -0
  154. data/test/wiring/wire_test.rb +51 -0
  155. metadata +263 -0
@@ -0,0 +1,119 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Repository that initializes the state of aggregates using events read from an event
4
+ # store and appends changes to aggregates to an event store
5
+ class EventSourcingRepository < Repository::LockingRepository
6
+ # @return [AggregateFactory]
7
+ attr_reader :aggregate_factory
8
+
9
+ # @return [ConflictResolver]
10
+ attr_accessor :conflict_resolver
11
+
12
+ # @return [EventStore]
13
+ attr_reader :event_store
14
+
15
+ # @return [Array<EventStreamDecorator>]
16
+ attr_reader :stream_decorators
17
+
18
+ # @param [AggregateFactory] aggregate_factory
19
+ # @param [EventStore] event_store
20
+ # @param [LockManager] lock_manager
21
+ # @return [undefined]
22
+ def initialize(aggregate_factory, event_store, lock_manager)
23
+ super lock_manager
24
+
25
+ @aggregate_factory = aggregate_factory
26
+ @event_store = event_store
27
+ @stream_decorators = Array.new
28
+ @storage_listener =
29
+ EventSourcedStorageListener.new @event_store, @lock_manager, @stream_decorators, type_identifier
30
+ end
31
+
32
+ # Appends a stream decorator onto the end of the list of stream decorators
33
+ #
34
+ # @param [EventStreamDecorator] stream_decorator
35
+ # @return [undefined]
36
+ def add_stream_decorator(stream_decorator)
37
+ @stream_decorators.push stream_decorator
38
+ end
39
+
40
+ protected
41
+
42
+ # @raise [AggregateNotFoundError]
43
+ # If the aggregate with the given identifier could not be found
44
+ # @raise [AggregateDeletedError]
45
+ # If the loaded aggregate has been marked as deleted
46
+ # @raise [ConflictingModificationError]
47
+ # If the expected version doesn't match the aggregate's actual version
48
+ # @param [Object] aggregate_id
49
+ # @param [Integer] expected_version
50
+ # @return [AggregateRoot]
51
+ def perform_load(aggregate_id, expected_version)
52
+ begin
53
+ stream = @event_store.read_events type_identifier, aggregate_id
54
+ rescue EventStore::StreamNotFoundError
55
+ raise Repository::AggregateNotFoundError
56
+ end
57
+
58
+ @stream_decorators.each do |decorator|
59
+ stream = decorator.decorate_for_read aggregate_type, aggregate_id, stream
60
+ end
61
+
62
+ aggregate = @aggregate_factory.create_aggregate aggregate_id, stream.peek
63
+
64
+ stream = add_conflict_resolution stream, aggregate, expected_version
65
+
66
+ aggregate.initialize_from_stream stream
67
+
68
+ if aggregate.deleted?
69
+ raise AggregateDeletedError
70
+ end
71
+
72
+ if expected_version and @conflict_resolver.nil?
73
+ assert_version_expected aggregate, expected_version
74
+ end
75
+
76
+ aggregate
77
+ end
78
+
79
+ # @return [Class]
80
+ def aggregate_type
81
+ @aggregate_factory.aggregate_type
82
+ end
83
+
84
+ # @return [StorageListener]
85
+ def storage_listener
86
+ @storage_listener
87
+ end
88
+
89
+ # @return [String]
90
+ def type_identifier
91
+ @aggregate_factory.type_identifier
92
+ end
93
+
94
+ private
95
+
96
+ # @param [DomainEventStream] stream
97
+ # @param [AggregateRoot] aggregate
98
+ # @param [Integer] expected_version
99
+ # @return [DomainEventStream]
100
+ def add_conflict_resolution(stream, aggregate, expected_version)
101
+ unless expected_version and @conflict_resolver
102
+ return stream
103
+ end
104
+
105
+ unseen_events = Array.new
106
+
107
+ stream = CapturingEventStream.new stream, unseen_events, expected_version
108
+ listener = ConflictResolvingUnitOfWorkListener.new aggregate, unseen_events, @conflict_resolver
109
+
110
+ register_listener listener
111
+
112
+ stream
113
+ end
114
+ end
115
+
116
+ # Raised when an aggregate has been found but it was marked for deletion
117
+ class AggregateDeletedError < Repository::AggregateNotFoundError; end
118
+ end
119
+ end
@@ -0,0 +1,86 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Event stream decorator that simply counts each event that is retrieved from a delegate stream
4
+ class CountingEventStream < Domain::DomainEventStream
5
+ extend Forwardable
6
+
7
+ # @param [DomainEventStream] delegate
8
+ # @param [Atomic] counter
9
+ # @return [undefined]
10
+ def initialize(delegate, counter)
11
+ @delegate = delegate
12
+ @counter = counter
13
+ end
14
+
15
+ # @return [DomainEventMessage]
16
+ def next_event
17
+ next_event = @delegate.next_event
18
+
19
+ @counter.update do |value|
20
+ value = value.next
21
+ end
22
+
23
+ next_event
24
+ end
25
+
26
+ # Delegate un-decorated domain event stream operations
27
+ def_delegators :@delegate, :end?, :peek
28
+ end
29
+
30
+ # Event stream decorator that counts each event retrieved from the delegate stream and
31
+ # registers a listener with the current unit of work that can trigger a snapshot after the
32
+ # unit of work has been cleaned up
33
+ class TriggeringEventStream < CountingEventStream
34
+ # @param [DomainEventStream] delegate
35
+ # @param [Atomic] counter
36
+ # @param [String] type_identifier
37
+ # @param [Object] aggregate_id
38
+ # @param [EventCountSnapshotTrigger] trigger
39
+ # @return [undefined]
40
+ def initialize(delegate, counter, type_identifier, aggregate_id, trigger)
41
+ super delegate, counter
42
+
43
+ @type_identifier = type_identifier
44
+ @aggregate_id = aggregate_id
45
+ @trigger = trigger
46
+ end
47
+
48
+ # @return [Boolean]
49
+ def end?
50
+ the_end = @delegate.end?
51
+
52
+ if the_end
53
+ listener = SnapshotUnitOfWorkListener.new @type_identifier, @aggregate_id, @counter, @trigger
54
+
55
+ unit = @trigger.unit_provider.current
56
+ unit.register_listener listener
57
+
58
+ @trigger.clear_counter @aggregate_id
59
+ end
60
+
61
+ the_end
62
+ end
63
+ end
64
+
65
+ # Unit of work listener that is used to trigger snapshots after a unit of work has been cleaned up
66
+ class SnapshotUnitOfWorkListener < UnitOfWork::UnitOfWorkListener
67
+ # @param [String] type_identifier
68
+ # @param [Object] aggregate_id
69
+ # @param [Atomic] counter
70
+ # @param [EventCountSnapshotTrigger] trigger
71
+ # @return [undefined]
72
+ def initialize(type_identifier, aggregate_id, counter, trigger)
73
+ @type_identifier = type_identifier
74
+ @aggregate_id = aggregate_id
75
+ @trigger = trigger
76
+ @counter = counter
77
+ end
78
+
79
+ # @param [UnitOfWork] unit
80
+ # @return [undefined]
81
+ def on_cleanup(unit)
82
+ @trigger.trigger_snapshot @type_identifier, @aggregate_id, @counter
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,91 @@
1
+ require 'atomic'
2
+
3
+ module Synapse
4
+ module EventSourcing
5
+ # Snapshot trigger that counts the number of events between snapshots to decide when to
6
+ # schedule the next snapshot
7
+ class EventCountSnapshotTrigger < EventStreamDecorator
8
+ # Default threshold for a snapshot to be scheduled
9
+ # @return [Integer]
10
+ DEFAULT_THRESHOLD = 50
11
+
12
+ # @return [SnapshotTaker]
13
+ attr_reader :snapshot_taker
14
+
15
+ # @return [Integer] The number of events between snapshots
16
+ attr_accessor :threshold
17
+
18
+ # @return [UnitOfWorkProvider]
19
+ attr_reader :unit_provider
20
+
21
+ # @param [SnapshotTaker] snapshot_taker
22
+ # @param [UnitOfWorkProvider] unit_provider
23
+ # @return [undefined]
24
+ def initialize(snapshot_taker, unit_provider)
25
+ @counters = Hash.new
26
+ @lock = Mutex.new
27
+ @logger = Logging.logger.new self.class
28
+ @threshold = DEFAULT_THRESHOLD
29
+
30
+ @snapshot_taker = snapshot_taker
31
+ @unit_provider = unit_provider
32
+ end
33
+
34
+ # If the event threshold has been reached for the aggregate with the given identifier, this
35
+ # will cause a snapshot to be scheduled
36
+ #
37
+ # @param [String] type_identifier
38
+ # @param [Object] aggregate_id
39
+ # @param [Atomic] counter
40
+ # @return [undefined]
41
+ def trigger_snapshot(type_identifier, aggregate_id, counter)
42
+ if counter.value > @threshold
43
+ @logger.debug 'Snapshot threshold reached for [%s] [%s]' % [type_identifier, aggregate_id]
44
+
45
+ @snapshot_taker.schedule_snapshot type_identifier, aggregate_id
46
+ counter.value = 1
47
+ end
48
+ end
49
+
50
+ # @param [String] type_identifier
51
+ # @param [Object] aggregate_id
52
+ # @param [DomainEventStream] stream
53
+ # @return [DomainEventStream]
54
+ def decorate_for_read(type_identifier, aggregate_id, stream)
55
+ CountingEventStream.new(stream, counter_for(aggregate_id))
56
+ end
57
+
58
+ # @param [String] type_identifier
59
+ # @param [AggregateRoot] aggregate
60
+ # @param [DomainEventStream] stream
61
+ # @return [DomainEventStream]
62
+ def decorate_for_append(type_identifier, aggregate, stream)
63
+ TriggeringEventStream.new(stream, counter_for(aggregate.id), type_identifier, aggregate.id, self)
64
+ end
65
+
66
+ # Returns the event counter for the aggregate with the given identifier
67
+ #
68
+ # @param [Object] aggregate_id
69
+ # @return [Atomic]
70
+ def counter_for(aggregate_id)
71
+ @lock.synchronize do
72
+ if @counters.has_key? aggregate_id
73
+ @counters.fetch aggregate_id
74
+ else
75
+ @counters.store aggregate_id, Atomic.new(0)
76
+ end
77
+ end
78
+ end
79
+
80
+ # Clears the event counter for the aggregate with the given identifier
81
+ #
82
+ # @param [Object] aggregate_id
83
+ # @return [undefined]
84
+ def clear_counter(aggregate_id)
85
+ @lock.synchronize do
86
+ @counters.delete aggregate_id
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,73 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Represents a mechanism for creating snapshot events for aggregates
4
+ #
5
+ # Implementations can choose whether to snapshot the aggregate in the calling thread or
6
+ # asynchronously, though it is typically done asynchronously.
7
+ #
8
+ # @abstract
9
+ class SnapshotTaker
10
+ # Schedules a snapshot to be taken for an aggregate of the given type and with the given
11
+ # identifier
12
+ #
13
+ # @abstract
14
+ # @param [String] type_identifier
15
+ # @param [Object] aggregate_id
16
+ # @return [undefined]
17
+ def schedule_snapshot(type_identifier, aggregate_id); end
18
+ end
19
+
20
+ # Snapshot taker that uses EventMachine to defer scheduling of snapshots
21
+ class DeferredSnapshotTaker < SnapshotTaker
22
+ # @param [SnapshotTaker] delegate
23
+ # @return [undefined]
24
+ def initialize(delegate)
25
+ @delegate = delegate
26
+ end
27
+
28
+ # @param [String] type_identifier
29
+ # @param [Object] aggregate_id
30
+ # @return [undefined]
31
+ def schedule_snapshot(type_identifier, aggregate_id)
32
+ EventMachine.defer do
33
+ @delegate.schedule_snapshot type_identifier, aggregate_id
34
+ end
35
+ end
36
+ end
37
+
38
+ # Snapshot taker that uses the actual aggregate and its state to create a snapshot event
39
+ class AggregateSnapshotTaker < SnapshotTaker
40
+ # @param [SnapshotEventStore] event_store
41
+ # @return [undefined]
42
+ def initialize(event_store)
43
+ @aggregate_factories = Hash.new
44
+ @event_store = event_store
45
+ end
46
+
47
+ # @param [AggregateFactory] factory
48
+ # @return [undefined]
49
+ def register_factory(factory)
50
+ @aggregate_factories.store factory.type_identifier, factory
51
+ end
52
+
53
+ # @param [String] type_identifier
54
+ # @param [Object] aggregate_id
55
+ # @return [undefined]
56
+ def schedule_snapshot(type_identifier, aggregate_id)
57
+ stream = @event_store.read_events type_identifier, aggregate_id
58
+ factory = @aggregate_factories.fetch type_identifier
59
+
60
+ aggregate = factory.create_aggregate aggregate_id, stream.peek
61
+ aggregate.initialize_from_stream stream
62
+
63
+ snapshot = Domain::DomainEventMessage.build do |builder|
64
+ builder.payload = aggregate
65
+ builder.aggregate_id = aggregate.id
66
+ builder.sequence_number = aggregate.version
67
+ end
68
+
69
+ @event_store.append_snapshot_event type_identifier, snapshot
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Storage listener that commits aggregates to an event store
4
+ class EventSourcedStorageListener < UnitOfWork::StorageListener
5
+ # @param [EventStore] event_store
6
+ # @param [LockManager] lock_manager
7
+ # @param [Array] stream_decorators
8
+ # @param [String] type_identifier
9
+ # @return [undefined]
10
+ def initialize(event_store, lock_manager, stream_decorators, type_identifier)
11
+ @event_store = event_store
12
+ @lock_manager = lock_manager
13
+ @stream_decorators = stream_decorators
14
+ @type_identifier = type_identifier
15
+ end
16
+
17
+ # @param [AggregateRoot] aggregate
18
+ # @return [undefined]
19
+ def store(aggregate)
20
+ if aggregate.version and !@lock_manager.validate_lock aggregate
21
+ raise Repository::ConflictingModificationError
22
+ end
23
+
24
+ stream = aggregate.uncommitted_events
25
+ @stream_decorators.reverse_each do |decorator|
26
+ stream = decorator.decorate_for_append @type_identifier, aggregate, stream
27
+ end
28
+
29
+ @event_store.append_events @type_identifier, stream
30
+ aggregate.mark_committed
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Represents a mechanism for decorating event streams when aggregates are read or appended
4
+ # @abstract
5
+ class EventStreamDecorator
6
+ # Decorates an event stream when it is read from the event store
7
+ #
8
+ # @abstract
9
+ # @param [String] type_identifier
10
+ # @param [Object] aggregate_id
11
+ # @param [DomainEventStream] stream
12
+ # @return [DomainEventStream]
13
+ def decorate_for_read(type_identifier, aggregate_id, stream); end
14
+
15
+ # Decorates an event stream when it is appended to the event store
16
+ #
17
+ # @abstract
18
+ # @param [String] type_identifier
19
+ # @param [AggregateRoot] aggregate
20
+ # @param [DomainEventStream] stream
21
+ # @return [DomainEventStream]
22
+ def decorate_for_append(type_identifier, aggregate, stream); end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module Synapse
2
+ module EventStore
3
+ # Raised when an error occurs when reading or appending events to an event store
4
+ class EventStoreError < NonTransientError; end
5
+
6
+ # Raised when a stream could not be found for a specific aggregate
7
+ class StreamNotFoundError < EventStoreError
8
+ # @param [String] type_identifier
9
+ # @param [Object] aggregate_id
10
+ # @return [undefined]
11
+ def initialize(type_identifier, aggregate_id)
12
+ super 'Stream not found for [%s] [%s]' % [type_identifier, aggregate_id]
13
+ end
14
+ end
15
+ end
16
+ end