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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/lib/synapse-core.rb +31 -1
  3. data/lib/synapse/command.rb +1 -0
  4. data/lib/synapse/command/callbacks/future.rb +3 -3
  5. data/lib/synapse/command/callbacks/void.rb +14 -0
  6. data/lib/synapse/command/command_bus.rb +2 -2
  7. data/lib/synapse/command/command_callback.rb +9 -2
  8. data/lib/synapse/command/gateway.rb +1 -1
  9. data/lib/synapse/command/gateway/interval_retry_scheduler.rb +6 -6
  10. data/lib/synapse/command/gateway/retry_scheduler.rb +7 -4
  11. data/lib/synapse/command/interceptor_chain.rb +1 -1
  12. data/lib/synapse/command/interceptors/serialization.rb +2 -1
  13. data/lib/synapse/command/simple_command_bus.rb +23 -34
  14. data/lib/synapse/common.rb +2 -2
  15. data/lib/synapse/common/concurrency/disposable_lock.rb +157 -0
  16. data/lib/synapse/common/concurrency/identifier_lock_manager.rb +164 -0
  17. data/lib/synapse/common/duplication.rb +4 -4
  18. data/lib/synapse/common/errors.rb +5 -0
  19. data/lib/synapse/configuration/container.rb +1 -1
  20. data/lib/synapse/configuration/container_builder.rb +1 -1
  21. data/lib/synapse/configuration/definition.rb +1 -1
  22. data/lib/synapse/domain/aggregate_root.rb +1 -1
  23. data/lib/synapse/domain/simple_stream.rb +3 -5
  24. data/lib/synapse/domain/stream.rb +2 -2
  25. data/lib/synapse/event_bus/event_bus.rb +2 -2
  26. data/lib/synapse/event_bus/simple_event_bus.rb +5 -6
  27. data/lib/synapse/event_sourcing/caching.rb +1 -1
  28. data/lib/synapse/event_sourcing/entity.rb +4 -2
  29. data/lib/synapse/event_sourcing/repository.rb +9 -13
  30. data/lib/synapse/event_sourcing/snapshot/taker.rb +1 -1
  31. data/lib/synapse/mapping/mapping.rb +1 -1
  32. data/lib/synapse/process_manager/correlation.rb +3 -29
  33. data/lib/synapse/process_manager/pessimistic_lock_manager.rb +3 -3
  34. data/lib/synapse/process_manager/process.rb +2 -6
  35. data/lib/synapse/process_manager/process_manager.rb +1 -1
  36. data/lib/synapse/process_manager/process_repository.rb +1 -1
  37. data/lib/synapse/process_manager/repository/in_memory.rb +5 -4
  38. data/lib/synapse/process_manager/simple_process_manager.rb +2 -2
  39. data/lib/synapse/repository/errors.rb +2 -2
  40. data/lib/synapse/repository/locking.rb +48 -0
  41. data/lib/synapse/repository/optimistic_lock_manager.rb +10 -9
  42. data/lib/synapse/repository/pessimistic_lock_manager.rb +5 -4
  43. data/lib/synapse/repository/repository.rb +1 -1
  44. data/lib/synapse/repository/simple_repository.rb +8 -7
  45. data/lib/synapse/serialization/converter.rb +3 -0
  46. data/lib/synapse/serialization/converter_factory.rb +6 -5
  47. data/lib/synapse/serialization/serialized_object.rb +4 -4
  48. data/lib/synapse/serialization/serialized_type.rb +4 -4
  49. data/lib/synapse/uow/listener_collection.rb +12 -9
  50. data/lib/synapse/uow/provider.rb +1 -1
  51. data/lib/synapse/uow/uow.rb +1 -1
  52. data/lib/synapse/upcasting/upcaster_chain.rb +1 -1
  53. data/lib/synapse/version.rb +1 -1
  54. data/test/command/serialization_test.rb +5 -2
  55. data/test/command/simple_command_bus_test.rb +9 -16
  56. data/test/command/validation_test.rb +1 -1
  57. data/test/common/concurrency/identifier_lock_manager_test.rb +137 -0
  58. data/test/configuration/component/serialization/converter_factory_test.rb +2 -2
  59. data/test/event_sourcing/repository_test.rb +18 -0
  60. data/test/repository/simple_repository_test.rb +42 -10
  61. data/test/test_helper.rb +3 -4
  62. metadata +25 -29
  63. data/lib/synapse.rb +0 -34
  64. data/lib/synapse/common/concurrency/identifier_lock.rb +0 -56
  65. data/lib/synapse/common/concurrency/public_lock.rb +0 -95
  66. data/lib/synapse/event_bus/clustering/cluster.rb +0 -10
  67. data/lib/synapse/event_bus/clustering/event_bus.rb +0 -55
  68. data/lib/synapse/event_bus/clustering/selector.rb +0 -14
  69. data/lib/synapse/rails/injection_helper.rb +0 -23
  70. data/lib/synapse/railtie.rb +0 -17
  71. data/test/common/concurrency/identifier_lock_test.rb +0 -25
  72. data/test/common/concurrency/public_lock_test.rb +0 -83
  73. data/test/process_manager/correlation_test.rb +0 -24
  74. 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
- @lock = Mutex.new
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
- @lock.synchronize do
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
- @lock.synchronize do
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
- @lock.synchronize do
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
@@ -40,7 +40,7 @@ module Synapse
40
40
  end
41
41
  end
42
42
 
43
- alias [] resolve
43
+ alias_method :[], :resolve
44
44
 
45
45
  # Resolves any definitions that have the given tag
46
46
  #
@@ -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
@@ -21,7 +21,7 @@ module Synapse
21
21
  # @return [Boolean] True if this aggregate has been marked for deletion
22
22
  attr_reader :deleted
23
23
 
24
- alias deleted? deleted
24
+ alias_method :deleted?, :deleted
25
25
 
26
26
  # @return [Object] The identifier of this aggregate
27
27
  attr_reader :id
@@ -9,14 +9,13 @@ module Synapse
9
9
  @next_index = 0
10
10
  end
11
11
 
12
- # Returns true if the end of the stream has been reached
12
+ # @api public
13
13
  # @return [Boolean]
14
14
  def end?
15
15
  @next_index >= @events.size
16
16
  end
17
17
 
18
- # Returns the next event in the stream and moves the stream's pointer forward
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
- # Returns the next event in the stream without moving the stream's pointer forward
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 = InMemoryDomainEventStream.new events
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 = InMemoryDomainEventStream.new events
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 [SubscriptionFailedError] If subscription of an event listener failed
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 SubscriptionFailedError < NonTransientError; end
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 'Dispatching event [%s] to listener [%s]' %
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 'Event listener [%s] subscribed' % listener.class
41
+ @logger.debug "Event listener {#{listener.class}} subscribed"
43
42
  else
44
- @logger.info 'Event listener [%s] not added, was already subscribed' % listener.class
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 'Event listener [%s] unsubscribed' % listener.class
52
+ @logger.debug "Event listener {#{listener.class}} unsubscribed"
54
53
  else
55
- @logger.info 'Event listener [%s] not removed, was not subscribed' % listener.class
54
+ @logger.info "Event listener {#{listener.class}} is not subscribed"
56
55
  end
57
56
  end
58
57
  end # SimpleEventBus
@@ -38,7 +38,7 @@ module Synapse
38
38
 
39
39
  # @param [AggregateRoot] aggregate
40
40
  # @return [undefined]
41
- def save_aggregate(aggregate)
41
+ def save_aggregate_with_lock(aggregate)
42
42
  super aggregate
43
43
  @cache.write aggregate.id, aggregate
44
44
  end
@@ -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 and !@aggregate_root.equal? aggregate_root
29
- raise 'Entity is registered to a different aggregate root'
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 and @conflict_resolver.nil?
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 and @snapshot_taker
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 delete_aggregate(aggregate)
89
- save_aggregate aggregate
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 save_aggregate(aggregate)
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 and @conflict_resolver
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 and snapshot.sequence_number > first_sequence_number
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
@@ -54,7 +54,7 @@ module Synapse
54
54
  @type == other.type
55
55
  end
56
56
 
57
- alias eql? ==
57
+ alias_method :eql?, :==
58
58
 
59
59
  # TODO Is this a good hash function? Probs not
60
60
  # @return [Integer]