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.
- data/lib/synapse/command/async_command_bus.rb +41 -0
- data/lib/synapse/command/command_callback.rb +49 -2
- data/lib/synapse/command/command_handler.rb +2 -0
- data/lib/synapse/command/filters/validation.rb +2 -2
- data/lib/synapse/command/gateway/interval_retry_scheduler.rb +75 -0
- data/lib/synapse/command/gateway/retry_scheduler.rb +26 -0
- data/lib/synapse/command/gateway/retrying_callback.rb +46 -0
- data/lib/synapse/command/gateway.rb +56 -11
- data/lib/synapse/command/interceptor_chain.rb +2 -2
- data/lib/synapse/command/interceptors/serialization.rb +2 -2
- data/lib/synapse/command/message.rb +16 -0
- data/lib/synapse/command/simple_command_bus.rb +6 -2
- data/lib/synapse/command/wiring.rb +3 -3
- data/lib/synapse/command.rb +7 -1
- data/lib/synapse/common/concurrency/identifier_lock.rb +2 -17
- data/lib/synapse/common/concurrency/public_lock.rb +8 -9
- data/lib/synapse/common/errors.rb +1 -1
- data/lib/synapse/common/message.rb +2 -6
- data/lib/synapse/common.rb +9 -0
- data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +41 -0
- data/lib/synapse/configuration/component/command_bus/gateway.rb +38 -0
- data/lib/synapse/configuration/component/command_bus/simple_command_bus.rb +81 -0
- data/lib/synapse/configuration/component/command_bus.rb +35 -0
- data/lib/synapse/configuration/component/event_bus/simple_event_bus.rb +41 -0
- data/lib/synapse/configuration/component/event_bus.rb +15 -0
- data/lib/synapse/configuration/component/event_sourcing/repository.rb +62 -0
- data/lib/synapse/configuration/component/event_sourcing.rb +15 -0
- data/lib/synapse/configuration/component/repository/locking_repository.rb +85 -0
- data/lib/synapse/configuration/component/repository/simple_repository.rb +31 -0
- data/lib/synapse/configuration/component/repository.rb +15 -0
- data/lib/synapse/configuration/component/serialization/converter_factory.rb +50 -0
- data/lib/synapse/configuration/component/serialization/serializer.rb +90 -0
- data/lib/synapse/configuration/component/serialization.rb +25 -0
- data/lib/synapse/configuration/component/uow/unit_factory.rb +51 -0
- data/lib/synapse/configuration/component/uow.rb +22 -0
- data/lib/synapse/configuration/component/upcasting/upcaster_chain.rb +61 -0
- data/lib/synapse/configuration/component/upcasting.rb +15 -0
- data/lib/synapse/configuration/container.rb +80 -0
- data/lib/synapse/configuration/container_builder.rb +108 -0
- data/lib/synapse/configuration/definition.rb +31 -0
- data/lib/synapse/configuration/definition_builder.rb +138 -0
- data/lib/synapse/configuration/dependent.rb +31 -0
- data/lib/synapse/configuration/ext.rb +35 -0
- data/lib/synapse/configuration.rb +32 -0
- data/lib/synapse/domain/aggregate_root.rb +25 -26
- data/lib/synapse/domain/event_container.rb +5 -5
- data/lib/synapse/domain/message.rb +16 -0
- data/lib/synapse/event_bus/event_listener.rb +2 -0
- data/lib/synapse/event_bus/event_publisher.rb +19 -0
- data/lib/synapse/event_bus/simple_event_bus.rb +21 -30
- data/lib/synapse/event_bus/wiring.rb +5 -4
- data/lib/synapse/event_bus.rb +1 -1
- data/lib/synapse/event_sourcing/aggregate_root.rb +6 -4
- data/lib/synapse/process_manager/container_resource_injector.rb +18 -0
- data/lib/synapse/process_manager/pessimistic_lock_manager.rb +1 -1
- data/lib/synapse/process_manager/process.rb +4 -0
- data/lib/synapse/process_manager/process_factory.rb +3 -3
- data/lib/synapse/process_manager/repository/in_memory.rb +1 -1
- data/lib/synapse/process_manager/wiring/process.rb +6 -6
- data/lib/synapse/process_manager/wiring/process_manager.rb +8 -8
- data/lib/synapse/process_manager.rb +2 -0
- data/lib/synapse/repository/locking.rb +5 -3
- data/lib/synapse/repository/optimistic_lock_manager.rb +2 -7
- data/lib/synapse/repository/pessimistic_lock_manager.rb +3 -3
- data/lib/synapse/repository/repository.rb +2 -2
- data/lib/synapse/repository/simple_repository.rb +69 -0
- data/lib/synapse/repository.rb +1 -0
- data/lib/synapse/serialization/converter/chain.rb +2 -2
- data/lib/synapse/serialization/converter_factory.rb +2 -2
- data/lib/synapse/serialization/lazy_object.rb +2 -2
- data/lib/synapse/serialization/message/serialization_aware_message.rb +3 -3
- data/lib/synapse/serialization/message/serialized_message.rb +6 -10
- data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
- data/lib/synapse/serialization/message/serialized_object_cache.rb +2 -2
- data/lib/synapse/serialization/message/serializer.rb +2 -2
- data/lib/synapse/serialization/revision_resolver.rb +3 -3
- data/lib/synapse/serialization/serialized_object.rb +2 -2
- data/lib/synapse/serialization/serialized_type.rb +2 -2
- data/lib/synapse/serialization/serializer/attribute.rb +2 -2
- data/lib/synapse/serialization/serializer/marshal.rb +2 -2
- data/lib/synapse/serialization/serializer/oj.rb +2 -2
- data/lib/synapse/serialization/serializer/ox.rb +2 -2
- data/lib/synapse/serialization/serializer.rb +2 -2
- data/lib/synapse/uow/factory.rb +2 -2
- data/lib/synapse/uow/listener_collection.rb +2 -2
- data/lib/synapse/uow/nesting.rb +9 -2
- data/lib/synapse/uow/provider.rb +2 -2
- data/lib/synapse/uow/uow.rb +8 -2
- data/lib/synapse/upcasting/{chain.rb → upcaster_chain.rb} +0 -0
- data/lib/synapse/upcasting.rb +1 -1
- data/lib/synapse/version.rb +1 -1
- data/lib/synapse/wiring/message_wiring.rb +31 -0
- data/lib/synapse.rb +2 -14
- data/test/auditing/data_provider_test.rb +2 -2
- data/test/auditing/dispatch_interceptor_test.rb +1 -1
- data/test/auditing/unit_listener_test.rb +3 -3
- data/test/command/async_command_bus_test.rb +49 -0
- data/test/command/duplication_test.rb +2 -2
- data/test/command/gateway/interval_retry_scheduler_test.rb +42 -0
- data/test/command/gateway/retrying_callback_test.rb +57 -0
- data/test/command/gateway_test.rb +41 -7
- data/test/command/interceptor_chain_test.rb +1 -1
- data/test/command/message_test.rb +17 -0
- data/test/command/serialization_test.rb +2 -2
- data/test/command/simple_command_bus_test.rb +7 -7
- data/test/command/validation_test.rb +3 -3
- data/test/command/wiring_test.rb +3 -3
- data/test/common/concurrency/identifier_lock_test.rb +2 -13
- data/test/common/concurrency/public_lock_test.rb +6 -6
- data/test/{duplication_test.rb → common/duplication_test.rb} +3 -3
- data/test/configuration/component/command_bus/async_command_bus_test.rb +36 -0
- data/test/configuration/component/command_bus/simple_command_bus_test.rb +57 -0
- data/test/configuration/component/event_bus/simple_event_bus_test.rb +58 -0
- data/test/configuration/component/serialization/converter_factory_test.rb +48 -0
- data/test/configuration/component/serialization/serializer_test.rb +78 -0
- data/test/configuration/component/uow/unit_factory_test.rb +46 -0
- data/test/configuration/container_builder_test.rb +47 -0
- data/test/configuration/container_test.rb +88 -0
- data/test/configuration/definition_builder_test.rb +126 -0
- data/test/configuration/definition_test.rb +41 -0
- data/test/configuration/dependent_test.rb +30 -0
- data/test/configuration/ext_test.rb +19 -0
- data/test/configuration/fixtures/dependent.rb +10 -0
- data/test/domain/aggregate_root_test.rb +5 -5
- data/test/domain/message_test.rb +15 -3
- data/test/domain/stream_test.rb +2 -2
- data/test/event_bus/publisher_test.rb +29 -0
- data/test/event_bus/wiring_test.rb +1 -1
- data/test/event_sourcing/aggregate_factory_test.rb +12 -6
- data/test/event_sourcing/aggregate_root_test.rb +4 -4
- data/test/event_sourcing/entity_test.rb +10 -9
- data/test/event_sourcing/repository_test.rb +6 -6
- data/test/event_sourcing/storage_listener_test.rb +8 -4
- data/test/event_store/in_memory_test.rb +3 -3
- data/test/process_manager/container_resource_injector_test.rb +19 -0
- data/test/process_manager/correlation_set_test.rb +2 -2
- data/test/process_manager/correlation_test.rb +2 -2
- data/test/process_manager/in_memory_test.rb +3 -3
- data/test/process_manager/process_factory_test.rb +2 -2
- data/test/process_manager/process_test.rb +3 -3
- data/test/process_manager/simple_process_manager_test.rb +5 -5
- data/test/process_manager/wiring/process_manager_test.rb +4 -4
- data/test/process_manager/wiring/process_test.rb +2 -2
- data/test/repository/locking_test.rb +4 -4
- data/test/repository/optimistic_test.rb +2 -2
- data/test/repository/pessimistic_test.rb +1 -1
- data/test/repository/simple_repository_test.rb +79 -0
- data/test/support/countdown_latch.rb +18 -0
- data/test/test_helper.rb +6 -3
- data/test/upcasting/data_test.rb +31 -0
- metadata +84 -25
- data/lib/synapse/event_bus/event_listener_proxy.rb +0 -12
- data/lib/synapse/partitioning/memory_queue_reader.rb +0 -31
- data/lib/synapse/partitioning/memory_queue_writer.rb +0 -19
- data/lib/synapse/partitioning/message_receipt.rb +0 -25
- data/lib/synapse/partitioning/packing/json_packer.rb +0 -93
- data/lib/synapse/partitioning/packing/json_unpacker.rb +0 -83
- data/lib/synapse/partitioning/packing.rb +0 -27
- data/lib/synapse/partitioning/queue_reader.rb +0 -32
- data/lib/synapse/partitioning/queue_writer.rb +0 -17
- data/lib/synapse/partitioning.rb +0 -18
- data/test/partitioning/memory_test.rb +0 -34
- 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
|
-
|
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
|
@@ -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
|
-
|
28
|
+
send_with_callback command, CommandCallback.new
|
17
29
|
end
|
18
30
|
|
19
|
-
|
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
|
-
# @
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
@@ -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 =
|
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
|
data/lib/synapse/command.rb
CHANGED
@@ -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
|
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
|
-
@
|
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
|
-
|
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
|
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
|