synapse-core 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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