synapse-core 0.4.0 → 0.5.1

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 (163) hide show
  1. data/lib/synapse/command/async_command_bus.rb +41 -0
  2. data/lib/synapse/command/command_callback.rb +49 -2
  3. data/lib/synapse/command/command_handler.rb +2 -0
  4. data/lib/synapse/command/filters/validation.rb +2 -2
  5. data/lib/synapse/command/gateway/interval_retry_scheduler.rb +75 -0
  6. data/lib/synapse/command/gateway/retry_scheduler.rb +26 -0
  7. data/lib/synapse/command/gateway/retrying_callback.rb +46 -0
  8. data/lib/synapse/command/gateway.rb +56 -11
  9. data/lib/synapse/command/interceptor_chain.rb +2 -2
  10. data/lib/synapse/command/interceptors/serialization.rb +2 -2
  11. data/lib/synapse/command/message.rb +16 -0
  12. data/lib/synapse/command/simple_command_bus.rb +6 -2
  13. data/lib/synapse/command/wiring.rb +3 -3
  14. data/lib/synapse/command.rb +7 -1
  15. data/lib/synapse/common/concurrency/identifier_lock.rb +2 -17
  16. data/lib/synapse/common/concurrency/public_lock.rb +8 -9
  17. data/lib/synapse/common/errors.rb +1 -1
  18. data/lib/synapse/common/message.rb +2 -6
  19. data/lib/synapse/common.rb +9 -0
  20. data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +41 -0
  21. data/lib/synapse/configuration/component/command_bus/gateway.rb +38 -0
  22. data/lib/synapse/configuration/component/command_bus/simple_command_bus.rb +81 -0
  23. data/lib/synapse/configuration/component/command_bus.rb +35 -0
  24. data/lib/synapse/configuration/component/event_bus/simple_event_bus.rb +41 -0
  25. data/lib/synapse/configuration/component/event_bus.rb +15 -0
  26. data/lib/synapse/configuration/component/event_sourcing/repository.rb +62 -0
  27. data/lib/synapse/configuration/component/event_sourcing.rb +15 -0
  28. data/lib/synapse/configuration/component/repository/locking_repository.rb +85 -0
  29. data/lib/synapse/configuration/component/repository/simple_repository.rb +31 -0
  30. data/lib/synapse/configuration/component/repository.rb +15 -0
  31. data/lib/synapse/configuration/component/serialization/converter_factory.rb +50 -0
  32. data/lib/synapse/configuration/component/serialization/serializer.rb +90 -0
  33. data/lib/synapse/configuration/component/serialization.rb +25 -0
  34. data/lib/synapse/configuration/component/uow/unit_factory.rb +51 -0
  35. data/lib/synapse/configuration/component/uow.rb +22 -0
  36. data/lib/synapse/configuration/component/upcasting/upcaster_chain.rb +61 -0
  37. data/lib/synapse/configuration/component/upcasting.rb +15 -0
  38. data/lib/synapse/configuration/container.rb +80 -0
  39. data/lib/synapse/configuration/container_builder.rb +108 -0
  40. data/lib/synapse/configuration/definition.rb +31 -0
  41. data/lib/synapse/configuration/definition_builder.rb +138 -0
  42. data/lib/synapse/configuration/dependent.rb +31 -0
  43. data/lib/synapse/configuration/ext.rb +35 -0
  44. data/lib/synapse/configuration.rb +32 -0
  45. data/lib/synapse/domain/aggregate_root.rb +25 -26
  46. data/lib/synapse/domain/event_container.rb +5 -5
  47. data/lib/synapse/domain/message.rb +16 -0
  48. data/lib/synapse/event_bus/event_listener.rb +2 -0
  49. data/lib/synapse/event_bus/event_publisher.rb +19 -0
  50. data/lib/synapse/event_bus/simple_event_bus.rb +21 -30
  51. data/lib/synapse/event_bus/wiring.rb +5 -4
  52. data/lib/synapse/event_bus.rb +1 -1
  53. data/lib/synapse/event_sourcing/aggregate_root.rb +6 -4
  54. data/lib/synapse/process_manager/container_resource_injector.rb +18 -0
  55. data/lib/synapse/process_manager/pessimistic_lock_manager.rb +1 -1
  56. data/lib/synapse/process_manager/process.rb +4 -0
  57. data/lib/synapse/process_manager/process_factory.rb +3 -3
  58. data/lib/synapse/process_manager/repository/in_memory.rb +1 -1
  59. data/lib/synapse/process_manager/wiring/process.rb +6 -6
  60. data/lib/synapse/process_manager/wiring/process_manager.rb +8 -8
  61. data/lib/synapse/process_manager.rb +2 -0
  62. data/lib/synapse/repository/locking.rb +5 -3
  63. data/lib/synapse/repository/optimistic_lock_manager.rb +2 -7
  64. data/lib/synapse/repository/pessimistic_lock_manager.rb +3 -3
  65. data/lib/synapse/repository/repository.rb +2 -2
  66. data/lib/synapse/repository/simple_repository.rb +69 -0
  67. data/lib/synapse/repository.rb +1 -0
  68. data/lib/synapse/serialization/converter/chain.rb +2 -2
  69. data/lib/synapse/serialization/converter_factory.rb +2 -2
  70. data/lib/synapse/serialization/lazy_object.rb +2 -2
  71. data/lib/synapse/serialization/message/serialization_aware_message.rb +3 -3
  72. data/lib/synapse/serialization/message/serialized_message.rb +6 -10
  73. data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
  74. data/lib/synapse/serialization/message/serialized_object_cache.rb +2 -2
  75. data/lib/synapse/serialization/message/serializer.rb +2 -2
  76. data/lib/synapse/serialization/revision_resolver.rb +3 -3
  77. data/lib/synapse/serialization/serialized_object.rb +2 -2
  78. data/lib/synapse/serialization/serialized_type.rb +2 -2
  79. data/lib/synapse/serialization/serializer/attribute.rb +2 -2
  80. data/lib/synapse/serialization/serializer/marshal.rb +2 -2
  81. data/lib/synapse/serialization/serializer/oj.rb +2 -2
  82. data/lib/synapse/serialization/serializer/ox.rb +2 -2
  83. data/lib/synapse/serialization/serializer.rb +2 -2
  84. data/lib/synapse/uow/factory.rb +2 -2
  85. data/lib/synapse/uow/listener_collection.rb +2 -2
  86. data/lib/synapse/uow/nesting.rb +9 -2
  87. data/lib/synapse/uow/provider.rb +2 -2
  88. data/lib/synapse/uow/uow.rb +8 -2
  89. data/lib/synapse/upcasting/{chain.rb → upcaster_chain.rb} +0 -0
  90. data/lib/synapse/upcasting.rb +1 -1
  91. data/lib/synapse/version.rb +1 -1
  92. data/lib/synapse/wiring/message_wiring.rb +31 -0
  93. data/lib/synapse.rb +2 -14
  94. data/test/auditing/data_provider_test.rb +2 -2
  95. data/test/auditing/dispatch_interceptor_test.rb +1 -1
  96. data/test/auditing/unit_listener_test.rb +3 -3
  97. data/test/command/async_command_bus_test.rb +49 -0
  98. data/test/command/duplication_test.rb +2 -2
  99. data/test/command/gateway/interval_retry_scheduler_test.rb +42 -0
  100. data/test/command/gateway/retrying_callback_test.rb +57 -0
  101. data/test/command/gateway_test.rb +41 -7
  102. data/test/command/interceptor_chain_test.rb +1 -1
  103. data/test/command/message_test.rb +17 -0
  104. data/test/command/serialization_test.rb +2 -2
  105. data/test/command/simple_command_bus_test.rb +7 -7
  106. data/test/command/validation_test.rb +3 -3
  107. data/test/command/wiring_test.rb +3 -3
  108. data/test/common/concurrency/identifier_lock_test.rb +2 -13
  109. data/test/common/concurrency/public_lock_test.rb +6 -6
  110. data/test/{duplication_test.rb → common/duplication_test.rb} +3 -3
  111. data/test/configuration/component/command_bus/async_command_bus_test.rb +36 -0
  112. data/test/configuration/component/command_bus/simple_command_bus_test.rb +57 -0
  113. data/test/configuration/component/event_bus/simple_event_bus_test.rb +58 -0
  114. data/test/configuration/component/serialization/converter_factory_test.rb +48 -0
  115. data/test/configuration/component/serialization/serializer_test.rb +78 -0
  116. data/test/configuration/component/uow/unit_factory_test.rb +46 -0
  117. data/test/configuration/container_builder_test.rb +47 -0
  118. data/test/configuration/container_test.rb +88 -0
  119. data/test/configuration/definition_builder_test.rb +126 -0
  120. data/test/configuration/definition_test.rb +41 -0
  121. data/test/configuration/dependent_test.rb +30 -0
  122. data/test/configuration/ext_test.rb +19 -0
  123. data/test/configuration/fixtures/dependent.rb +10 -0
  124. data/test/domain/aggregate_root_test.rb +5 -5
  125. data/test/domain/message_test.rb +15 -3
  126. data/test/domain/stream_test.rb +2 -2
  127. data/test/event_bus/publisher_test.rb +29 -0
  128. data/test/event_bus/wiring_test.rb +1 -1
  129. data/test/event_sourcing/aggregate_factory_test.rb +12 -6
  130. data/test/event_sourcing/aggregate_root_test.rb +4 -4
  131. data/test/event_sourcing/entity_test.rb +10 -9
  132. data/test/event_sourcing/repository_test.rb +6 -6
  133. data/test/event_sourcing/storage_listener_test.rb +8 -4
  134. data/test/event_store/in_memory_test.rb +3 -3
  135. data/test/process_manager/container_resource_injector_test.rb +19 -0
  136. data/test/process_manager/correlation_set_test.rb +2 -2
  137. data/test/process_manager/correlation_test.rb +2 -2
  138. data/test/process_manager/in_memory_test.rb +3 -3
  139. data/test/process_manager/process_factory_test.rb +2 -2
  140. data/test/process_manager/process_test.rb +3 -3
  141. data/test/process_manager/simple_process_manager_test.rb +5 -5
  142. data/test/process_manager/wiring/process_manager_test.rb +4 -4
  143. data/test/process_manager/wiring/process_test.rb +2 -2
  144. data/test/repository/locking_test.rb +4 -4
  145. data/test/repository/optimistic_test.rb +2 -2
  146. data/test/repository/pessimistic_test.rb +1 -1
  147. data/test/repository/simple_repository_test.rb +79 -0
  148. data/test/support/countdown_latch.rb +18 -0
  149. data/test/test_helper.rb +6 -3
  150. data/test/upcasting/data_test.rb +31 -0
  151. metadata +84 -25
  152. data/lib/synapse/event_bus/event_listener_proxy.rb +0 -12
  153. data/lib/synapse/partitioning/memory_queue_reader.rb +0 -31
  154. data/lib/synapse/partitioning/memory_queue_writer.rb +0 -19
  155. data/lib/synapse/partitioning/message_receipt.rb +0 -25
  156. data/lib/synapse/partitioning/packing/json_packer.rb +0 -93
  157. data/lib/synapse/partitioning/packing/json_unpacker.rb +0 -83
  158. data/lib/synapse/partitioning/packing.rb +0 -27
  159. data/lib/synapse/partitioning/queue_reader.rb +0 -32
  160. data/lib/synapse/partitioning/queue_writer.rb +0 -17
  161. data/lib/synapse/partitioning.rb +0 -18
  162. data/test/partitioning/memory_test.rb +0 -34
  163. data/test/partitioning/packing/json_test.rb +0 -62
@@ -29,6 +29,6 @@ module Synapse
29
29
  def native_content_type
30
30
  String
31
31
  end
32
- end
33
- end
32
+ end # OjSerializer
33
+ end # Serialization
34
34
  end
@@ -26,6 +26,6 @@ module Synapse
26
26
  def native_content_type
27
27
  String
28
28
  end
29
- end
30
- end
29
+ end # OxSerializer
30
+ end # Serialization
31
31
  end
@@ -95,6 +95,6 @@ module Synapse
95
95
  def convert(original, source_type, target_type)
96
96
  converter_factory.converter(source_type, target_type).convert_content(original)
97
97
  end
98
- end
99
- end
98
+ end # Serializer
99
+ end # Serialization
100
100
  end
@@ -23,6 +23,6 @@ module Synapse
23
23
  unit.start
24
24
  end
25
25
  end
26
- end
27
- end
26
+ end # UnitOfWorkFactory
27
+ end # UnitOfWork
28
28
  end
@@ -146,6 +146,6 @@ module Synapse
146
146
 
147
147
  @logger.debug 'Listeners successfully notified'
148
148
  end
149
- end
150
- end
149
+ end # UnitOfWorkListenerCollection
150
+ end # UnitOfWork
151
151
  end
@@ -26,6 +26,7 @@ module Synapse
26
26
  # resources it acquired. The effectively means that a rollback is done if the unit of work
27
27
  # failed to commit.
28
28
  #
29
+ # @api public
29
30
  # @raise [RuntimeError] If unit of work hasn't been started yet
30
31
  # @return [undefined]
31
32
  def commit
@@ -61,6 +62,7 @@ module Synapse
61
62
  # Any buffered events and registered aggregates are discarded and any registered unit of work
62
63
  # listeners are notified of the rollback.
63
64
  #
65
+ # @api public
64
66
  # @param [Error] cause
65
67
  # @return [undefined]
66
68
  def rollback(cause = nil)
@@ -79,6 +81,7 @@ module Synapse
79
81
 
80
82
  # Starts the unit of work, preparing it for aggregate registration
81
83
  #
84
+ # @api public
82
85
  # @raise [RuntimeError] If unit of work has already been started
83
86
  # @return [undefined]
84
87
  def start
@@ -105,6 +108,8 @@ module Synapse
105
108
  end
106
109
 
107
110
  # Returns true if this unit of work has been started
111
+ #
112
+ # @api public
108
113
  # @return [Boolean]
109
114
  def started?
110
115
  @started
@@ -224,6 +229,8 @@ module Synapse
224
229
 
225
230
  # Listener that allows a nested unit of work to properly operate within in a unit of
226
231
  # work that is not aware of nesting
232
+ #
233
+ # @api private
227
234
  class OuterCommitUnitOfWorkListener < UnitOfWorkListener
228
235
  # @param [UnitOfWork] inner_unit
229
236
  # @param [UnitOfWorkProvider] provider
@@ -257,6 +264,6 @@ module Synapse
257
264
  def on_cleanup(outer_unit)
258
265
  @inner_unit.perform_cleanup
259
266
  end
260
- end
261
- end
267
+ end # NestableUnitOfWork
268
+ end # UnitOfWork
262
269
  end
@@ -66,6 +66,6 @@ module Synapse
66
66
  rescue KeyError
67
67
  @threads.store Thread.current, Array.new
68
68
  end
69
- end
70
- end
69
+ end # UnitOfWorkProvider
70
+ end # UnitOfWork
71
71
  end
@@ -13,6 +13,8 @@ module Synapse
13
13
  end
14
14
 
15
15
  # Returns true if this unit of work is bound to a transaction
16
+ #
17
+ # @api public
16
18
  # @return [Boolean]
17
19
  def transactional?
18
20
  !!@transaction_manager
@@ -20,6 +22,7 @@ module Synapse
20
22
 
21
23
  # Registers a listener that is notified of state changes in this unit of work
22
24
  #
25
+ # @api public
23
26
  # @param [UnitOfWorkListener] listener
24
27
  # @return [undefined]
25
28
  def register_listener(listener)
@@ -38,6 +41,7 @@ module Synapse
38
41
  # and with the same identifier, that aggregate will be returned instead of the given
39
42
  # aggregate.
40
43
  #
44
+ # @api public
41
45
  # @param [AggregateRoot] aggregate
42
46
  # @param [EventBus] event_bus
43
47
  # @param [StorageListener] storage_listener
@@ -60,6 +64,7 @@ module Synapse
60
64
  # Buffers an event for publication to the given event bus until this unit of work is
61
65
  # committed
62
66
  #
67
+ # @api public
63
68
  # @param [EventMessage] event
64
69
  # @param [EventBus] event_bus
65
70
  # @return [EventMessage] The event that will be published to the event bus
@@ -78,6 +83,7 @@ module Synapse
78
83
 
79
84
  # Sets the transaction manager that will be used by this unit of work
80
85
  #
86
+ # @api public
81
87
  # @raise [RuntimeError] If unit of work has been started
82
88
  # @param [TransactionManager] transaction_manager
83
89
  # @return [undefined]
@@ -173,6 +179,6 @@ module Synapse
173
179
  end
174
180
  end
175
181
  end
176
- end
177
- end
182
+ end # UnitOfWork
183
+ end # UnitOfWork
178
184
  end
@@ -1,5 +1,5 @@
1
1
  require 'synapse/upcasting/context'
2
- require 'synapse/upcasting/chain'
3
2
  require 'synapse/upcasting/data'
3
+ require 'synapse/upcasting/upcaster_chain'
4
4
  require 'synapse/upcasting/upcaster'
5
5
  require 'synapse/upcasting/single_upcaster'
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.1'
3
3
  end
@@ -1,6 +1,10 @@
1
1
  module Synapse
2
2
  module Wiring
3
3
  # Base mixin that make it easier to wire handlers to their respective types
4
+ #
5
+ # It is recommended to use mixins more specific to the component being implemented, like
6
+ # wiring command handlers, event listeners, event-sourced members, or processes.
7
+ #
4
8
  # @abstract
5
9
  module MessageWiring
6
10
  extend ActiveSupport::Concern
@@ -14,6 +18,33 @@ module Synapse
14
18
  end
15
19
 
16
20
  module ClassMethods
21
+ # Wires a message handler to messages with payload of the given type
22
+ #
23
+ # @example
24
+ # wire CashWithdrawnEvent do |event|
25
+ # # do something with the event
26
+ # end
27
+ #
28
+ # @example
29
+ # wire CashWithdrawnEvent :to => :on_withdraw
30
+ #
31
+ # def on_withdraw(event)
32
+ # # do something with the event
33
+ # end
34
+ #
35
+ # Certain components that use message handling have different options that can be set
36
+ # on wires, like wiring processes.
37
+ #
38
+ # @example
39
+ # wire SellTransactionStartedEvent, :start => true, :correlate => :transaction_id do
40
+ # # do something with the event
41
+ # end
42
+ #
43
+ # @api public
44
+ # @param [Class] type
45
+ # @param [Object...] args
46
+ # @param [Proc] block
47
+ # @return [undefined]
17
48
  def wire(type, *args, &block)
18
49
  options = args.extract_options!
19
50
 
data/lib/synapse.rb CHANGED
@@ -3,24 +3,12 @@ require 'active_support/core_ext'
3
3
  require 'logging'
4
4
  require 'set'
5
5
 
6
+ require 'synapse/common'
6
7
  require 'synapse/version'
7
8
 
8
- require 'synapse/common/errors'
9
- require 'synapse/common/identifier'
10
- require 'synapse/common/message'
11
- require 'synapse/common/message_builder'
12
-
13
- require 'synapse/common/concurrency/identifier_lock'
14
- require 'synapse/common/concurrency/public_lock'
15
-
16
9
  module Synapse
17
10
  extend ActiveSupport::Autoload
18
11
 
19
- autoload_at 'synapse/common/duplication' do
20
- autoload :DuplicationError
21
- autoload :DuplicationRecorder
22
- end
23
-
24
12
  eager_autoload do
25
13
  # Common components
26
14
  autoload :Command
@@ -34,9 +22,9 @@ module Synapse
34
22
 
35
23
  # Optional components
36
24
  autoload :Auditing
25
+ autoload :Configuration
37
26
  autoload :EventSourcing
38
27
  autoload :EventStore
39
- autoload :Partitioning
40
28
  autoload :ProcessManager
41
29
  autoload :Upcasting
42
30
  end
@@ -4,7 +4,7 @@ module Synapse
4
4
  module Auditing
5
5
 
6
6
  class CommandMetadataProviderTest < Test::Unit::TestCase
7
- def test_provide_data_for
7
+ should 'provide the metadata from a command for auditing' do
8
8
  data = { foo: 0 }
9
9
 
10
10
  provider = CommandMetadataProvider.new
@@ -17,7 +17,7 @@ module Synapse
17
17
  end
18
18
 
19
19
  class CorrelationDataProviderTest < Test::Unit::TestCase
20
- def test_provide_data_for
20
+ should 'provide the identifier of a command for auditing' do
21
21
  provider = CorrelationDataProvider.new
22
22
  command = Command::CommandMessage.build
23
23
 
@@ -4,7 +4,7 @@ module Synapse
4
4
  module Auditing
5
5
 
6
6
  class AuditingDispatchInterceptorTest < Test::Unit::TestCase
7
- def test_intercept
7
+ should 'register a unit of work listener for auditing' do
8
8
  return_value = Object.new
9
9
  chain = Object.new
10
10
  command = Object.new
@@ -4,7 +4,7 @@ module Synapse
4
4
  module Auditing
5
5
 
6
6
  class AuditingUnitOfWorkListenerTest < Test::Unit::TestCase
7
- def test_on_event_registered
7
+ should 'supplement events with auditing data' do
8
8
  data_provider_a = Object.new
9
9
  data_provider_b = Object.new
10
10
 
@@ -30,7 +30,7 @@ module Synapse
30
30
  assert listener.recorded_events.include? event
31
31
  end
32
32
 
33
- def test_after_commit
33
+ should 'notify the audit logger of success after the unit of work is committed' do
34
34
  logger = Object.new
35
35
 
36
36
  command = Object.new
@@ -48,7 +48,7 @@ module Synapse
48
48
  listener.after_commit Object.new
49
49
  end
50
50
 
51
- def test_on_rollback
51
+ should 'notify the audit logger of failure when a unit of work is rolled back' do
52
52
  logger = Object.new
53
53
 
54
54
  command = Object.new
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+ require 'support/countdown_latch'
3
+
4
+ module Synapse
5
+ module Command
6
+ class AsynchronousCommandBusTest < Test::Unit::TestCase
7
+ def setup
8
+ unit_provider = UnitOfWork::UnitOfWorkProvider.new
9
+ unit_factory = UnitOfWork::UnitOfWorkFactory.new unit_provider
10
+
11
+ @bus = AsynchronousCommandBus.new unit_factory
12
+ @bus.thread_pool = Thread.pool 2
13
+ end
14
+
15
+ should 'be able to dispatch commands asynchronously using a thread pool' do
16
+ x = 5 # Number of commands to dispatch
17
+
18
+ command = CommandMessage.as_message TestCommand.new
19
+ handler = TestAsyncHandler.new x
20
+
21
+ @bus.subscribe TestCommand, handler
22
+
23
+ x.times do
24
+ @bus.dispatch command
25
+ end
26
+
27
+ wait_until do
28
+ handler.latch.count == 0
29
+ end
30
+
31
+ @bus.shutdown
32
+ end
33
+ end
34
+
35
+ class TestAsyncHandler
36
+ attr_reader :latch
37
+
38
+ def initialize(x)
39
+ @latch = CountdownLatch.new x
40
+ end
41
+
42
+ def handle(command, unit)
43
+ @latch.countdown!
44
+ end
45
+ end
46
+
47
+ class TestCommand; end
48
+ end
49
+ end
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  module Synapse
4
4
  module Command
5
5
  class DuplicationFilterTest < Test::Unit::TestCase
6
- def test_filter
6
+ should 'record commands so that duplication can be detected' do
7
7
  recorder = DuplicationRecorder.new
8
8
  filter = DuplicationFilter.new recorder
9
9
 
@@ -17,7 +17,7 @@ module Synapse
17
17
  end
18
18
 
19
19
  class DuplicationCleanupInterceptorTest < Test::Unit::TestCase
20
- def test_intercept
20
+ should 'forget recorded commands only if a transient error occurs' do
21
21
  recorder = DuplicationRecorder.new
22
22
  interceptor = DuplicationCleanupInterceptor.new recorder
23
23
 
@@ -0,0 +1,42 @@
1
+ require 'test_helper'
2
+ require 'eventmachine'
3
+
4
+ module Synapse
5
+ module Command
6
+ class IntervalRetrySchedulerTest < Test::Unit::TestCase
7
+
8
+ def setup
9
+ @maxRetries = 3
10
+
11
+ @scheduler = IntervalRetryScheduler.new 5.0, @maxRetries
12
+ @command = CommandMessage.as_message Object.new
13
+ @dispatcher = proc {}
14
+ end
15
+
16
+ should 'schedule up until the max number of retries' do
17
+ failures = []
18
+
19
+ mock(EventMachine).add_timer(5.0, &@dispatcher).times(@maxRetries)
20
+
21
+ @maxRetries.times do
22
+ failures.push RuntimeError.new
23
+ assert @scheduler.schedule @command, failures, @dispatcher
24
+ end
25
+
26
+ failures.push RuntimeError.new
27
+ refute @scheduler.schedule @command, failures, @dispatcher
28
+ end
29
+
30
+ should 'not schedule if failure was explicitly non-transient' do
31
+ failure = CommandValidationError.new
32
+ refute @scheduler.schedule @command, [failure], @dispatcher
33
+ end
34
+
35
+ should 'not schedule if cause of failure was explicitly non-transient' do
36
+ failure = CommandExecutionError.new NonTransientError.new
37
+ refute @scheduler.schedule @command, [failure], @dispatcher
38
+ end
39
+
40
+ end # IntervalRetrySchedulerTest
41
+ end # Command
42
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module Command
5
+ class RetryingCallbackTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @command = CommandMessage.as_message Object.new
9
+ @delegate = Object.new
10
+ @retry_scheduler = Object.new
11
+ @command_bus = Object.new
12
+
13
+ @callback = RetryingCallback.new @delegate, @command, @retry_scheduler, @command_bus
14
+ end
15
+
16
+ should 'notify delegate of a successful dispatch' do
17
+ result = Object.new
18
+
19
+ mock(@delegate).on_success(result)
20
+ @callback.on_success result
21
+ end
22
+
23
+ should 'not notify delegate of a failed dispatch when rescheduling' do
24
+ failures = [RuntimeError.new]
25
+
26
+ mock(@command_bus).dispatch_with_callback(@command, @callback)
27
+
28
+ mock(@retry_scheduler).schedule(@command, failures, is_a(Proc)) do |_, _, dispatcher|
29
+ dispatcher.call
30
+ true
31
+ end
32
+
33
+ @callback.on_failure failures.last
34
+ end
35
+
36
+ should 'notify delegate of a failed dispatch when not rescheduling' do
37
+ failures = [RuntimeError.new]
38
+
39
+ mock(@retry_scheduler).schedule(@command, failures, is_a(Proc)) do
40
+ false
41
+ end
42
+ mock(@delegate).on_failure(failures.last)
43
+
44
+ @callback.on_failure failures.last
45
+ end
46
+
47
+ should 'notify delegate of a failed dispatch on an unchecked exception' do
48
+ failure = ThreadError.new
49
+
50
+ mock(@delegate).on_failure(failure)
51
+
52
+ @callback.on_failure failure
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -4,20 +4,54 @@ module Synapse
4
4
  module Command
5
5
 
6
6
  class CommandGatewayTest < Test::Unit::TestCase
7
- def test_send
8
- command_bus = Object.new
9
- gateway = CommandGateway.new command_bus
7
+ def setup
8
+ @command_bus = Object.new
9
+ @gateway = CommandGateway.new @command_bus
10
+ end
10
11
 
12
+ should 'wrap bare command objects in command messages before dispatch' do
11
13
  command = Object.new
12
14
  command_message = CommandMessage.build do |builder|
13
15
  builder.payload = command
14
16
  end
15
17
 
16
- mock(command_bus).dispatch(is_a(CommandMessage)).ordered
17
- mock(command_bus).dispatch(command_message).ordered
18
+ mock(@command_bus).dispatch_with_callback(is_a(CommandMessage), anything).ordered
19
+ mock(@command_bus).dispatch_with_callback(command_message, anything).ordered
20
+
21
+ @gateway.send command
22
+ @gateway.send command_message
23
+ end
24
+
25
+ should 'wrap callback in RetryingCallback if RetryScheduler' do
26
+ @gateway.retry_scheduler = IntervalRetryScheduler.new 3, 3
27
+
28
+ command = Object.new
29
+ callback = CommandCallback.new
30
+
31
+ mock(@command_bus).dispatch_with_callback(is_a(CommandMessage), is_a(RetryingCallback))
32
+
33
+ @gateway.send_with_callback command, callback
34
+ end
35
+
36
+ should 'send a command and wait for it to be dispatched' do
37
+ @gateway.retry_scheduler = IntervalRetryScheduler.new 3, 3
38
+
39
+ command = Object.new
40
+ result = Object.new
41
+
42
+ mock(@command_bus).dispatch_with_callback(is_a(CommandMessage), is_a(RetryingCallback)) do |message, callback|
43
+ callback.on_success result
44
+ end
45
+
46
+ received_result = nil
47
+
48
+ Thread.new do
49
+ received_result = @gateway.send_and_wait(command)
50
+ end
18
51
 
19
- gateway.send command
20
- gateway.send command_message
52
+ wait_until do
53
+ received_result.equal? result
54
+ end
21
55
  end
22
56
  end
23
57
 
@@ -4,7 +4,7 @@ module Synapse
4
4
  module Command
5
5
 
6
6
  class InterceptorChainTest < Test::Unit::TestCase
7
- def test_proceed
7
+ should 'allow interceptors to control the flow of dispatch' do
8
8
  unit = Object.new
9
9
  interceptor = Object.new
10
10
  interceptors = Array.new << interceptor
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module Command
5
+ class CommandMessageTest < Test::Unit::TestCase
6
+ should 'not wrap objects that are already command messages' do
7
+ command = Object.new
8
+ command_message = CommandMessage.build
9
+
10
+ assert_same command_message, CommandMessage.as_message(command_message)
11
+
12
+ wrapped = CommandMessage.as_message(command)
13
+ assert_same command, wrapped.payload
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,7 +4,7 @@ module Synapse
4
4
  module Command
5
5
 
6
6
  class SerializationOptimizingInterceptorTest < Test::Unit::TestCase
7
- def test_intercept
7
+ should 'register a serialization optimizing listener to the current unit of work' do
8
8
  interceptor = SerializationOptimizingInterceptor.new
9
9
 
10
10
  command = CommandMessage.build
@@ -19,7 +19,7 @@ module Synapse
19
19
  end
20
20
 
21
21
  class SerializationOptimizingListenerTest < Test::Unit::TestCase
22
- def test_on_event_registered
22
+ should 'wrap event messages with serialization aware event messages' do
23
23
  listener = SerializationOptimizingListener.new
24
24
  unit = Object.new
25
25
 
@@ -15,7 +15,7 @@ module Synapse
15
15
  end
16
16
  end
17
17
 
18
- def test_dispatch
18
+ should 'dispatch a command message to its registered handler' do
19
19
  handler = Object.new
20
20
  command = CommandMessage.build do |m|
21
21
  m.payload = TestCommand.new
@@ -28,7 +28,7 @@ module Synapse
28
28
  @command_bus.dispatch command
29
29
  end
30
30
 
31
- def test_dispatch_result
31
+ should 'invoke a callback with the return value from a command handler' do
32
32
  handler = Object.new
33
33
  callback = Object.new
34
34
  command = CommandMessage.build do |m|
@@ -47,7 +47,7 @@ module Synapse
47
47
  @command_bus.dispatch_with_callback command, callback
48
48
  end
49
49
 
50
- def test_dispatch_no_handler
50
+ should 'raise an exception when a dispatched command has no registered handler' do
51
51
  command = CommandMessage.build do |m|
52
52
  m.payload = TestCommand.new
53
53
  end
@@ -58,7 +58,7 @@ module Synapse
58
58
  @command_bus.dispatch_with_callback command, callback
59
59
  end
60
60
 
61
- def test_dispatch_rollback_on_exception
61
+ should 'roll back the current unit of work if the command handler raises an exception' do
62
62
  handler = Object.new
63
63
  command = CommandMessage.build do |m|
64
64
  m.payload = TestCommand.new
@@ -81,7 +81,7 @@ module Synapse
81
81
  @command_bus.dispatch_with_callback command, callback
82
82
  end
83
83
 
84
- def test_dispatch_commit_on_exception
84
+ should 'commit the current unit of work if the command handler raises an exception' do
85
85
  handler = Object.new
86
86
  command = CommandMessage.build do |m|
87
87
  m.payload = TestCommand.new
@@ -108,7 +108,7 @@ module Synapse
108
108
  @command_bus.dispatch_with_callback command, callback
109
109
  end
110
110
 
111
- def test_subscribe
111
+ should 'log when a subscribed handler is replaced' do
112
112
  handler = Object.new
113
113
 
114
114
  mock(@logger).debug(anything).ordered
@@ -118,7 +118,7 @@ module Synapse
118
118
  @command_bus.subscribe TestCommand, handler
119
119
  end
120
120
 
121
- def test_unsubscribe
121
+ should 'log when a handler is unsubscribed' do
122
122
  handler_a = Object.new
123
123
  handler_b = Object.new
124
124
 
@@ -5,16 +5,16 @@ module Synapse
5
5
  module Command
6
6
 
7
7
  class ActiveModelValidationFilterTest < Test::Unit::TestCase
8
- def test_filter
8
+ should 'continue if payload of a command message is valid' do
9
9
  message = CommandMessage.build do |m|
10
- m.payload = CreatePersonCommand.new 'River'
10
+ m.payload = CreatePersonCommand.new 'River Tam'
11
11
  end
12
12
 
13
13
  filter = ActiveModelValidationFilter.new
14
14
  filter.filter message
15
15
  end
16
16
 
17
- def test_filter_fails
17
+ should 'raise an exception if payload of a command message is invalid' do
18
18
  message = CommandMessage.build do |m|
19
19
  m.payload = CreatePersonCommand.new nil
20
20
  end