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.
- 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
|