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,127 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Domain
|
|
3
|
+
# Container that tracks uncommitted events published by an aggregate root and its child entities
|
|
4
|
+
# @api private
|
|
5
|
+
class EventContainer
|
|
6
|
+
# @return [Object] The identifier of the aggregate being tracked
|
|
7
|
+
attr_reader :aggregate_id
|
|
8
|
+
|
|
9
|
+
# @return [Integer] The sequence number of the last committed event
|
|
10
|
+
attr_reader :last_committed_sequence_number
|
|
11
|
+
|
|
12
|
+
# Initializes this event container
|
|
13
|
+
#
|
|
14
|
+
# @param [Object] aggregate_id
|
|
15
|
+
# The identifier of the aggregate being tracked
|
|
16
|
+
#
|
|
17
|
+
# @return [undefined]
|
|
18
|
+
def initialize(aggregate_id)
|
|
19
|
+
@aggregate_id = aggregate_id
|
|
20
|
+
@events = Array.new
|
|
21
|
+
@listeners = Array.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers an event published by the aggregate to this container
|
|
25
|
+
#
|
|
26
|
+
# During this process, a domain event message is created. Event registration listeners can
|
|
27
|
+
# choose to modify or replace the message before it is committed.
|
|
28
|
+
#
|
|
29
|
+
# @param [Object] payload
|
|
30
|
+
# Payload of the message; the actual event object
|
|
31
|
+
#
|
|
32
|
+
# @param [Hash] metadata
|
|
33
|
+
# Metadata associated with the event
|
|
34
|
+
#
|
|
35
|
+
# @return [DomainEventMessage] The event that will be committed
|
|
36
|
+
def register_event(payload, metadata)
|
|
37
|
+
event = DomainEventMessage.build do |b|
|
|
38
|
+
b.aggregate_id = @aggregate_id
|
|
39
|
+
b.sequence_number = next_sequence_number
|
|
40
|
+
b.metadata = metadata
|
|
41
|
+
b.payload = payload
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@listeners.each do |listener|
|
|
45
|
+
event = listener.call event
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@last_sequence_number = event.sequence_number
|
|
49
|
+
@events.push event
|
|
50
|
+
|
|
51
|
+
event
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Adds an event registration listener to this container
|
|
55
|
+
#
|
|
56
|
+
# If the listener is added after events have already registered with the container, it will
|
|
57
|
+
# be called with a backlog of events to process.
|
|
58
|
+
#
|
|
59
|
+
# @param [#call] listener
|
|
60
|
+
# @return [undefined]
|
|
61
|
+
def add_registration_listener(listener)
|
|
62
|
+
@listeners.push listener
|
|
63
|
+
|
|
64
|
+
@events.map! do |event|
|
|
65
|
+
listener.call event
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Sets the last committed sequence number for the container
|
|
70
|
+
#
|
|
71
|
+
# @raise [RuntimeError] If events have already been registered to the container
|
|
72
|
+
# @param [Integer] last_known
|
|
73
|
+
# @return [undefined]
|
|
74
|
+
def initialize_sequence_number(last_known)
|
|
75
|
+
unless @events.empty?
|
|
76
|
+
raise 'Sequence number must be set before events are registered'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@last_committed_sequence_number = last_known
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the sequence number of the last event known by this container
|
|
83
|
+
# @return [Integer]
|
|
84
|
+
def last_sequence_number
|
|
85
|
+
if @events.empty?
|
|
86
|
+
return @last_committed_sequence_number
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
unless @last_sequence_number
|
|
90
|
+
@last_sequence_number = @events.last.sequence_number
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@last_sequence_number
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Updates the last committed sequence number and clears any uncommitted events and any
|
|
97
|
+
# event registration listeners
|
|
98
|
+
#
|
|
99
|
+
# @return [undefined]
|
|
100
|
+
def mark_committed
|
|
101
|
+
@last_committed_sequence_number = @last_sequence_number
|
|
102
|
+
@events.clear
|
|
103
|
+
@listeners.clear
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns an event stream containing the uncommitted events in this container
|
|
107
|
+
# @return [DomainEventStream]
|
|
108
|
+
def to_stream
|
|
109
|
+
SimpleDomainEventStream.new @events
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns the number of uncommitted events in this container
|
|
113
|
+
# @return [Integer]
|
|
114
|
+
def size
|
|
115
|
+
@events.size
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Returns the next sequence number to use for registered events
|
|
121
|
+
# @return [Integer]
|
|
122
|
+
def next_sequence_number
|
|
123
|
+
last_sequence_number ? last_sequence_number.next : 0
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Domain
|
|
3
|
+
# Represents the occurance of an event in the application that may be of some importance
|
|
4
|
+
# to another component of the application. It contains the relevant data for other
|
|
5
|
+
# components to act upon.
|
|
6
|
+
class EventMessage < Message
|
|
7
|
+
# The timestamp of when the event was reported
|
|
8
|
+
# @return [Time]
|
|
9
|
+
attr_reader :timestamp
|
|
10
|
+
|
|
11
|
+
# @param [String] id
|
|
12
|
+
# @param [Hash] metadata
|
|
13
|
+
# @param [Object] payload
|
|
14
|
+
# @param [Time] timestamp
|
|
15
|
+
# @return [undefined]
|
|
16
|
+
def initialize(id, metadata, payload, timestamp)
|
|
17
|
+
super id, metadata, payload
|
|
18
|
+
@timestamp = timestamp
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Class]
|
|
22
|
+
def self.builder
|
|
23
|
+
EventMessageBuilder
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
# @param [EventMessageBuilder] builder
|
|
29
|
+
# @param [Hash] metadata
|
|
30
|
+
# @return [undefined]
|
|
31
|
+
def build_duplicate(builder, metadata)
|
|
32
|
+
super
|
|
33
|
+
builder.timestamp = @timestamp
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Message that contains a domain event as a payload that represents a state change in the domain.
|
|
38
|
+
#
|
|
39
|
+
# In contrast to a regular event message, this type of message contains the identifier of the
|
|
40
|
+
# aggregate that reported it. It also contains a sequence number that allows the messages to be placed
|
|
41
|
+
# in the order they were reported.
|
|
42
|
+
class DomainEventMessage < EventMessage
|
|
43
|
+
# The identifier of the aggregate that reported the event
|
|
44
|
+
# @return [Object]
|
|
45
|
+
attr_reader :aggregate_id
|
|
46
|
+
|
|
47
|
+
# The sequence number of the event in the order of generation
|
|
48
|
+
# @return [Integer]
|
|
49
|
+
attr_reader :sequence_number
|
|
50
|
+
|
|
51
|
+
# @param [String] id
|
|
52
|
+
# @param [Hash] metadata
|
|
53
|
+
# @param [Object] payload
|
|
54
|
+
# @param [Time] timestamp
|
|
55
|
+
# @param [Object] aggregate_id
|
|
56
|
+
# @param [Integer] sequence_number
|
|
57
|
+
# @return [undefined]
|
|
58
|
+
def initialize(id, metadata, payload, timestamp, aggregate_id, sequence_number)
|
|
59
|
+
super id, metadata, payload, timestamp
|
|
60
|
+
|
|
61
|
+
@aggregate_id = aggregate_id
|
|
62
|
+
@sequence_number = sequence_number
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Class]
|
|
66
|
+
def self.builder
|
|
67
|
+
DomainEventMessageBuilder
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
protected
|
|
71
|
+
|
|
72
|
+
# @param [DomainEventMessageBuilder] builder
|
|
73
|
+
# @param [Hash] metadata
|
|
74
|
+
# @return [undefined]
|
|
75
|
+
def build_duplicate(builder, metadata)
|
|
76
|
+
super
|
|
77
|
+
builder.aggregate_id = @aggregate_id
|
|
78
|
+
builder.sequence_number = @sequence_number
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Domain
|
|
3
|
+
# Message builder capable of producing EventMessage instances
|
|
4
|
+
class EventMessageBuilder < MessageBuilder
|
|
5
|
+
# @return [Time]
|
|
6
|
+
attr_accessor :timestamp
|
|
7
|
+
|
|
8
|
+
# @return [EventMessage]
|
|
9
|
+
def build
|
|
10
|
+
EventMessage.new @id, @metadata, @payload, @timestamp
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [undefined]
|
|
14
|
+
def populate_defaults
|
|
15
|
+
super
|
|
16
|
+
@timestamp ||= Time.now
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Message builder capable of producing DomainEventMessage instances
|
|
21
|
+
class DomainEventMessageBuilder < EventMessageBuilder
|
|
22
|
+
# @return [Object]
|
|
23
|
+
attr_accessor :aggregate_id
|
|
24
|
+
|
|
25
|
+
# @return [Integer]
|
|
26
|
+
attr_accessor :sequence_number
|
|
27
|
+
|
|
28
|
+
# @return [DomainEventMessage]
|
|
29
|
+
def build
|
|
30
|
+
DomainEventMessage.new @id, @metadata, @payload, @timestamp, @aggregate_id, @sequence_number
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Domain
|
|
3
|
+
# Represents a historical stream of domain events in chronological order
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# stream = InMemoryDomainEventStream.new events
|
|
7
|
+
# until stream.end?
|
|
8
|
+
# puts stream.next_event
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# stream = InMemoryDomainEventStream.new events
|
|
13
|
+
# stream.each do |event|
|
|
14
|
+
# puts event
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @abstract
|
|
18
|
+
class DomainEventStream
|
|
19
|
+
# Returns true if the end of the stream has been reached
|
|
20
|
+
#
|
|
21
|
+
# @abstract
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def end?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the next event in the stream and moves the stream's pointer forward
|
|
28
|
+
#
|
|
29
|
+
# @abstract
|
|
30
|
+
# @return [DomainEventMessage]
|
|
31
|
+
def next_event; end
|
|
32
|
+
|
|
33
|
+
# Returns the next event in the stream without moving the stream's pointer forward
|
|
34
|
+
#
|
|
35
|
+
# @abstract
|
|
36
|
+
# @return [DomainEventMessage]
|
|
37
|
+
def peek; end
|
|
38
|
+
|
|
39
|
+
# Yields the next domain events in the stream until the end of the stream has been reached
|
|
40
|
+
#
|
|
41
|
+
# @yield [DomainEventMessage] The next event in the event stream
|
|
42
|
+
# @return [undefined]
|
|
43
|
+
def each
|
|
44
|
+
until end?
|
|
45
|
+
yield next_event
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns the domain events in this stream as an array
|
|
50
|
+
# @return [Array<DomainEventMessage>]
|
|
51
|
+
def to_a
|
|
52
|
+
events = Array.new
|
|
53
|
+
each do |event|
|
|
54
|
+
events.push event
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
events
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
protected
|
|
61
|
+
|
|
62
|
+
def assert_valid
|
|
63
|
+
if end?
|
|
64
|
+
raise EndOfStreamError
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Raised when the end of a domain event stream has been reached
|
|
70
|
+
class EndOfStreamError < NonTransientError; end
|
|
71
|
+
|
|
72
|
+
# Implementation of a domain event stream that holds a stream of events in memory
|
|
73
|
+
class SimpleDomainEventStream < DomainEventStream
|
|
74
|
+
def initialize(*events)
|
|
75
|
+
@events = events.flatten
|
|
76
|
+
@next_index = 0
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Returns true if the end of the stream has been reached
|
|
80
|
+
# @return [Boolean]
|
|
81
|
+
def end?
|
|
82
|
+
@next_index >= @events.size
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns the next event in the stream and moves the stream's pointer forward
|
|
86
|
+
#
|
|
87
|
+
# @raise [EndOfStreamError] If the end of the stream has been reached
|
|
88
|
+
# @return [DomainEventMessage]
|
|
89
|
+
def next_event
|
|
90
|
+
assert_valid
|
|
91
|
+
|
|
92
|
+
event = @events.at @next_index
|
|
93
|
+
@next_index += 1
|
|
94
|
+
|
|
95
|
+
event
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns the next event in the stream without moving the stream's pointer forward
|
|
99
|
+
#
|
|
100
|
+
# @raise [EndOfStreamError] If the end of the stream has been reached
|
|
101
|
+
# @return [DomainEventMessage]
|
|
102
|
+
def peek
|
|
103
|
+
assert_valid
|
|
104
|
+
@events.at @next_index
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
# Records messages as they are sent to a bus so that duplicates can be tracked and prevented.
|
|
3
|
+
# Inspired by the de-duplication manager from Lokad.CQRS
|
|
4
|
+
#
|
|
5
|
+
# This implementation is thread-safe
|
|
6
|
+
class DuplicationRecorder
|
|
7
|
+
def initialize
|
|
8
|
+
@recorded = Hash.new
|
|
9
|
+
@lock = Mutex.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Records the given message so that duplicates can be ignored
|
|
13
|
+
#
|
|
14
|
+
# @raise [DuplicationError] If a duplicate message has been detected
|
|
15
|
+
# @param [Message] message
|
|
16
|
+
# @return [undefined]
|
|
17
|
+
def record(message)
|
|
18
|
+
@lock.synchronize do
|
|
19
|
+
if @recorded.has_key? message.id
|
|
20
|
+
raise DuplicationError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@recorded.store message.id, Time.now
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns true if the given message has been recorded
|
|
28
|
+
#
|
|
29
|
+
# @param [Message] message
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def recorded?(message)
|
|
32
|
+
@recorded.has_key? message.id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Forgets the given message
|
|
36
|
+
#
|
|
37
|
+
# @param [Message] message
|
|
38
|
+
# @return [undefined]
|
|
39
|
+
def forget(message)
|
|
40
|
+
@lock.synchronize do
|
|
41
|
+
@recorded.delete message.id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Cleans up messages that are older than the given timestamp
|
|
46
|
+
#
|
|
47
|
+
# @param [Time] threshold
|
|
48
|
+
# @return [undefined]
|
|
49
|
+
def forget_older_than(threshold)
|
|
50
|
+
@lock.synchronize do
|
|
51
|
+
@recorded.delete_if do |message_id, timestamp|
|
|
52
|
+
timestamp <= threshold
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Raised when a duplicate message has been detected by the duplication recorder
|
|
59
|
+
class DuplicationError < NonTransientError; end
|
|
60
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
# Base exception for all Synapse framework-related errors
|
|
3
|
+
class SynapseError < RuntimeError; end
|
|
4
|
+
|
|
5
|
+
# Raised when an error has occured that resulted from misconfiguration
|
|
6
|
+
class ConfigurationError < SynapseError; end
|
|
7
|
+
|
|
8
|
+
# Raised when an error has occured that cannot be resolved with intervention
|
|
9
|
+
class NonTransientError < SynapseError; end
|
|
10
|
+
|
|
11
|
+
# Raised when an error has occured that might be resolved by retrying the operation
|
|
12
|
+
class TransientError < SynapseError; end
|
|
13
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventBus
|
|
3
|
+
# Represents a mechanism for event listeners to subscribe to events and for event publishers
|
|
4
|
+
# to dispatch their events to any interested parties.
|
|
5
|
+
#
|
|
6
|
+
# Implementations may or may not dispatch the events to listeners in the dispatching thread.
|
|
7
|
+
#
|
|
8
|
+
# @abstract
|
|
9
|
+
class EventBus
|
|
10
|
+
# Publishes one or more events to any listeners subscribed to this event bus
|
|
11
|
+
#
|
|
12
|
+
# Implementations may treat the given events as a single batch and distribute them as such
|
|
13
|
+
# to all subscribed event listeners.
|
|
14
|
+
#
|
|
15
|
+
# @abstract
|
|
16
|
+
# @param [EventMessage...] events
|
|
17
|
+
# @return [undefined]
|
|
18
|
+
def publish(*events); end
|
|
19
|
+
|
|
20
|
+
# Subscribes the given listener to this event bus
|
|
21
|
+
#
|
|
22
|
+
# @abstract
|
|
23
|
+
# @raise [SubscriptionFailedError] If subscription of an event listener failed
|
|
24
|
+
# @param [EventListener] listener
|
|
25
|
+
# @return [undefined]
|
|
26
|
+
def subscribe(listener); end
|
|
27
|
+
|
|
28
|
+
# Unsubscribes the given listener from this event bus
|
|
29
|
+
#
|
|
30
|
+
# @abstract
|
|
31
|
+
# @param [EventListener] listener
|
|
32
|
+
# @return [undefined]
|
|
33
|
+
def unsubscribe(listener); end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Raised when the subscription of an event listener has not succeeded. Generally, this means that
|
|
37
|
+
# some precondition set by an event bus implementation for the listener have not been met.
|
|
38
|
+
class SubscriptionFailedError < NonTransientError; end
|
|
39
|
+
end
|
|
40
|
+
end
|