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.
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]