synapse-core 0.5.6 → 0.6.0
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.
- checksums.yaml +7 -0
- data/lib/synapse-core.rb +31 -1
- data/lib/synapse/command.rb +1 -0
- data/lib/synapse/command/callbacks/future.rb +3 -3
- data/lib/synapse/command/callbacks/void.rb +14 -0
- data/lib/synapse/command/command_bus.rb +2 -2
- data/lib/synapse/command/command_callback.rb +9 -2
- data/lib/synapse/command/gateway.rb +1 -1
- data/lib/synapse/command/gateway/interval_retry_scheduler.rb +6 -6
- data/lib/synapse/command/gateway/retry_scheduler.rb +7 -4
- data/lib/synapse/command/interceptor_chain.rb +1 -1
- data/lib/synapse/command/interceptors/serialization.rb +2 -1
- data/lib/synapse/command/simple_command_bus.rb +23 -34
- data/lib/synapse/common.rb +2 -2
- data/lib/synapse/common/concurrency/disposable_lock.rb +157 -0
- data/lib/synapse/common/concurrency/identifier_lock_manager.rb +164 -0
- data/lib/synapse/common/duplication.rb +4 -4
- data/lib/synapse/common/errors.rb +5 -0
- data/lib/synapse/configuration/container.rb +1 -1
- data/lib/synapse/configuration/container_builder.rb +1 -1
- data/lib/synapse/configuration/definition.rb +1 -1
- data/lib/synapse/domain/aggregate_root.rb +1 -1
- data/lib/synapse/domain/simple_stream.rb +3 -5
- data/lib/synapse/domain/stream.rb +2 -2
- data/lib/synapse/event_bus/event_bus.rb +2 -2
- data/lib/synapse/event_bus/simple_event_bus.rb +5 -6
- data/lib/synapse/event_sourcing/caching.rb +1 -1
- data/lib/synapse/event_sourcing/entity.rb +4 -2
- data/lib/synapse/event_sourcing/repository.rb +9 -13
- data/lib/synapse/event_sourcing/snapshot/taker.rb +1 -1
- data/lib/synapse/mapping/mapping.rb +1 -1
- data/lib/synapse/process_manager/correlation.rb +3 -29
- data/lib/synapse/process_manager/pessimistic_lock_manager.rb +3 -3
- data/lib/synapse/process_manager/process.rb +2 -6
- data/lib/synapse/process_manager/process_manager.rb +1 -1
- data/lib/synapse/process_manager/process_repository.rb +1 -1
- data/lib/synapse/process_manager/repository/in_memory.rb +5 -4
- data/lib/synapse/process_manager/simple_process_manager.rb +2 -2
- data/lib/synapse/repository/errors.rb +2 -2
- data/lib/synapse/repository/locking.rb +48 -0
- data/lib/synapse/repository/optimistic_lock_manager.rb +10 -9
- data/lib/synapse/repository/pessimistic_lock_manager.rb +5 -4
- data/lib/synapse/repository/repository.rb +1 -1
- data/lib/synapse/repository/simple_repository.rb +8 -7
- data/lib/synapse/serialization/converter.rb +3 -0
- data/lib/synapse/serialization/converter_factory.rb +6 -5
- data/lib/synapse/serialization/serialized_object.rb +4 -4
- data/lib/synapse/serialization/serialized_type.rb +4 -4
- data/lib/synapse/uow/listener_collection.rb +12 -9
- data/lib/synapse/uow/provider.rb +1 -1
- data/lib/synapse/uow/uow.rb +1 -1
- data/lib/synapse/upcasting/upcaster_chain.rb +1 -1
- data/lib/synapse/version.rb +1 -1
- data/test/command/serialization_test.rb +5 -2
- data/test/command/simple_command_bus_test.rb +9 -16
- data/test/command/validation_test.rb +1 -1
- data/test/common/concurrency/identifier_lock_manager_test.rb +137 -0
- data/test/configuration/component/serialization/converter_factory_test.rb +2 -2
- data/test/event_sourcing/repository_test.rb +18 -0
- data/test/repository/simple_repository_test.rb +42 -10
- data/test/test_helper.rb +3 -4
- metadata +25 -29
- data/lib/synapse.rb +0 -34
- data/lib/synapse/common/concurrency/identifier_lock.rb +0 -56
- data/lib/synapse/common/concurrency/public_lock.rb +0 -95
- data/lib/synapse/event_bus/clustering/cluster.rb +0 -10
- data/lib/synapse/event_bus/clustering/event_bus.rb +0 -55
- data/lib/synapse/event_bus/clustering/selector.rb +0 -14
- data/lib/synapse/rails/injection_helper.rb +0 -23
- data/lib/synapse/railtie.rb +0 -17
- data/test/common/concurrency/identifier_lock_test.rb +0 -25
- data/test/common/concurrency/public_lock_test.rb +0 -83
- data/test/process_manager/correlation_test.rb +0 -24
- data/test/rails/injection_helper_test.rb +0 -27
@@ -0,0 +1,164 @@
|
|
1
|
+
module Synapse
|
2
|
+
# Generic lock manager that can be used to lock identifiers for exclusive access
|
3
|
+
class IdentifierLockManager
|
4
|
+
@mutex = Mutex.new
|
5
|
+
@instances = Ref::WeakKeyMap.new
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @api private
|
9
|
+
# @return [Array]
|
10
|
+
def instances
|
11
|
+
@mutex.synchronize do
|
12
|
+
@instances.keys
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
# @param [IdentifierLockManager] instance
|
18
|
+
# @return [undefined]
|
19
|
+
def add(instance)
|
20
|
+
@mutex.synchronize do
|
21
|
+
@instances[instance] = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# @param [Thread] thread
|
27
|
+
# @return [Set]
|
28
|
+
def waiters_for_locks_owned_by(thread)
|
29
|
+
stack = Array.new
|
30
|
+
waiters = Set.new
|
31
|
+
|
32
|
+
find_waiters thread, instances, waiters, stack
|
33
|
+
|
34
|
+
waiters
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @param [Thread] thread
|
40
|
+
# @param [Array] managers
|
41
|
+
# @param [Set] waiters
|
42
|
+
# @param [Array] stack
|
43
|
+
# @return [undefined]
|
44
|
+
def find_waiters(thread, managers, waiters, stack)
|
45
|
+
stack.push thread
|
46
|
+
|
47
|
+
for manager in managers
|
48
|
+
for lock in manager.internal_locks
|
49
|
+
next unless lock.owned_by? thread
|
50
|
+
|
51
|
+
for waiter in lock.waiters
|
52
|
+
# Avoid infinite recursion in the case of an imminent deadlock
|
53
|
+
next if stack.include? waiter
|
54
|
+
|
55
|
+
# Skip waiters that are already known
|
56
|
+
next unless waiters.add? waiter
|
57
|
+
|
58
|
+
# Recursively find waiters for locks
|
59
|
+
find_waiters waiter, managers, waiters, stack
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
stack.pop
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [undefined]
|
69
|
+
def initialize
|
70
|
+
@locks = Hash.new
|
71
|
+
@mutex = Mutex.new
|
72
|
+
|
73
|
+
IdentifierLockManager.add self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true if the calling thread holds the lock for the given identifier
|
77
|
+
#
|
78
|
+
# @param [Object] identifier
|
79
|
+
# @return [Boolean]
|
80
|
+
def owned?(identifier)
|
81
|
+
lock_available?(identifier) && lock_for(identifier).owned?
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Obtains a lock for the given identifier, blocking until the lock is obtained
|
86
|
+
#
|
87
|
+
# @param [Object] identifier
|
88
|
+
# @return [undefined]
|
89
|
+
def obtain_lock(identifier)
|
90
|
+
loop do
|
91
|
+
lock = lock_for identifier
|
92
|
+
|
93
|
+
return if lock.lock
|
94
|
+
remove_lock identifier, lock
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Releases a lock for the given identifier
|
99
|
+
#
|
100
|
+
# @raise [RuntimeError] If no lock was ever obtained for the identifier
|
101
|
+
# @param [Object] identifier
|
102
|
+
# @return [undefined]
|
103
|
+
def release_lock(identifier)
|
104
|
+
unless lock_available? identifier
|
105
|
+
raise RuntimeError
|
106
|
+
end
|
107
|
+
|
108
|
+
lock = lock_for identifier
|
109
|
+
lock.unlock
|
110
|
+
|
111
|
+
try_dispose identifier, lock
|
112
|
+
end
|
113
|
+
|
114
|
+
# @api private
|
115
|
+
# @return [Array]
|
116
|
+
def internal_locks
|
117
|
+
@mutex.synchronize do
|
118
|
+
@locks.values
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# @param [Object] identifier
|
125
|
+
# @param [DisposableLock] lock
|
126
|
+
# @return [undefined]
|
127
|
+
def try_dispose(identifier, lock)
|
128
|
+
if lock.try_close
|
129
|
+
remove_lock identifier, lock
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# @param [Object] identifier
|
134
|
+
# @param [DisposableLock] lock
|
135
|
+
# @return [undefined]
|
136
|
+
def remove_lock(identifier, lock)
|
137
|
+
@mutex.synchronize do
|
138
|
+
@locks.delete_if do |i, l|
|
139
|
+
i == identifier && l == lock
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [Object] identifier
|
145
|
+
# @return [DisposableLock]
|
146
|
+
def lock_for(identifier)
|
147
|
+
@mutex.synchronize do
|
148
|
+
if @locks.has_key? identifier
|
149
|
+
@locks.fetch identifier
|
150
|
+
else
|
151
|
+
@locks.store identifier, DisposableLock.new
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# @param [Object] identifier
|
157
|
+
# @return [Boolean]
|
158
|
+
def lock_available?(identifier)
|
159
|
+
@mutex.synchronize do
|
160
|
+
@locks.has_key? identifier
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end # IdentifierLockManager
|
164
|
+
end
|
@@ -6,7 +6,7 @@ module Synapse
|
|
6
6
|
class DuplicationRecorder
|
7
7
|
def initialize
|
8
8
|
@recorded = Hash.new
|
9
|
-
@
|
9
|
+
@mutex = Mutex.new
|
10
10
|
end
|
11
11
|
|
12
12
|
# Records the given message so that duplicates can be ignored
|
@@ -15,7 +15,7 @@ module Synapse
|
|
15
15
|
# @param [Message] message
|
16
16
|
# @return [undefined]
|
17
17
|
def record(message)
|
18
|
-
@
|
18
|
+
@mutex.synchronize do
|
19
19
|
if @recorded.has_key? message.id
|
20
20
|
raise DuplicationError
|
21
21
|
end
|
@@ -37,7 +37,7 @@ module Synapse
|
|
37
37
|
# @param [Message] message
|
38
38
|
# @return [undefined]
|
39
39
|
def forget(message)
|
40
|
-
@
|
40
|
+
@mutex.synchronize do
|
41
41
|
@recorded.delete message.id
|
42
42
|
end
|
43
43
|
end
|
@@ -47,7 +47,7 @@ module Synapse
|
|
47
47
|
# @param [Time] threshold
|
48
48
|
# @return [undefined]
|
49
49
|
def forget_older_than(threshold)
|
50
|
-
@
|
50
|
+
@mutex.synchronize do
|
51
51
|
@recorded.delete_if do |message_id, timestamp|
|
52
52
|
timestamp <= threshold
|
53
53
|
end
|
@@ -10,4 +10,9 @@ module Synapse
|
|
10
10
|
|
11
11
|
# Raised when an error has occured that might be resolved by retrying the operation
|
12
12
|
class TransientError < SynapseError; end
|
13
|
+
|
14
|
+
# Raised when an imminent deadlock is detected
|
15
|
+
#
|
16
|
+
# It is typically safe to retry an operation if it resulted in this exception
|
17
|
+
class DeadlockError < TransientError; end
|
13
18
|
end
|
@@ -4,7 +4,7 @@ module Synapse
|
|
4
4
|
# @see Synapse#build
|
5
5
|
class ContainerBuilder
|
6
6
|
# Registered initializers that will be executed upon instantiation
|
7
|
-
# @return [Array]
|
7
|
+
# @return [Array<Proc>]
|
8
8
|
class_attribute :initializers
|
9
9
|
|
10
10
|
# Registers a block that will be executed upon instantiation of a container builder
|
@@ -3,7 +3,7 @@ module Synapse
|
|
3
3
|
# Represents a definition for a service being provided by the container
|
4
4
|
# @see DefinitionBuilder
|
5
5
|
class Definition
|
6
|
-
# @return [Set] Symbols that this definition is tagged with
|
6
|
+
# @return [Set<Symbol>] Symbols that this definition is tagged with
|
7
7
|
attr_reader :tags
|
8
8
|
|
9
9
|
# @param [Set] tags
|
@@ -9,14 +9,13 @@ module Synapse
|
|
9
9
|
@next_index = 0
|
10
10
|
end
|
11
11
|
|
12
|
-
#
|
12
|
+
# @api public
|
13
13
|
# @return [Boolean]
|
14
14
|
def end?
|
15
15
|
@next_index >= @events.size
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# @api public
|
20
19
|
# @raise [EndOfStreamError] If the end of the stream has been reached
|
21
20
|
# @return [DomainEventMessage]
|
22
21
|
def next_event
|
@@ -28,8 +27,7 @@ module Synapse
|
|
28
27
|
event
|
29
28
|
end
|
30
29
|
|
31
|
-
#
|
32
|
-
#
|
30
|
+
# @api public
|
33
31
|
# @raise [EndOfStreamError] If the end of the stream has been reached
|
34
32
|
# @return [DomainEventMessage]
|
35
33
|
def peek
|
@@ -3,13 +3,13 @@ module Synapse
|
|
3
3
|
# Represents a historical stream of domain events in chronological order
|
4
4
|
#
|
5
5
|
# @example
|
6
|
-
# stream =
|
6
|
+
# stream = SimpleDomainEventStream.new events
|
7
7
|
# until stream.end?
|
8
8
|
# puts stream.next_event
|
9
9
|
# end
|
10
10
|
#
|
11
11
|
# @example
|
12
|
-
# stream =
|
12
|
+
# stream = SimpleDomainEventStream.new events
|
13
13
|
# stream.each do |event|
|
14
14
|
# puts event
|
15
15
|
# end
|
@@ -22,7 +22,7 @@ module Synapse
|
|
22
22
|
# Subscribes the given listener to this event bus
|
23
23
|
#
|
24
24
|
# @abstract
|
25
|
-
# @raise [
|
25
|
+
# @raise [SubscriptionError] If subscription of an event listener failed
|
26
26
|
# @param [EventListener] listener
|
27
27
|
# @return [undefined]
|
28
28
|
def subscribe(listener)
|
@@ -41,6 +41,6 @@ module Synapse
|
|
41
41
|
|
42
42
|
# Raised when the subscription of an event listener has not succeeded. Generally, this means that
|
43
43
|
# some precondition set by an event bus implementation for the listener have not been met.
|
44
|
-
class
|
44
|
+
class SubscriptionError < NonTransientError; end
|
45
45
|
end
|
46
46
|
end
|
@@ -17,8 +17,7 @@ module Synapse
|
|
17
17
|
events.flatten!
|
18
18
|
events.each do |event|
|
19
19
|
@listeners.each do |listener|
|
20
|
-
@logger.debug
|
21
|
-
[event.payload_type, listener.class]
|
20
|
+
@logger.debug "Publishing event {#{event.payload_type}} to {#{listener.class}}"
|
22
21
|
|
23
22
|
listener.notify event
|
24
23
|
end
|
@@ -39,9 +38,9 @@ module Synapse
|
|
39
38
|
# @return [undefined]
|
40
39
|
def subscribe(listener)
|
41
40
|
if @listeners.add? listener
|
42
|
-
@logger.debug
|
41
|
+
@logger.debug "Event listener {#{listener.class}} subscribed"
|
43
42
|
else
|
44
|
-
@logger.info
|
43
|
+
@logger.info "Event listener {#{listener.class}} is already subscribed"
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
@@ -50,9 +49,9 @@ module Synapse
|
|
50
49
|
# @return [undefined]
|
51
50
|
def unsubscribe(listener)
|
52
51
|
if @listeners.delete? listener
|
53
|
-
@logger.debug
|
52
|
+
@logger.debug "Event listener {#{listener.class}} unsubscribed"
|
54
53
|
else
|
55
|
-
@logger.info
|
54
|
+
@logger.info "Event listener {#{listener.class}} is not subscribed"
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end # SimpleEventBus
|
@@ -25,8 +25,10 @@ module Synapse
|
|
25
25
|
# @param [AggregateRoot] aggregate_root
|
26
26
|
# @return [undefined]
|
27
27
|
def aggregate_root=(aggregate_root)
|
28
|
-
if @aggregate_root
|
29
|
-
|
28
|
+
if @aggregate_root
|
29
|
+
unless @aggregate_root === aggregate_root
|
30
|
+
raise 'Entity is registered to a different aggregate root'
|
31
|
+
end
|
30
32
|
end
|
31
33
|
|
32
34
|
@aggregate_root = aggregate_root
|
@@ -65,7 +65,7 @@ module Synapse
|
|
65
65
|
raise AggregateDeletedError.new type_identifier, aggregate_id
|
66
66
|
end
|
67
67
|
|
68
|
-
if expected_version
|
68
|
+
if expected_version && @conflict_resolver.nil?
|
69
69
|
assert_version_expected aggregate, expected_version
|
70
70
|
end
|
71
71
|
|
@@ -75,7 +75,7 @@ module Synapse
|
|
75
75
|
# @param [AggregateRoot] aggregate
|
76
76
|
# @return [undefined]
|
77
77
|
def post_registration(aggregate)
|
78
|
-
if @snapshot_policy
|
78
|
+
if @snapshot_policy && @snapshot_taker
|
79
79
|
listener =
|
80
80
|
SnapshotUnitOfWorkListener.new type_identifier, aggregate, @snapshot_policy, @snapshot_taker
|
81
81
|
|
@@ -85,17 +85,13 @@ module Synapse
|
|
85
85
|
|
86
86
|
# @param [AggregateRoot] aggregate
|
87
87
|
# @return [undefined]
|
88
|
-
def
|
89
|
-
|
88
|
+
def delete_aggregate_with_lock(aggregate)
|
89
|
+
save_aggregate_with_lock aggregate
|
90
90
|
end
|
91
91
|
|
92
92
|
# @param [AggregateRoot] aggregate
|
93
93
|
# @return [undefined]
|
94
|
-
def
|
95
|
-
if aggregate.version and !@lock_manager.validate_lock aggregate
|
96
|
-
raise Repository::ConflictingModificationError
|
97
|
-
end
|
98
|
-
|
94
|
+
def save_aggregate_with_lock(aggregate)
|
99
95
|
stream = aggregate.uncommitted_events
|
100
96
|
@stream_decorators.reverse_each do |decorator|
|
101
97
|
stream = decorator.decorate_for_append type_identifier, aggregate, stream
|
@@ -122,7 +118,7 @@ module Synapse
|
|
122
118
|
# @param [Integer] expected_version
|
123
119
|
# @return [DomainEventStream]
|
124
120
|
def add_conflict_resolution(stream, aggregate, expected_version)
|
125
|
-
unless expected_version
|
121
|
+
unless expected_version && @conflict_resolver
|
126
122
|
return stream
|
127
123
|
end
|
128
124
|
|
@@ -135,7 +131,7 @@ module Synapse
|
|
135
131
|
|
136
132
|
stream
|
137
133
|
end
|
138
|
-
end
|
134
|
+
end # EventSourcingRepository
|
139
135
|
|
140
136
|
# Raised when an aggregate has been found but it was marked for deletion
|
141
137
|
class AggregateDeletedError < Repository::AggregateNotFoundError
|
@@ -145,6 +141,6 @@ module Synapse
|
|
145
141
|
def initialize(type_identifier, aggregate_id)
|
146
142
|
super 'Aggregate marked for deletion [%s] [%s]' % [type_identifier, aggregate_id]
|
147
143
|
end
|
148
|
-
end
|
149
|
-
end
|
144
|
+
end # AggregateDeletedError
|
145
|
+
end # EventSourcing
|
150
146
|
end
|
@@ -30,7 +30,7 @@ module Synapse
|
|
30
30
|
first_sequence_number = stream.peek.sequence_number
|
31
31
|
snapshot = create_snapshot type_identifier, aggregate_id, stream
|
32
32
|
|
33
|
-
if snapshot
|
33
|
+
if snapshot && snapshot.sequence_number > first_sequence_number
|
34
34
|
@event_store.append_snapshot_event type_identifier, snapshot
|
35
35
|
end
|
36
36
|
end
|