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.
- data/lib/synapse.rb +351 -0
- data/lib/synapse/command/command_bus.rb +45 -0
- data/lib/synapse/command/command_callback.rb +18 -0
- data/lib/synapse/command/command_filter.rb +17 -0
- data/lib/synapse/command/command_handler.rb +13 -0
- data/lib/synapse/command/dispatch_interceptor.rb +16 -0
- data/lib/synapse/command/duplication.rb +43 -0
- data/lib/synapse/command/errors.rb +27 -0
- data/lib/synapse/command/filters/validation.rb +32 -0
- data/lib/synapse/command/gateway.rb +34 -0
- data/lib/synapse/command/interceptor_chain.rb +31 -0
- data/lib/synapse/command/interceptors/serialization.rb +35 -0
- data/lib/synapse/command/message.rb +19 -0
- data/lib/synapse/command/rollback_policy.rb +22 -0
- data/lib/synapse/command/simple_command_bus.rb +138 -0
- data/lib/synapse/command/wiring.rb +47 -0
- data/lib/synapse/domain/aggregate_root.rb +121 -0
- data/lib/synapse/domain/event_container.rb +127 -0
- data/lib/synapse/domain/message.rb +82 -0
- data/lib/synapse/domain/message_builder.rb +34 -0
- data/lib/synapse/domain/stream.rb +108 -0
- data/lib/synapse/duplication.rb +60 -0
- data/lib/synapse/errors.rb +13 -0
- data/lib/synapse/event_bus/event_bus.rb +40 -0
- data/lib/synapse/event_bus/event_listener.rb +16 -0
- data/lib/synapse/event_bus/event_listener_proxy.rb +12 -0
- data/lib/synapse/event_bus/simple_event_bus.rb +69 -0
- data/lib/synapse/event_bus/wiring.rb +23 -0
- data/lib/synapse/event_sourcing/aggregate_factory.rb +69 -0
- data/lib/synapse/event_sourcing/aggregate_root.rb +104 -0
- data/lib/synapse/event_sourcing/conflict_resolver.rb +80 -0
- data/lib/synapse/event_sourcing/entity.rb +64 -0
- data/lib/synapse/event_sourcing/member.rb +72 -0
- data/lib/synapse/event_sourcing/repository.rb +119 -0
- data/lib/synapse/event_sourcing/snapshot/count_stream.rb +86 -0
- data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +91 -0
- data/lib/synapse/event_sourcing/snapshot/taker.rb +73 -0
- data/lib/synapse/event_sourcing/storage_listener.rb +34 -0
- data/lib/synapse/event_sourcing/stream_decorator.rb +25 -0
- data/lib/synapse/event_store/errors.rb +16 -0
- data/lib/synapse/event_store/event_store.rb +43 -0
- data/lib/synapse/event_store/in_memory.rb +59 -0
- data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
- data/lib/synapse/event_store/mongo/event_store.rb +86 -0
- data/lib/synapse/event_store/mongo/per_commit_strategy.rb +253 -0
- data/lib/synapse/event_store/mongo/per_event_strategy.rb +143 -0
- data/lib/synapse/event_store/mongo/storage_strategy.rb +113 -0
- data/lib/synapse/event_store/mongo/template.rb +73 -0
- data/lib/synapse/identifier.rb +23 -0
- data/lib/synapse/message.rb +101 -0
- data/lib/synapse/message_builder.rb +38 -0
- data/lib/synapse/process_manager/correlation.rb +32 -0
- data/lib/synapse/process_manager/correlation_resolver.rb +14 -0
- data/lib/synapse/process_manager/correlation_set.rb +58 -0
- data/lib/synapse/process_manager/process.rb +71 -0
- data/lib/synapse/repository/errors.rb +26 -0
- data/lib/synapse/repository/lock_manager.rb +40 -0
- data/lib/synapse/repository/locking.rb +97 -0
- data/lib/synapse/repository/pessimistic_lock_manager.rb +61 -0
- data/lib/synapse/repository/repository.rb +109 -0
- data/lib/synapse/serialization/converter.rb +39 -0
- data/lib/synapse/serialization/converter/chain.rb +45 -0
- data/lib/synapse/serialization/converter/factory.rb +68 -0
- data/lib/synapse/serialization/converter/identity.rb +29 -0
- data/lib/synapse/serialization/converter/json.rb +31 -0
- data/lib/synapse/serialization/converter/ox.rb +31 -0
- data/lib/synapse/serialization/errors.rb +12 -0
- data/lib/synapse/serialization/lazy_object.rb +61 -0
- data/lib/synapse/serialization/message/data.rb +25 -0
- data/lib/synapse/serialization/message/metadata.rb +13 -0
- data/lib/synapse/serialization/message/serialization_aware.rb +17 -0
- data/lib/synapse/serialization/message/serialization_aware_message.rb +66 -0
- data/lib/synapse/serialization/message/serialized_message.rb +201 -0
- data/lib/synapse/serialization/message/serialized_message_builder.rb +64 -0
- data/lib/synapse/serialization/message/serialized_object_cache.rb +50 -0
- data/lib/synapse/serialization/message/serializer.rb +47 -0
- data/lib/synapse/serialization/revision_resolver.rb +30 -0
- data/lib/synapse/serialization/serialized_object.rb +37 -0
- data/lib/synapse/serialization/serialized_type.rb +31 -0
- data/lib/synapse/serialization/serializer.rb +98 -0
- data/lib/synapse/serialization/serializer/marshal.rb +32 -0
- data/lib/synapse/serialization/serializer/oj.rb +34 -0
- data/lib/synapse/serialization/serializer/ox.rb +31 -0
- data/lib/synapse/uow/factory.rb +28 -0
- data/lib/synapse/uow/listener.rb +79 -0
- data/lib/synapse/uow/listener_collection.rb +93 -0
- data/lib/synapse/uow/nesting.rb +262 -0
- data/lib/synapse/uow/provider.rb +71 -0
- data/lib/synapse/uow/storage_listener.rb +14 -0
- data/lib/synapse/uow/transaction_manager.rb +27 -0
- data/lib/synapse/uow/uow.rb +178 -0
- data/lib/synapse/upcasting/chain.rb +78 -0
- data/lib/synapse/upcasting/context.rb +58 -0
- data/lib/synapse/upcasting/data.rb +30 -0
- data/lib/synapse/upcasting/single_upcaster.rb +57 -0
- data/lib/synapse/upcasting/upcaster.rb +55 -0
- data/lib/synapse/version.rb +3 -0
- data/lib/synapse/wiring/message_wiring.rb +41 -0
- data/lib/synapse/wiring/wire.rb +55 -0
- data/lib/synapse/wiring/wire_registry.rb +61 -0
- data/test/command/duplication_test.rb +54 -0
- data/test/command/gateway_test.rb +25 -0
- data/test/command/interceptor_chain_test.rb +26 -0
- data/test/command/serialization_test.rb +37 -0
- data/test/command/simple_command_bus_test.rb +141 -0
- data/test/command/validation_test.rb +42 -0
- data/test/command/wiring_test.rb +73 -0
- data/test/domain/aggregate_root_test.rb +57 -0
- data/test/domain/fixtures.rb +31 -0
- data/test/domain/message_test.rb +61 -0
- data/test/domain/stream_test.rb +35 -0
- data/test/duplication_test.rb +40 -0
- data/test/event_bus/wiring_test.rb +46 -0
- data/test/event_sourcing/aggregate_factory_test.rb +28 -0
- data/test/event_sourcing/aggregate_root_test.rb +76 -0
- data/test/event_sourcing/entity_test.rb +34 -0
- data/test/event_sourcing/fixtures.rb +85 -0
- data/test/event_sourcing/repository_test.rb +102 -0
- data/test/event_sourcing/snapshot/aggregate_taker_test.rb +39 -0
- data/test/event_sourcing/snapshot/deferred_taker_test.rb +19 -0
- data/test/event_sourcing/snapshot/integration_test.rb +65 -0
- data/test/event_sourcing/storage_listener_test.rb +77 -0
- data/test/event_store/in_memory_test.rb +47 -0
- data/test/process_manager/correlation_set_test.rb +49 -0
- data/test/process_manager/correlation_test.rb +24 -0
- data/test/process_manager/process_test.rb +52 -0
- data/test/repository/locking_test.rb +101 -0
- data/test/serialization/converter/factory_test.rb +33 -0
- data/test/serialization/converter/identity_test.rb +17 -0
- data/test/serialization/converter/json_test.rb +31 -0
- data/test/serialization/converter/ox_test.rb +40 -0
- data/test/serialization/fixtures.rb +17 -0
- data/test/serialization/lazy_object_test.rb +32 -0
- data/test/serialization/message/metadata_test.rb +19 -0
- data/test/serialization/message/serialization_aware_message_test.rb +88 -0
- data/test/serialization/message/serialized_message_builder_test.rb +41 -0
- data/test/serialization/message/serialized_message_test.rb +140 -0
- data/test/serialization/message/serializer_test.rb +50 -0
- data/test/serialization/revision_resolver_test.rb +12 -0
- data/test/serialization/serialized_object_test.rb +36 -0
- data/test/serialization/serialized_type_test.rb +27 -0
- data/test/serialization/serializer/marshal_test.rb +22 -0
- data/test/serialization/serializer/oj_test.rb +24 -0
- data/test/serialization/serializer/ox_test.rb +36 -0
- data/test/serialization/serializer_test.rb +20 -0
- data/test/test_helper.rb +19 -0
- data/test/uow/factory_test.rb +23 -0
- data/test/uow/outer_commit_listener_test.rb +50 -0
- data/test/uow/provider_test.rb +70 -0
- data/test/uow/uow_test.rb +337 -0
- data/test/upcasting/chain_test.rb +29 -0
- data/test/upcasting/fixtures.rb +66 -0
- data/test/wiring/wire_registry_test.rb +60 -0
- data/test/wiring/wire_test.rb +51 -0
- 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
|