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
@@ -0,0 +1,41 @@
1
+ require 'thread/pool'
2
+
3
+ module Synapse
4
+ module Command
5
+ # Command bus that uses a thread pool to asynchronously execute commands, invoking the given
6
+ # callback when execution is completed or resulted in an error
7
+ #
8
+ # @todo Look into non-blocking circular buffers or LMAX Disruptor
9
+ class AsynchronousCommandBus < SimpleCommandBus
10
+ # Pool of dispatching threads backed by a queue
11
+ # @return [Thread::Pool]
12
+ attr_accessor :thread_pool
13
+
14
+ # @api public
15
+ # @param [CommandMessage] command
16
+ # @param [CommandCallback] callback
17
+ # @return [undefined]
18
+ def dispatch_with_callback(command, callback)
19
+ @thread_pool.process do
20
+ super command, callback
21
+ end
22
+ end
23
+
24
+ # Shuts down the command bus, waiting until all tasks are finished
25
+ #
26
+ # @api public
27
+ # @return [undefined]
28
+ def shutdown
29
+ @thread_pool.shutdown
30
+ end
31
+
32
+ # Shuts down the command bus without waiting for tasks to finish
33
+ #
34
+ # @api public
35
+ # @return [undefined]
36
+ def shutdown!
37
+ @thread_pool.shutdown!
38
+ end
39
+ end # AsynchronousCommandBus
40
+ end # Command
41
+ end
@@ -13,6 +13,53 @@ module Synapse
13
13
  # @param [Exception] exception The cause of the failure
14
14
  # @return [undefined]
15
15
  def on_failure(exception); end
16
- end
17
- end
16
+ end # CommandCallback
17
+
18
+ # Callback that provides a deferred result or exception from the execution of a command
19
+ class FutureCallback < CommandCallback
20
+ def initialize
21
+ @mutex = Mutex.new
22
+ @condition = ConditionVariable.new
23
+ end
24
+
25
+ # @raise [Exception] If an exception occured during command execution
26
+ # @param [Float] timeout
27
+ # @return [Object] The result from the command handler
28
+ def result(timeout = nil)
29
+ @mutex.synchronize do
30
+ unless @dispatched
31
+ @condition.wait @mutex, timeout
32
+ end
33
+
34
+ if @exception
35
+ raise @exception
36
+ end
37
+
38
+ @result
39
+ end
40
+ end
41
+
42
+ # @param [Object] result The result from the command handler
43
+ # @return [undefined]
44
+ def on_success(result)
45
+ @mutex.synchronize do
46
+ @dispatched = true
47
+ @result = result
48
+
49
+ @condition.broadcast
50
+ end
51
+ end
52
+
53
+ # @param [Exception] exception The cause of the failure
54
+ # @return [undefined]
55
+ def on_failure(exception)
56
+ @mutex.synchronize do
57
+ @dispatched = true
58
+ @exception = exception
59
+
60
+ @condition.broadcast
61
+ end
62
+ end
63
+ end # FutureCallback
64
+ end # Command
18
65
  end
@@ -1,6 +1,8 @@
1
1
  module Synapse
2
2
  module Command
3
3
  # Mixin for an object capable of handling commands
4
+ #
5
+ # Consider using the command handler mixin that uses message wiring.
4
6
  module CommandHandler
5
7
  # Handles the given command
6
8
  #
@@ -15,7 +15,7 @@ module Synapse
15
15
 
16
16
  command
17
17
  end
18
- end
18
+ end # ActiveModelValidationFilter
19
19
 
20
20
  # Raised when a command with ActiveModel doesn't pass validation
21
21
  class ActiveModelValidationError < CommandValidationError
@@ -27,6 +27,6 @@ module Synapse
27
27
  def initialize(errors)
28
28
  @errors = errors
29
29
  end
30
- end
30
+ end # ActiveModelValidationError
31
31
  end
32
32
  end
@@ -0,0 +1,75 @@
1
+ module Synapse
2
+ module Command
3
+ # Implementation of a retry scheduler that retries commands at regular intervals
4
+ #
5
+ # If the last failure is explicitly non-transient exception or the number of failures reaches
6
+ # the maximum number of retries, the command will not be scheduled for retry.
7
+ #
8
+ # This implementation uses EventMachine to schedule one-shot timers.
9
+ class IntervalRetryScheduler < RetryScheduler
10
+ # @param [Float] interval
11
+ # @param [Integer] maxRetries
12
+ # @return [undefined]
13
+ def initialize(interval, maxRetries)
14
+ @interval = interval
15
+ @maxRetries = maxRetries
16
+
17
+ @logger = Logging.logger[self.class]
18
+ end
19
+
20
+ # @param [CommandMessage] command
21
+ # @param [Array] failures
22
+ # @param [Proc] dispatcher
23
+ # @return [Boolean]
24
+ def schedule(command, failures, dispatcher)
25
+ lastFailure = failures.last
26
+
27
+ if explicitly_non_transient? lastFailure
28
+ @logger.info 'Dispatch of command [%s] [%s] resulted in non-transient exception' %
29
+ [command.payload_type, command.id]
30
+
31
+ return false
32
+ end
33
+
34
+ failureCount = failures.size
35
+
36
+ if failureCount > @maxRetries
37
+ @logger.info 'Dispatch of command [%s] [%s] resulted in exception [%s] times' %
38
+ [command.payload_type, command.id, failureCount]
39
+
40
+ return false
41
+ end
42
+
43
+ if @logger.info?
44
+ @logger.info 'Dispatch of command [%s] [%s] resulted in exception; will retry up to [%s] more times' %
45
+ [command.payload_type, command.id, @maxRetries - failureCount]
46
+ end
47
+
48
+ perform_schedule command, dispatcher
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ # @param [Exception] exception
56
+ # @return [Boolean]
57
+ def explicitly_non_transient?(exception)
58
+ return true if exception.is_a? NonTransientError
59
+
60
+ if exception.respond_to? :cause
61
+ explicitly_non_transient? exception.cause
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ # @param [CommandMessage] command
68
+ # @param [Proc] dispatcher
69
+ # @return [undefined]
70
+ def perform_schedule(command, dispatcher)
71
+ EventMachine.add_timer @interval, &dispatcher
72
+ end
73
+ end # IntervalRetryScheduler
74
+ end # Command
75
+ end
@@ -0,0 +1,26 @@
1
+ module Synapse
2
+ module Command
3
+ # Represents a mechanism that decides whether or not to dispatch a command when previous
4
+ # attempts resulted in an exception being raised.
5
+ #
6
+ # @abstract
7
+ class RetryScheduler
8
+ # Inspects the given command message that failed due to an exception
9
+ #
10
+ # The given list of failures contains exceptions that have occured each time the command
11
+ # was dispatched by the command bus. It includes the most recent failure at the end of
12
+ # the list.
13
+ #
14
+ # The return value of this method indicates whether or not the command has been scheduled
15
+ # for a retry. If it has, the callback for the command should not be invoked. Otherwise,
16
+ # the failure will be interpreted as terminal and the callback will be invoked with the
17
+ # last recorded failure.
18
+ #
19
+ # @param [CommandMessage] command
20
+ # @param [Array] failures
21
+ # @param [Proc] dispatcher
22
+ # @return [Boolean]
23
+ def schedule(command, failures, dispatcher); end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ module Synapse
2
+ module Command
3
+ # Implementation of a callback that will retry the dispatch of a command if execution results
4
+ # in an exception
5
+ #
6
+ # This callback is not meant to be used directly. Use a command gateway implementation instead.
7
+ class RetryingCallback < CommandCallback
8
+ # @param [CommandCallback] delegate
9
+ # @param [CommandMessage] command
10
+ # @param [RetryScheduler] retry_scheduler
11
+ # @param [CommandBus] command_bus
12
+ # @return [undefined]
13
+ def initialize(delegate, command, retry_scheduler, command_bus)
14
+ @command = command
15
+ @delegate = delegate
16
+ @retry_scheduler = retry_scheduler
17
+
18
+ @failures = Array.new
19
+ @dispatcher = proc do
20
+ command_bus.dispatch_with_callback command, self
21
+ end
22
+ end
23
+
24
+ # @param [Object] result The result from the command handler
25
+ # @return [undefined]
26
+ def on_success(result)
27
+ @delegate.on_success result
28
+ end
29
+
30
+ # @param [Exception] exception The cause of the failure
31
+ # @return [undefined]
32
+ def on_failure(exception)
33
+ @failures.push exception
34
+
35
+ begin
36
+ unless exception.is_a? RuntimeError and
37
+ @retry_scheduler.schedule @command, @failures, @dispatcher
38
+ @delegate.on_failure exception
39
+ end
40
+ rescue
41
+ @delegate.on_failure $!
42
+ end
43
+ end
44
+ end # RetryingCallback
45
+ end # Command
46
+ end
@@ -1,34 +1,79 @@
1
1
  module Synapse
2
2
  module Command
3
3
  # Simplified interface to the command bus
4
+ # @api public
4
5
  class CommandGateway
6
+ # @return [Array]
7
+ attr_reader :filters
8
+
9
+ # @return [RetryScheduler]
10
+ attr_accessor :retry_scheduler
11
+
5
12
  # @param [CommandBus] command_bus
6
13
  # @return [undefined]
7
14
  def initialize(command_bus)
8
15
  @command_bus = command_bus
16
+ @filters = Array.new
9
17
  end
10
18
 
11
19
  # Fire and forget method of sending a command to the command bus
12
20
  #
21
+ # If the given command is a bare object, it will be wrapped in a command message before
22
+ # being dispatched on the command bus.
23
+ #
24
+ # @api public
13
25
  # @param [Object] command
14
26
  # @return [undefined]
15
27
  def send(command)
16
- @command_bus.dispatch(as_command_message(command))
28
+ send_with_callback command, CommandCallback.new
17
29
  end
18
30
 
19
- private
20
-
31
+ # Sends the given command
32
+ #
33
+ # If the given command is a bare object, it will be wrapped in a command message before
34
+ # being dispatched on the command bus.
35
+ #
36
+ # @api public
21
37
  # @param [Object] command
22
- # @return [CommandMessage]
23
- def as_command_message(command)
24
- unless command.is_a? CommandMessage
25
- command = CommandMessage.build do |builder|
26
- builder.payload = command
27
- end
38
+ # @param [CommandCallback] callback
39
+ # @return [undefined]
40
+ def send_with_callback(command, callback)
41
+ command = process_with_filters(CommandMessage.as_message(command))
42
+
43
+ if @retry_scheduler
44
+ callback = RetryingCallback.new callback, command, @retry_scheduler, @command_bus
28
45
  end
29
46
 
47
+ @command_bus.dispatch_with_callback(command, callback)
48
+ end
49
+
50
+ # Sends the given command and blocks indefinitely until the result of the execution is
51
+ # provided, the timeout is created or the thread is interrupted.
52
+ #
53
+ # If the given command is a bare object, it will be wrapped in a command message before
54
+ # being dispatched on the command bus.
55
+ #
56
+ # @api public
57
+ # @raise [CommandExecutionError] If an error occured while executing the command
58
+ # @param [Object] command
59
+ # @param [Float] timeout
60
+ # @return [Object] The return value from the command handler
61
+ def send_and_wait(command, timeout = nil)
62
+ callback = FutureCallback.new
63
+ send_with_callback command, callback
64
+ callback.result timeout
65
+ end
66
+
67
+ protected
68
+
69
+ # @param [CommandMessage] command
70
+ # @return [CommandMessage] The message to dispatch
71
+ def process_with_filters(command)
72
+ @filters.each do |filter|
73
+ command = filter.filter command
74
+ end
30
75
  command
31
76
  end
32
- end
33
- end
77
+ end # CommandGateway
78
+ end # Command
34
79
  end
@@ -26,6 +26,6 @@ module Synapse
26
26
  @handler.handle command, @unit
27
27
  end
28
28
  end
29
- end
30
- end
29
+ end # InterceptorChain
30
+ end # Command
31
31
  end
@@ -16,7 +16,7 @@ module Synapse
16
16
  unit.register_listener @listener
17
17
  chain.proceed command
18
18
  end
19
- end
19
+ end # SerializationOptimizingInterceptor
20
20
 
21
21
  # @api private
22
22
  class SerializationOptimizingListener < UnitOfWork::UnitOfWorkListener
@@ -30,6 +30,6 @@ module Synapse
30
30
  Serialization::SerializationAwareEventMessage.decorate event
31
31
  end
32
32
  end
33
- end
33
+ end # SerializationOptimizingListener
34
34
  end
35
35
  end
@@ -6,6 +6,22 @@ module Synapse
6
6
  def self.builder
7
7
  CommandMessageBuilder
8
8
  end
9
+
10
+ # Creates a command message using the given command object
11
+ #
12
+ # If the given object is an command message, it will be returned unchanged.
13
+ #
14
+ # @param [Object] command
15
+ # @return [CommandMessage]
16
+ def self.as_message(command)
17
+ unless command.is_a? CommandMessage
18
+ command = self.build do |builder|
19
+ builder.payload = command
20
+ end
21
+ end
22
+
23
+ command
24
+ end
9
25
  end
10
26
 
11
27
  # Message builder capable of producing CommandMessage instances
@@ -23,12 +23,14 @@ module Synapse
23
23
  @unit_factory = unit_factory
24
24
  end
25
25
 
26
+ # @api public
26
27
  # @param [CommandMessage] command
27
28
  # @return [undefined]
28
29
  def dispatch(command)
29
30
  dispatch_with_callback command, CommandCallback.new
30
31
  end
31
32
 
33
+ # @api public
32
34
  # @param [CommandMessage] command
33
35
  # @param [CommandCallback] callback
34
36
  # @return [undefined]
@@ -45,6 +47,7 @@ module Synapse
45
47
  end
46
48
  end
47
49
 
50
+ # @api public
48
51
  # @param [Class] command_type
49
52
  # @param [CommandHandler] handler
50
53
  # @return [undefined]
@@ -61,6 +64,7 @@ module Synapse
61
64
  @handlers.store command_type, handler
62
65
  end
63
66
 
67
+ # @api public
64
68
  # @param [Class] command_type
65
69
  # @param [CommandHandler] handler
66
70
  # @return [undefined]
@@ -133,6 +137,6 @@ module Synapse
133
137
  raise NoHandlerError, 'No handler subscribed for command [%s]' % type
134
138
  end
135
139
  end
136
- end
137
- end
140
+ end # SimpleCommandBus
141
+ end # Command
138
142
  end
@@ -14,7 +14,7 @@ module Synapse
14
14
  # @param [UnitOfWork] current_unit Current unit of work
15
15
  # @return [Object] The result of handling the given command
16
16
  def handle(command, current_unit)
17
- wire = self.wire_registry.wire_for command.payload_type
17
+ wire = wire_registry.wire_for command.payload_type
18
18
 
19
19
  unless wire
20
20
  raise ArgumentError, 'Not capable of handling [%s] commands' % command.payload_type
@@ -42,6 +42,6 @@ module Synapse
42
42
  command_bus.unsubscribe type, self
43
43
  end
44
44
  end
45
- end
46
- end
45
+ end # WiringCommandHandler
46
+ end # Command
47
47
  end
@@ -2,6 +2,8 @@ module Synapse
2
2
  module Command
3
3
  extend ActiveSupport::Autoload
4
4
 
5
+ autoload :AsynchronousCommandBus, 'synapse/command/async_command_bus'
6
+
5
7
  # Optional filters and interceptors
6
8
  autoload_at 'synapse/command/duplication' do
7
9
  autoload :DuplicationFilter
@@ -26,9 +28,13 @@ require 'synapse/command/command_filter'
26
28
  require 'synapse/command/command_handler'
27
29
  require 'synapse/command/dispatch_interceptor'
28
30
  require 'synapse/command/errors'
29
- require 'synapse/command/gateway'
30
31
  require 'synapse/command/interceptor_chain'
31
32
  require 'synapse/command/message'
32
33
  require 'synapse/command/rollback_policy'
33
34
  require 'synapse/command/simple_command_bus'
34
35
  require 'synapse/command/wiring'
36
+
37
+ require 'synapse/command/gateway'
38
+ require 'synapse/command/gateway/retry_scheduler'
39
+ require 'synapse/command/gateway/interval_retry_scheduler'
40
+ require 'synapse/command/gateway/retrying_callback'
@@ -1,4 +1,5 @@
1
1
  module Synapse
2
+ # Generic implementation of a lock that can be used to lock an identifier for use
2
3
  # @todo Deadlock detection
3
4
  class IdentifierLock
4
5
  # @return [undefined]
@@ -34,7 +35,6 @@ module Synapse
34
35
  end
35
36
 
36
37
  lock_for(identifier).unlock
37
- dispose_if_unused(identifier)
38
38
  end
39
39
 
40
40
  private
@@ -52,20 +52,5 @@ module Synapse
52
52
  def lock_available?(identifier)
53
53
  @identifiers.has_key? identifier
54
54
  end
55
-
56
- # Disposes of the lock for the given identifier if it has no threads waiting for it
57
- #
58
- # @param [String] identifier
59
- # @return [undefined]
60
- def dispose_if_unused(identifier)
61
- lock = lock_for identifier
62
- if lock.try_lock
63
- @lock.synchronize do
64
- @identifiers.delete identifier
65
- end
66
-
67
- lock.unlock
68
- end
69
- end
70
55
  end # IdentifierLock
71
- end # Synapse
56
+ end
@@ -1,6 +1,5 @@
1
- require 'monitor'
2
-
3
1
  module Synapse
2
+ # Lock that tracks the thread owning the lock and any waiting threads
4
3
  class PublicLock
5
4
  # @return [Thread] The current owner of the thread, if any
6
5
  attr_reader :owner
@@ -11,7 +10,6 @@ module Synapse
11
10
  # @return [undefined]
12
11
  def initialize
13
12
  @mutex = Mutex.new
14
- @condition = ConditionVariable.new
15
13
  @waiting = Array.new
16
14
  end
17
15
 
@@ -50,9 +48,10 @@ module Synapse
50
48
  end
51
49
 
52
50
  while @owner
51
+ @waiting.push Thread.current
52
+
53
53
  begin
54
- @waiting.push Thread.current
55
- @condition.wait @mutex
54
+ @mutex.sleep
56
55
  ensure
57
56
  @waiting.delete Thread.current
58
57
  end
@@ -68,7 +67,9 @@ module Synapse
68
67
  @mutex.synchronize do
69
68
  if @owner == Thread.current
70
69
  @owner = nil
71
- @condition.signal
70
+
71
+ first_waiter = @waiting.first
72
+ first_waiter.wakeup if first_waiter
72
73
  else
73
74
  raise ThreadError, 'Lock is not owned by the current thread'
74
75
  end
@@ -83,9 +84,7 @@ module Synapse
83
84
  raise ThreadError, 'Lock is already owned by the current thread'
84
85
  end
85
86
 
86
- if @owner
87
- return false
88
- end
87
+ return false if @owner
89
88
 
90
89
  @owner = Thread.current
91
90
  end
@@ -5,7 +5,7 @@ module Synapse
5
5
  # Raised when an error has occured that resulted from misconfiguration
6
6
  class ConfigurationError < SynapseError; end
7
7
 
8
- # Raised when an error has occured that cannot be resolved with intervention
8
+ # Raised when an error has occured that cannot be resolved without intervention
9
9
  class NonTransientError < SynapseError; end
10
10
 
11
11
  # Raised when an error has occured that might be resolved by retrying the operation
@@ -47,9 +47,7 @@ module Synapse
47
47
  # @param [Hash] additional_metadata
48
48
  # @return [Message]
49
49
  def and_metadata(additional_metadata)
50
- if additional_metadata.empty?
51
- return self
52
- end
50
+ return self if additional_metadata.empty?
53
51
 
54
52
  builder = self.class.builder.new
55
53
  build_duplicate builder, @metadata.merge(additional_metadata)
@@ -61,9 +59,7 @@ module Synapse
61
59
  # @param [Hash] replacement_metadata
62
60
  # @return [Message]
63
61
  def with_metadata(replacement_metadata)
64
- if @metadata == replacement_metadata
65
- return self
66
- end
62
+ return self if @metadata == replacement_metadata
67
63
 
68
64
  builder = self.class.builder.new
69
65
  build_duplicate builder, replacement_metadata
@@ -0,0 +1,9 @@
1
+ require 'synapse/common/errors'
2
+ require 'synapse/common/identifier'
3
+ require 'synapse/common/message'
4
+ require 'synapse/common/message_builder'
5
+
6
+ require 'synapse/common/concurrency/identifier_lock'
7
+ require 'synapse/common/concurrency/public_lock'
8
+
9
+ require 'synapse/common/duplication'
@@ -0,0 +1,41 @@
1
+ module Synapse
2
+ module Configuration
3
+ # Definition builder used to build an asynchronous command bus
4
+ #
5
+ # @see SimpleCommandBusDefinitionBuilder For additional options
6
+ #
7
+ # @example The minimum possible effort to build an asynchronous command bus
8
+ # async_command_bus
9
+ #
10
+ # @example Create an asynchronous command bus with a custom thread count
11
+ # async_command_bus do
12
+ # use_threads 8, 12
13
+ # end
14
+ class AsynchronousCommandBusDefinitionBuilder < SimpleCommandBusDefinitionBuilder
15
+ # @param [Integer] min_threads
16
+ # @param [Integer] max_threads
17
+ # @return [undefined]
18
+ def use_threads(min_threads, max_threads = nil)
19
+ @min_threads = min_threads
20
+ @max_threads = max_threads
21
+ end
22
+
23
+ protected
24
+
25
+ # @return [undefined]
26
+ def populate_defaults
27
+ super
28
+ use_threads 4
29
+ end
30
+
31
+ # @param [UnitOfWorkFactory] unit_factory
32
+ # @return [AsynchronousCommandBus]
33
+ def create_command_bus(unit_factory)
34
+ command_bus = Command::AsynchronousCommandBus.new unit_factory
35
+ command_bus.thread_pool = Thread.pool @min_threads, @max_threads
36
+
37
+ command_bus
38
+ end
39
+ end # AsynchronousCommandBusDefinitionBuilder
40
+ end # Configuration
41
+ end