synapse-core 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|