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
@@ -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
|