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