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
@@ -1,32 +1,6 @@
|
|
1
1
|
module Synapse
|
2
2
|
module ProcessManager
|
3
|
-
# Combination key and value that is used to correlate incoming events with process
|
4
|
-
|
5
|
-
|
6
|
-
attr_reader :key
|
7
|
-
|
8
|
-
# @return [String]
|
9
|
-
attr_reader :value
|
10
|
-
|
11
|
-
# @param [Symbol] key
|
12
|
-
# @param [String] value
|
13
|
-
# @return [undefined]
|
14
|
-
def initialize(key, value)
|
15
|
-
@key = key.to_sym
|
16
|
-
@value = value.to_s
|
17
|
-
end
|
18
|
-
|
19
|
-
def ==(other)
|
20
|
-
self.class === other and
|
21
|
-
other.key == @key and
|
22
|
-
other.value == @value
|
23
|
-
end
|
24
|
-
|
25
|
-
alias eql? ==
|
26
|
-
|
27
|
-
def hash
|
28
|
-
@key.hash ^ @value.hash
|
29
|
-
end
|
30
|
-
end # Correlation
|
31
|
-
end # ProcessManager
|
3
|
+
# Combination key and value that is used to correlate incoming events with process instance
|
4
|
+
Correlation = Struct.new :key, :value
|
5
|
+
end
|
32
6
|
end
|
@@ -3,20 +3,20 @@ module Synapse
|
|
3
3
|
# Lock manager that blocks until a lock can be obtained for a process
|
4
4
|
class PessimisticLockManager
|
5
5
|
def initialize
|
6
|
-
@
|
6
|
+
@manager = IdentifierLockManager.new
|
7
7
|
end
|
8
8
|
|
9
9
|
# @param [String] process_id
|
10
10
|
# @return [undefined]
|
11
11
|
def obtain_lock(process_id)
|
12
|
-
@
|
12
|
+
@manager.obtain_lock process_id
|
13
13
|
end
|
14
14
|
|
15
15
|
# @raise [ThreadError] If thread didn't previously hold the lock
|
16
16
|
# @param [String] process_id
|
17
17
|
# @return [undefined]
|
18
18
|
def release_lock(process_id)
|
19
|
-
@
|
19
|
+
@manager.release_lock process_id
|
20
20
|
end
|
21
21
|
end # PessimisticLockManager
|
22
22
|
end # ProcessManager
|
@@ -19,16 +19,12 @@ module Synapse
|
|
19
19
|
# @return [Boolean] True if this process is active
|
20
20
|
attr_reader :active
|
21
21
|
|
22
|
-
|
22
|
+
alias_method :active?, :active
|
23
23
|
|
24
24
|
# @param [String] id
|
25
25
|
# @return [undefined]
|
26
26
|
def initialize(id = nil)
|
27
|
-
|
28
|
-
id = IdentifierFactory.instance.generate
|
29
|
-
end
|
30
|
-
|
31
|
-
@id = id
|
27
|
+
@id = id ||= IdentifierFactory.instance.generate
|
32
28
|
@correlations = CorrelationSet.new
|
33
29
|
@active = true
|
34
30
|
|
@@ -117,7 +117,7 @@ module Synapse
|
|
117
117
|
def notify_current_process(process_id, event, correlation)
|
118
118
|
process = @repository.load process_id
|
119
119
|
|
120
|
-
unless process
|
120
|
+
unless process && process.active && process.correlations.include?(correlation)
|
121
121
|
# Process has changed or was deleted between the time of the selection query and the
|
122
122
|
# actual loading and locking of the process
|
123
123
|
return
|
@@ -2,16 +2,17 @@ module Synapse
|
|
2
2
|
module ProcessManager
|
3
3
|
# Process repository that stores all processes in memory
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# While the storage of processes are thread-safe, the processes themselves may not be. Use a
|
6
|
+
# lock manager if the processes are not thread-safe.
|
6
7
|
class InMemoryProcessRepository < ProcessRepository
|
7
8
|
def initialize
|
8
9
|
@managed_processes = Hash.new
|
9
|
-
@
|
10
|
+
@mutex = Mutex.new
|
10
11
|
end
|
11
12
|
|
12
13
|
# @param [Class] type
|
13
14
|
# @param [Correlation] correlation
|
14
|
-
# @return [Set]
|
15
|
+
# @return [Set<String>]
|
15
16
|
def find(type, correlation)
|
16
17
|
matching = Array.new
|
17
18
|
|
@@ -35,7 +36,7 @@ module Synapse
|
|
35
36
|
# @param [Process] process
|
36
37
|
# @return [undefined]
|
37
38
|
def commit(process)
|
38
|
-
@
|
39
|
+
@mutex.synchronize do
|
39
40
|
if process.active?
|
40
41
|
@managed_processes.store process.id, process
|
41
42
|
else
|
@@ -2,10 +2,10 @@ module Synapse
|
|
2
2
|
module ProcessManager
|
3
3
|
# Simple implementation of a process manager
|
4
4
|
class SimpleProcessManager < ProcessManager
|
5
|
-
# @return [Array] Types of events that will always result in the creation of a process
|
5
|
+
# @return [Array<Class>] Types of events that will always result in the creation of a process
|
6
6
|
attr_accessor :always_create_events
|
7
7
|
|
8
|
-
# @return [Array] Types of events that will result in the creation of a process if one
|
8
|
+
# @return [Array<Class>] Types of events that will result in the creation of a process if one
|
9
9
|
# doesn't already exist
|
10
10
|
attr_accessor :optionally_create_events
|
11
11
|
|
@@ -74,11 +74,59 @@ module Synapse
|
|
74
74
|
raise NotImplementedError
|
75
75
|
end
|
76
76
|
|
77
|
+
# Deletes the given aggregate from the underlying storage mechanism, ensuring that the lock
|
78
|
+
# for the aggregate is valid before doing so
|
79
|
+
#
|
80
|
+
# @abstract
|
81
|
+
# @param [AggregateRoot] aggregate
|
82
|
+
# @return [undefined]
|
83
|
+
def delete_aggregate_with_lock(aggregate)
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
# Saves the given aggregate using the underlying storage mechanism, ensuring that the lock
|
88
|
+
# for the aggregate is valid before doing so
|
89
|
+
#
|
90
|
+
# @abstract
|
91
|
+
# @param [AggregateRoot] aggregate
|
92
|
+
# @return [undefined]
|
93
|
+
def save_aggregate_with_lock(aggregate)
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
96
|
+
|
77
97
|
# Hook that is called after an aggregate is registered to the current unit of work
|
78
98
|
#
|
79
99
|
# @param [AggregateRoot] aggregate
|
80
100
|
# @return [undefined]
|
81
101
|
def post_registration(aggregate); end
|
102
|
+
|
103
|
+
# @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
|
104
|
+
# the lock manager
|
105
|
+
# @param [AggregateRoot] aggregate
|
106
|
+
# @return [undefined]
|
107
|
+
def delete_aggregate(aggregate)
|
108
|
+
assert_valid_lock aggregate
|
109
|
+
delete_aggregate_with_lock aggregate
|
110
|
+
end
|
111
|
+
|
112
|
+
# @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
|
113
|
+
# the lock manager
|
114
|
+
# @param [AggregateRoot] aggregate
|
115
|
+
# @return [undefined]
|
116
|
+
def save_aggregate(aggregate)
|
117
|
+
assert_valid_lock aggregate
|
118
|
+
save_aggregate_with_lock aggregate
|
119
|
+
end
|
120
|
+
|
121
|
+
# @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
|
122
|
+
# the lock manager
|
123
|
+
# @param [AggregateRoot] aggregate
|
124
|
+
# @return [undefined]
|
125
|
+
def assert_valid_lock(aggregate)
|
126
|
+
if aggregate.version && !@lock_manager.validate_lock(aggregate)
|
127
|
+
raise ConcurrencyError
|
128
|
+
end
|
129
|
+
end
|
82
130
|
end # LockingRepository
|
83
131
|
|
84
132
|
# Unit of work listener that releases the lock on an aggregate when the unit of work
|
@@ -5,15 +5,16 @@ module Synapse
|
|
5
5
|
# This implementation uses the sequence number of an aggregate's last committed event to
|
6
6
|
# detect concurrenct access.
|
7
7
|
class OptimisticLockManager < LockManager
|
8
|
+
# @return [undefined]
|
8
9
|
def initialize
|
9
10
|
@aggregates = Hash.new
|
10
|
-
@
|
11
|
+
@mutex = Mutex.new
|
11
12
|
end
|
12
13
|
|
13
14
|
# @param [AggregateRoot] aggregate
|
14
15
|
# @return [Boolean]
|
15
16
|
def validate_lock(aggregate)
|
16
|
-
@aggregates.has_key?
|
17
|
+
@aggregates.has_key?(aggregate.id) && @aggregates[aggregate.id].validate(aggregate)
|
17
18
|
end
|
18
19
|
|
19
20
|
# @param [Object] aggregate_id
|
@@ -22,7 +23,7 @@ module Synapse
|
|
22
23
|
obtained = false
|
23
24
|
until obtained
|
24
25
|
lock = lock_for aggregate_id
|
25
|
-
obtained = lock
|
26
|
+
obtained = lock && lock.lock
|
26
27
|
unless obtained
|
27
28
|
remove_lock aggregate_id, lock
|
28
29
|
end
|
@@ -47,8 +48,8 @@ module Synapse
|
|
47
48
|
# @param [OptimisticLock] lock
|
48
49
|
# @return [undefined]
|
49
50
|
def remove_lock(aggregate_id, lock)
|
50
|
-
@
|
51
|
-
if @aggregates.has_key?
|
51
|
+
@mutex.synchronize do
|
52
|
+
if @aggregates.has_key?(aggregate_id) && @aggregates[aggregate_id] === lock
|
52
53
|
@aggregates.delete aggregate_id
|
53
54
|
end
|
54
55
|
end
|
@@ -57,7 +58,7 @@ module Synapse
|
|
57
58
|
# @param [Object] aggregate_id
|
58
59
|
# @return [OptimisticLock]
|
59
60
|
def lock_for(aggregate_id)
|
60
|
-
@
|
61
|
+
@mutex.synchronize do
|
61
62
|
if @aggregates.has_key? aggregate_id
|
62
63
|
@aggregates[aggregate_id]
|
63
64
|
else
|
@@ -73,7 +74,7 @@ module Synapse
|
|
73
74
|
# @return [Boolean] True if this lock can be disposed
|
74
75
|
attr_reader :closed
|
75
76
|
|
76
|
-
|
77
|
+
alias_method :closed?, :closed
|
77
78
|
|
78
79
|
# @return [Hash] Hash of threads to the number of times they hold the lock
|
79
80
|
attr_reader :threads
|
@@ -87,8 +88,8 @@ module Synapse
|
|
87
88
|
# @return [Boolean]
|
88
89
|
def validate(aggregate)
|
89
90
|
last_committed = aggregate.version
|
90
|
-
if @version.nil?
|
91
|
-
@version = (last_committed
|
91
|
+
if @version.nil? || @version == last_committed
|
92
|
+
@version = (last_committed || 0) + aggregate.uncommitted_event_count
|
92
93
|
true
|
93
94
|
else
|
94
95
|
false
|
@@ -2,26 +2,27 @@ module Synapse
|
|
2
2
|
module Repository
|
3
3
|
# Implementation of a lock manager that blocks until a lock can be obtained
|
4
4
|
class PessimisticLockManager < LockManager
|
5
|
+
# @return [undefined]
|
5
6
|
def initialize
|
6
|
-
@
|
7
|
+
@manager = IdentifierLockManager.new
|
7
8
|
end
|
8
9
|
|
9
10
|
# @param [AggregateRoot] aggregate
|
10
11
|
# @return [Boolean]
|
11
12
|
def validate_lock(aggregate)
|
12
|
-
@
|
13
|
+
@manager.owned? aggregate.id
|
13
14
|
end
|
14
15
|
|
15
16
|
# @param [Object] aggregate_id
|
16
17
|
# @return [undefined]
|
17
18
|
def obtain_lock(aggregate_id)
|
18
|
-
@
|
19
|
+
@manager.obtain_lock aggregate_id
|
19
20
|
end
|
20
21
|
|
21
22
|
# @param [Object] aggregate_id
|
22
23
|
# @return [undefined]
|
23
24
|
def release_lock(aggregate_id)
|
24
|
-
@
|
25
|
+
@manager.release_lock aggregate_id
|
25
26
|
end
|
26
27
|
end # PessimisticLockManager
|
27
28
|
end # Repository
|
@@ -97,7 +97,7 @@ module Synapse
|
|
97
97
|
# @param [Integer] expected_version
|
98
98
|
# @return [undefined]
|
99
99
|
def assert_version_expected(aggregate, expected_version)
|
100
|
-
if expected_version
|
100
|
+
if expected_version && aggregate.version && aggregate.version > expected_version
|
101
101
|
raise ConflictingAggregateVersionError.new aggregate, expected_version
|
102
102
|
end
|
103
103
|
end
|
@@ -30,13 +30,14 @@ module Synapse
|
|
30
30
|
# Most ORMs that I can think of use #find like this -- no need for orm_adapter or anything
|
31
31
|
# crazy like that
|
32
32
|
aggregate = @aggregate_type.find aggregate_id
|
33
|
-
aggregate.tap do
|
34
|
-
unless aggregate
|
35
|
-
raise AggregateNotFoundError
|
36
|
-
end
|
37
33
|
|
38
|
-
|
34
|
+
unless aggregate
|
35
|
+
raise AggregateNotFoundError
|
39
36
|
end
|
37
|
+
|
38
|
+
assert_version_expected aggregate, expected_version
|
39
|
+
|
40
|
+
aggregate
|
40
41
|
end
|
41
42
|
|
42
43
|
# @return [Class]
|
@@ -46,13 +47,13 @@ module Synapse
|
|
46
47
|
|
47
48
|
# @param [AggregateRoot] aggregate
|
48
49
|
# @return [undefined]
|
49
|
-
def
|
50
|
+
def delete_aggregate_with_lock(aggregate)
|
50
51
|
aggregate.destroy
|
51
52
|
end
|
52
53
|
|
53
54
|
# @param [AggregateRoot] aggregate
|
54
55
|
# @return [undefined]
|
55
|
-
def
|
56
|
+
def save_aggregate_with_lock(aggregate)
|
56
57
|
aggregate.save
|
57
58
|
end
|
58
59
|
end # SimpleRepository
|
@@ -3,10 +3,12 @@ module Synapse
|
|
3
3
|
# Represents a mechanism for storing and retrieving converters capable of converting content
|
4
4
|
# of one type to another type, for the purpose of serialization and upcasting.
|
5
5
|
class ConverterFactory
|
6
|
+
# @return [Set<Converter>]
|
6
7
|
attr_reader :converters
|
7
8
|
|
9
|
+
# @return [undefined]
|
8
10
|
def initialize
|
9
|
-
@converters =
|
11
|
+
@converters = Set.new
|
10
12
|
end
|
11
13
|
|
12
14
|
# Adds the given converter to this converter factory
|
@@ -14,7 +16,7 @@ module Synapse
|
|
14
16
|
# @param [Converter] converter
|
15
17
|
# @return [undefined]
|
16
18
|
def register(converter)
|
17
|
-
@converters.
|
19
|
+
@converters.add converter
|
18
20
|
end
|
19
21
|
|
20
22
|
# Convenience method for converting a given serialized object to the given target type
|
@@ -40,9 +42,8 @@ module Synapse
|
|
40
42
|
end
|
41
43
|
|
42
44
|
@converters.each do |converter|
|
43
|
-
if converter.source_type == source_type &&
|
44
|
-
|
45
|
-
end
|
45
|
+
return converter if converter.source_type == source_type &&
|
46
|
+
converter.target_type == target_type
|
46
47
|
end
|
47
48
|
|
48
49
|
raise ConversionError, 'No converter capable of [%s] -> [%s]' % [source_type, target_type]
|
@@ -21,13 +21,13 @@ module Synapse
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def ==(other)
|
24
|
-
self.class === other
|
25
|
-
other.content == @content
|
26
|
-
other.content_type == @content_type
|
24
|
+
self.class === other &&
|
25
|
+
other.content == @content &&
|
26
|
+
other.content_type == @content_type &&
|
27
27
|
other.type == @type
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
alias_method :eql?, :==
|
31
31
|
|
32
32
|
def hash
|
33
33
|
@content.hash ^ @content_type.hash ^ @type.hash
|
@@ -16,16 +16,16 @@ module Synapse
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def ==(other)
|
19
|
-
self.class === other
|
20
|
-
other.name == @name
|
19
|
+
self.class === other &&
|
20
|
+
other.name == @name &&
|
21
21
|
other.revision == @revision
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
alias_method :eql?, :==
|
25
25
|
|
26
26
|
def hash
|
27
27
|
@name.hash ^ @revision.hash
|
28
28
|
end
|
29
29
|
end # SerializedType
|
30
30
|
end # Serialization
|
31
|
-
end
|
31
|
+
end
|