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
@@ -12,6 +12,7 @@ module Synapse
12
12
  # before any listeners are allowed to do anything, and log that the commit is finished after
13
13
  # all other listeners have finished.
14
14
  class UnitOfWorkListenerCollection < UnitOfWorkListener
15
+ # @return [undefined]
15
16
  def initialize
16
17
  @listeners = Array.new
17
18
  @logger = Logging.logger[self.class]
@@ -22,17 +23,17 @@ module Synapse
22
23
  # @param [UnitOfWorkListener] listener
23
24
  # @return [undefined]
24
25
  def push(listener)
25
- @logger.debug 'Registering listener [%s]' % listener.class
26
+ @logger.debug "Registering listener {#{listener.class}}"
26
27
  @listeners.push listener
27
28
  end
28
29
 
29
- alias << push
30
+ alias_method :<<, :push
30
31
 
31
32
  # @param [UnitOfWork] unit
32
33
  # @return [undefined]
33
34
  def on_start(unit)
34
35
  @listeners.each do |listener|
35
- @logger.debug 'Notifying [%s] of start' % listener.class
36
+ @logger.debug "Notifying {#{listener.class}} that unit of work is starting"
36
37
  listener.on_start unit
37
38
  end
38
39
  end
@@ -54,7 +55,7 @@ module Synapse
54
55
  # @return [undefined]
55
56
  def on_prepare_commit(unit, aggregates, events)
56
57
  @listeners.each do |listener|
57
- @logger.debug 'Notifying [%s] of commit' % listener.class
58
+ @logger.debug "Notifying {#{listener.class}} that unit of work is preparing for commit"
58
59
  listener.on_prepare_commit unit, aggregates, events
59
60
  end
60
61
  end
@@ -64,7 +65,7 @@ module Synapse
64
65
  # @return [undefined]
65
66
  def on_prepare_transaction_commit(unit, transaction)
66
67
  @listeners.each do |listener|
67
- @logger.debug 'Notifying [%s] of transactional commit' % listener.class
68
+ @logger.debug "Notifying {#{listener.class}} that unit of work is preparing for tx commit"
68
69
  listener.on_prepare_transaction_commit unit, transaction
69
70
  end
70
71
  end
@@ -73,7 +74,7 @@ module Synapse
73
74
  # @return [undefined]
74
75
  def after_commit(unit)
75
76
  @listeners.reverse_each do |listener|
76
- @logger.debug 'Notifying [%s] of finished commit' % listener.class
77
+ @logger.debug "Notifying {#{listener.class}} that unit of work has been committed"
77
78
  listener.after_commit unit
78
79
  end
79
80
  end
@@ -83,7 +84,7 @@ module Synapse
83
84
  # @return [undefined]
84
85
  def on_rollback(unit, cause = nil)
85
86
  @listeners.reverse_each do |listener|
86
- @logger.debug 'Notifying [%s] of rollback' % listener.class
87
+ @logger.debug "Notifying {#{listener.class}} that unit of work is rolling back"
87
88
  listener.on_rollback unit, cause
88
89
  end
89
90
  end
@@ -92,13 +93,15 @@ module Synapse
92
93
  # @return [undefined]
93
94
  def on_cleanup(unit)
94
95
  @listeners.reverse_each do |listener|
95
- @logger.debug 'Notifying [%s] of cleanup' % listener.class
96
+ @logger.debug "Notifying {#{listener.class}} that unit of work is cleaning up"
96
97
 
97
98
  begin
98
99
  listener.on_cleanup unit
99
100
  rescue => exception
100
101
  # Ignore this exception so that we can continue cleaning up
101
- @logger.warn 'Listener raised an exception during cleanup: %s' % exception.inspect
102
+ backtrace = exception.backtrace.join $RS
103
+ @logger.warn "Listener {#{listener.class}} raised exception during cleanup: " +
104
+ "#{exception.inspect} #{backtrace}"
102
105
  end
103
106
  end
104
107
  end
@@ -3,6 +3,7 @@ module Synapse
3
3
  # Entry point for components to access units of work. Components managing transactional
4
4
  # boundaries can register and clear unit of work instances.
5
5
  class UnitOfWorkProvider
6
+ # @return [undefined]
6
7
  def initialize
7
8
  @threads = Hash.new
8
9
  end
@@ -40,7 +41,6 @@ module Synapse
40
41
  stack.last
41
42
  end
42
43
 
43
-
44
44
  # Pushes the given unit of work onto the top of the stack, making it the active unit of work
45
45
  #
46
46
  # If there are other units of work bound to this provider, they will be held until the given
@@ -160,7 +160,7 @@ module Synapse
160
160
  # @return [AggregateRoot] Returns nil if no similar aggregate was found
161
161
  def find_similar_aggregate(aggregate)
162
162
  @aggregates.each_key do |candidate|
163
- if aggregate.class === candidate and aggregate.id == candidate.id
163
+ if aggregate.class === candidate && aggregate.id == candidate.id
164
164
  return candidate
165
165
  end
166
166
  end
@@ -22,7 +22,7 @@ module Synapse
22
22
  @upcasters.push upcaster
23
23
  end
24
24
 
25
- alias << push
25
+ alias_method :<<, :push
26
26
 
27
27
  # @param [SerializedObject] serialized_object
28
28
  # @param [UpcastingContext] upcast_context
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = '0.5.6'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -10,11 +10,14 @@ module Synapse
10
10
  command = CommandMessage.build
11
11
  chain = Object.new
12
12
  unit = Object.new
13
+ result = Object.new
13
14
 
14
- mock(chain).proceed(command)
15
+ mock(chain).proceed(command) do
16
+ result
17
+ end
15
18
  mock(unit).register_listener(is_a(SerializationOptimizingListener))
16
19
 
17
- interceptor.intercept(command, unit, chain)
20
+ assert_same result, interceptor.intercept(command, unit, chain)
18
21
  end
19
22
  end
20
23
 
@@ -108,29 +108,22 @@ module Synapse
108
108
  @command_bus.dispatch_with_callback command, callback
109
109
  end
110
110
 
111
- should 'log when a subscribed handler is replaced' do
112
- handler = Object.new
113
-
114
- mock(@logger).debug(anything).ordered
115
- mock(@logger).info(anything).ordered
111
+ should 'return the previous handler when a subscribed handler is replaced' do
112
+ handler_a = Object.new
113
+ handler_b = Object.new
116
114
 
117
- @command_bus.subscribe TestCommand, handler
118
- @command_bus.subscribe TestCommand, handler
115
+ assert_nil (@command_bus.subscribe TestCommand, handler_a)
116
+ assert_same (@command_bus.subscribe TestCommand, handler_b), handler_a
119
117
  end
120
118
 
121
- should 'log when a handler is unsubscribed' do
119
+ should 'return true when a handler is unsubscribed' do
122
120
  handler_a = Object.new
123
121
  handler_b = Object.new
124
122
 
125
- mock(@logger).info(anything).ordered # not subscribed to anyone
126
- mock(@logger).debug(anything).ordered # now subscribed
127
- mock(@logger).info(anything).ordered # subscribed to different
128
- mock(@logger).debug(anything).ordered # now unsubscribed
129
-
130
- @command_bus.unsubscribe TestCommand, handler_a
123
+ refute @command_bus.unsubscribe TestCommand, handler_a
131
124
  @command_bus.subscribe TestCommand, handler_a
132
- @command_bus.unsubscribe TestCommand, handler_b
133
- @command_bus.unsubscribe TestCommand, handler_a
125
+ refute @command_bus.unsubscribe TestCommand, handler_b
126
+ assert @command_bus.unsubscribe TestCommand, handler_a
134
127
  end
135
128
  end
136
129
 
@@ -11,7 +11,7 @@ module Synapse
11
11
  end
12
12
 
13
13
  filter = ActiveModelValidationFilter.new
14
- filter.filter message
14
+ assert_same message, filter.filter(message)
15
15
  end
16
16
 
17
17
  should 'raise an exception if payload of a command message is invalid' do
@@ -0,0 +1,137 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ class IdentifierLockManagerTest < Test::Unit::TestCase
5
+ CountdownLatch = Contender::CountdownLatch
6
+
7
+ should 'dispose locks when they are no longer in use' do
8
+ manager = IdentifierLockManager.new
9
+
10
+ identifier = SecureRandom.uuid
11
+ manager.obtain_lock identifier
12
+ manager.release_lock identifier
13
+
14
+ assert_equal 0, manager.internal_locks.size
15
+ end
16
+
17
+ should 'not dispose locks when they are still in use' do
18
+ manager = IdentifierLockManager.new
19
+
20
+ identifier = SecureRandom.uuid
21
+
22
+ refute manager.owned? identifier
23
+
24
+ manager.obtain_lock identifier
25
+ assert manager.owned? identifier
26
+
27
+ manager.obtain_lock identifier
28
+ assert manager.owned? identifier
29
+
30
+ manager.release_lock identifier
31
+ assert manager.owned? identifier
32
+
33
+ manager.release_lock identifier
34
+ refute manager.owned? identifier
35
+ end
36
+
37
+ should 'detect a deadlock between two threads' do
38
+ manager = IdentifierLockManager.new
39
+
40
+ start_latch = CountdownLatch.new 1
41
+ latch = CountdownLatch.new 1
42
+ deadlock = Atomic.new false
43
+
44
+ lock_a = SecureRandom.uuid
45
+ lock_b = SecureRandom.uuid
46
+
47
+ start_lock_thread start_latch, latch, deadlock, manager, lock_a, manager, lock_b
48
+
49
+ manager.obtain_lock lock_b
50
+
51
+ start_latch.await
52
+ latch.countdown
53
+
54
+ begin
55
+ manager.obtain_lock lock_a
56
+ assert deadlock.get
57
+ rescue DeadlockError
58
+ # This is expected behavior
59
+ end
60
+ end
61
+
62
+ should 'detect a deadlock between two threads across lock managers' do
63
+ manager_a = IdentifierLockManager.new
64
+ manager_b = IdentifierLockManager.new
65
+
66
+ start_latch = CountdownLatch.new 1
67
+ latch = CountdownLatch.new 1
68
+ deadlock = Atomic.new false
69
+
70
+ lock_a = SecureRandom.uuid
71
+ lock_b = SecureRandom.uuid
72
+
73
+ start_lock_thread start_latch, latch, deadlock, manager_a, lock_a, manager_b, lock_a
74
+
75
+ manager_b.obtain_lock lock_a
76
+
77
+ start_latch.await
78
+ latch.countdown
79
+
80
+ begin
81
+ manager_a.obtain_lock lock_a
82
+ assert deadlock.get
83
+ rescue DeadlockError
84
+ # This is expected behavior
85
+ end
86
+ end
87
+
88
+ should 'detect a deadlock between three threads in a vector' do
89
+ manager = IdentifierLockManager.new
90
+
91
+ start_latch = CountdownLatch.new 3
92
+ latch = CountdownLatch.new 1
93
+ deadlock = Atomic.new false
94
+
95
+ lock_a = SecureRandom.uuid
96
+ lock_b = SecureRandom.uuid
97
+ lock_c = SecureRandom.uuid
98
+ lock_d = SecureRandom.uuid
99
+
100
+ start_lock_thread start_latch, latch, deadlock, manager, lock_a, manager, lock_b
101
+ start_lock_thread start_latch, latch, deadlock, manager, lock_b, manager, lock_c
102
+ start_lock_thread start_latch, latch, deadlock, manager, lock_c, manager, lock_d
103
+
104
+ manager.obtain_lock lock_d
105
+
106
+ start_latch.await
107
+ latch.countdown
108
+
109
+ begin
110
+ manager.obtain_lock lock_a
111
+ assert deadlock.get
112
+ rescue DeadlockError
113
+ # This is expected behavior
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def start_lock_thread(start_latch, latch, deadlock, manager_a, lock_a, manager_b, lock_b)
120
+ Thread.new do
121
+ manager_a.obtain_lock lock_a
122
+ start_latch.countdown
123
+
124
+ begin
125
+ latch.await
126
+
127
+ manager_b.obtain_lock lock_b
128
+ manager_b.release_lock lock_b
129
+ rescue DeadlockError
130
+ deadlock.set true
131
+ ensure
132
+ manager_a.release_lock lock_a
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -33,7 +33,7 @@ module Synapse
33
33
  @builder.converter_factory
34
34
 
35
35
  factory = @container.resolve :converter_factory
36
- factory.converters.at(0).is_a? Serialization::JsonToObjectConverter
36
+ factory.converters.first.is_a? Serialization::JsonToObjectConverter
37
37
 
38
38
  # Customized
39
39
  @builder.converter_factory :alt_factory do
@@ -41,7 +41,7 @@ module Synapse
41
41
  end
42
42
 
43
43
  factory = @container.resolve :alt_factory
44
- factory.converters.at(0).is_a? Serialization::JsonToObjectConverter
44
+ factory.converters.first.is_a? Serialization::JsonToObjectConverter
45
45
  end
46
46
  end
47
47
  end
@@ -70,6 +70,24 @@ module Synapse
70
70
  end
71
71
  end
72
72
 
73
+ should 'raise an exception while saving if lock could not be validated' do
74
+ event = create_event(123, 0, StubCreatedEvent.new(123))
75
+
76
+ mock(@event_store).read_events(@factory.type_identifier, 123) do
77
+ Domain::SimpleDomainEventStream.new event
78
+ end
79
+
80
+ aggregate = @repository.load 123
81
+
82
+ mock(@lock_manager).validate_lock(aggregate) do
83
+ false
84
+ end
85
+
86
+ assert_raise Repository::ConcurrencyError do
87
+ @unit.commit
88
+ end
89
+ end
90
+
73
91
  should 'defer version checking to a conflict resolver if one is set' do
74
92
  @repository.conflict_resolver = AcceptAllConflictResolver.new
75
93
 
@@ -2,12 +2,15 @@ require 'test_helper'
2
2
 
3
3
  module Synapse
4
4
  module Repository
5
+
5
6
  class SimpleRepositoryTest < Test::Unit::TestCase
6
7
  def setup
7
8
  @unit_provider = UnitOfWork::UnitOfWorkProvider.new
8
9
  @unit_factory = UnitOfWork::UnitOfWorkFactory.new @unit_provider
9
10
 
10
- @repository = SimpleRepository.new NullLockManager.new, TestMappedAggregate
11
+ @lock_manager = NullLockManager.new
12
+
13
+ @repository = SimpleRepository.new @lock_manager, TestMappedAggregate
11
14
  @repository.event_bus = EventBus::SimpleEventBus.new
12
15
  @repository.unit_provider = @unit_provider
13
16
  end
@@ -15,43 +18,71 @@ module Synapse
15
18
  should 'load an aggregate using its finder' do
16
19
  unit = @unit_factory.create
17
20
 
18
- aggregate = TestMappedAggregate.new '5677b4f7'
19
- mock(TestMappedAggregate).find(aggregate.id) do
21
+ aggregate_id = SecureRandom.uuid
22
+ aggregate = TestMappedAggregate.new aggregate_id
23
+
24
+ mock(TestMappedAggregate).find(aggregate_id) do
20
25
  aggregate
21
26
  end
22
27
 
23
- loaded = @repository.load aggregate.id
28
+ loaded = @repository.load aggregate_id
24
29
 
25
30
  assert_same loaded, aggregate
26
31
  end
27
32
 
28
33
  should 'raise an exception if the aggregate could not be found' do
29
- mock(TestMappedAggregate).find('5677b4f7')
34
+ aggregate_id = SecureRandom.uuid
35
+
36
+ mock(TestMappedAggregate).find(aggregate_id)
30
37
 
31
38
  assert_raise AggregateNotFoundError do
32
- @repository.load '5677b4f7'
39
+ @repository.load aggregate_id
33
40
  end
34
41
  end
35
42
 
36
43
  should 'raise an exception if the loaded aggregate has an unexpected version' do
37
44
  unit = @unit_factory.create
38
45
 
39
- aggregate = TestMappedAggregate.new '5677b4f7'
46
+ aggregate_id = SecureRandom.uuid
47
+ aggregate = TestMappedAggregate.new aggregate_id
40
48
  aggregate.version = 5
41
49
 
42
- mock(TestMappedAggregate).find(aggregate.id) do
50
+ mock(TestMappedAggregate).find(aggregate_id) do
43
51
  aggregate
44
52
  end
45
53
 
46
54
  assert_raise ConflictingAggregateVersionError do
47
- @repository.load aggregate.id, 4
55
+ @repository.load aggregate_id, 4
56
+ end
57
+ end
58
+
59
+ should 'raise an exception while saving if lock could not be validated' do
60
+ unit = @unit_factory.create
61
+
62
+ aggregate_id = SecureRandom.uuid
63
+ aggregate = TestMappedAggregate.new aggregate_id
64
+ aggregate.version = 5
65
+
66
+ mock(TestMappedAggregate).find(aggregate_id) do
67
+ aggregate
68
+ end
69
+
70
+ @repository.load aggregate_id
71
+
72
+ mock(@lock_manager).validate_lock(aggregate) do
73
+ false
74
+ end
75
+
76
+ assert_raise ConcurrencyError do
77
+ unit.commit
48
78
  end
49
79
  end
50
80
 
51
81
  should 'delete the aggregate if it has been marked for deletion' do
52
82
  unit = @unit_factory.create
53
83
 
54
- aggregate = TestMappedAggregate.new '5677b4f7'
84
+ aggregate_id = SecureRandom.uuid
85
+ aggregate = TestMappedAggregate.new aggregate_id
55
86
  aggregate.delete_this_thing
56
87
 
57
88
  @repository.add aggregate
@@ -75,5 +106,6 @@ module Synapse
75
106
  mark_deleted
76
107
  end
77
108
  end
109
+
78
110
  end
79
111
  end