synapse-core 0.5.6 → 0.6.0

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/lib/synapse-core.rb +31 -1
  3. data/lib/synapse/command.rb +1 -0
  4. data/lib/synapse/command/callbacks/future.rb +3 -3
  5. data/lib/synapse/command/callbacks/void.rb +14 -0
  6. data/lib/synapse/command/command_bus.rb +2 -2
  7. data/lib/synapse/command/command_callback.rb +9 -2
  8. data/lib/synapse/command/gateway.rb +1 -1
  9. data/lib/synapse/command/gateway/interval_retry_scheduler.rb +6 -6
  10. data/lib/synapse/command/gateway/retry_scheduler.rb +7 -4
  11. data/lib/synapse/command/interceptor_chain.rb +1 -1
  12. data/lib/synapse/command/interceptors/serialization.rb +2 -1
  13. data/lib/synapse/command/simple_command_bus.rb +23 -34
  14. data/lib/synapse/common.rb +2 -2
  15. data/lib/synapse/common/concurrency/disposable_lock.rb +157 -0
  16. data/lib/synapse/common/concurrency/identifier_lock_manager.rb +164 -0
  17. data/lib/synapse/common/duplication.rb +4 -4
  18. data/lib/synapse/common/errors.rb +5 -0
  19. data/lib/synapse/configuration/container.rb +1 -1
  20. data/lib/synapse/configuration/container_builder.rb +1 -1
  21. data/lib/synapse/configuration/definition.rb +1 -1
  22. data/lib/synapse/domain/aggregate_root.rb +1 -1
  23. data/lib/synapse/domain/simple_stream.rb +3 -5
  24. data/lib/synapse/domain/stream.rb +2 -2
  25. data/lib/synapse/event_bus/event_bus.rb +2 -2
  26. data/lib/synapse/event_bus/simple_event_bus.rb +5 -6
  27. data/lib/synapse/event_sourcing/caching.rb +1 -1
  28. data/lib/synapse/event_sourcing/entity.rb +4 -2
  29. data/lib/synapse/event_sourcing/repository.rb +9 -13
  30. data/lib/synapse/event_sourcing/snapshot/taker.rb +1 -1
  31. data/lib/synapse/mapping/mapping.rb +1 -1
  32. data/lib/synapse/process_manager/correlation.rb +3 -29
  33. data/lib/synapse/process_manager/pessimistic_lock_manager.rb +3 -3
  34. data/lib/synapse/process_manager/process.rb +2 -6
  35. data/lib/synapse/process_manager/process_manager.rb +1 -1
  36. data/lib/synapse/process_manager/process_repository.rb +1 -1
  37. data/lib/synapse/process_manager/repository/in_memory.rb +5 -4
  38. data/lib/synapse/process_manager/simple_process_manager.rb +2 -2
  39. data/lib/synapse/repository/errors.rb +2 -2
  40. data/lib/synapse/repository/locking.rb +48 -0
  41. data/lib/synapse/repository/optimistic_lock_manager.rb +10 -9
  42. data/lib/synapse/repository/pessimistic_lock_manager.rb +5 -4
  43. data/lib/synapse/repository/repository.rb +1 -1
  44. data/lib/synapse/repository/simple_repository.rb +8 -7
  45. data/lib/synapse/serialization/converter.rb +3 -0
  46. data/lib/synapse/serialization/converter_factory.rb +6 -5
  47. data/lib/synapse/serialization/serialized_object.rb +4 -4
  48. data/lib/synapse/serialization/serialized_type.rb +4 -4
  49. data/lib/synapse/uow/listener_collection.rb +12 -9
  50. data/lib/synapse/uow/provider.rb +1 -1
  51. data/lib/synapse/uow/uow.rb +1 -1
  52. data/lib/synapse/upcasting/upcaster_chain.rb +1 -1
  53. data/lib/synapse/version.rb +1 -1
  54. data/test/command/serialization_test.rb +5 -2
  55. data/test/command/simple_command_bus_test.rb +9 -16
  56. data/test/command/validation_test.rb +1 -1
  57. data/test/common/concurrency/identifier_lock_manager_test.rb +137 -0
  58. data/test/configuration/component/serialization/converter_factory_test.rb +2 -2
  59. data/test/event_sourcing/repository_test.rb +18 -0
  60. data/test/repository/simple_repository_test.rb +42 -10
  61. data/test/test_helper.rb +3 -4
  62. metadata +25 -29
  63. data/lib/synapse.rb +0 -34
  64. data/lib/synapse/common/concurrency/identifier_lock.rb +0 -56
  65. data/lib/synapse/common/concurrency/public_lock.rb +0 -95
  66. data/lib/synapse/event_bus/clustering/cluster.rb +0 -10
  67. data/lib/synapse/event_bus/clustering/event_bus.rb +0 -55
  68. data/lib/synapse/event_bus/clustering/selector.rb +0 -14
  69. data/lib/synapse/rails/injection_helper.rb +0 -23
  70. data/lib/synapse/railtie.rb +0 -17
  71. data/test/common/concurrency/identifier_lock_test.rb +0 -25
  72. data/test/common/concurrency/public_lock_test.rb +0 -83
  73. data/test/process_manager/correlation_test.rb +0 -24
  74. data/test/rails/injection_helper_test.rb +0 -27
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ecce43088ffbcd564924205697ffcf99a1b5c7d
4
+ data.tar.gz: 7366f73d7fb0ff57bb7f786b80615beaa6de0ccc
5
+ SHA512:
6
+ metadata.gz: 74a8511d2dcff70321d63c0dc54c6c170cf63bb2c710e70bebdbbc1996bd81afb8e5cc799d7d7f99240a937b806924239ae0a5e2ca719c37191e73dcba8efb6d
7
+ data.tar.gz: 7d2af413072cd726cdbab5c9775692a691c7505c12b4b2ed9a872ceec4b335fd26d788b330c00f9c8740e5a4675d3ebb48d7a8ac5ce0b5a80a4c22b676d0ce0c
data/lib/synapse-core.rb CHANGED
@@ -1 +1,31 @@
1
- require 'synapse'
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'atomic'
4
+ require 'contender'
5
+ require 'forwardable'
6
+ require 'logging'
7
+ require 'ref'
8
+ require 'set'
9
+
10
+ require 'synapse/common'
11
+ require 'synapse/version'
12
+
13
+ module Synapse
14
+ # Core components
15
+ autoload :Command, 'synapse/command'
16
+ autoload :Domain, 'synapse/domain'
17
+ autoload :EventBus, 'synapse/event_bus'
18
+ autoload :Mapping, 'synapse/mapping'
19
+ autoload :Repository, 'synapse/repository'
20
+ autoload :Serialization, 'synapse/serialization'
21
+ autoload :UnitOfWork, 'synapse/uow'
22
+
23
+ # Optional components
24
+ autoload :Auditing, 'synapse/auditing'
25
+ autoload :Configuration, 'synapse/configuration'
26
+ autoload :EventSourcing, 'synapse/event_sourcing'
27
+ autoload :EventStore, 'synapse/event_store'
28
+ autoload :ProcessManager, 'synapse/process_manager'
29
+ autoload :Upcasting, 'synapse/upcasting'
30
+ end
31
+
@@ -40,3 +40,4 @@ require 'synapse/command/gateway/interval_retry_scheduler'
40
40
  require 'synapse/command/gateway/retrying_callback'
41
41
 
42
42
  require 'synapse/command/callbacks/future'
43
+ require 'synapse/command/callbacks/void'
@@ -5,6 +5,8 @@ module Synapse
5
5
  def initialize
6
6
  @mutex = Mutex.new
7
7
  @condition = ConditionVariable.new
8
+
9
+ @dispatched = false
8
10
  end
9
11
 
10
12
  # @raise [Exception] If an exception occured during command execution
@@ -16,9 +18,7 @@ module Synapse
16
18
  @condition.wait @mutex, timeout
17
19
  end
18
20
 
19
- if @exception
20
- raise @exception
21
- end
21
+ raise @exception if @exception
22
22
 
23
23
  @result
24
24
  end
@@ -0,0 +1,14 @@
1
+ module Synapse
2
+ module Command
3
+ # Implementation of a command callback that does nothing
4
+ class VoidCallback < CommandCallback
5
+ # @param [Object] result The result from the command handler
6
+ # @return [undefined]
7
+ def on_success(result); end
8
+
9
+ # @param [Exception] exception The cause of the failure
10
+ # @return [undefined]
11
+ def on_failure(exception); end
12
+ end # VoidCallback
13
+ end # Command
14
+ end
@@ -34,7 +34,7 @@ module Synapse
34
34
  #
35
35
  # @param [Class] command_type
36
36
  # @param [CommandHandler] handler
37
- # @return [undefined]
37
+ # @return [CommandHandler] The command handler being replaced, if any
38
38
  def subscribe(command_type, handler)
39
39
  raise NotImplementedError
40
40
  end
@@ -44,7 +44,7 @@ module Synapse
44
44
  #
45
45
  # @param [Class] command_type
46
46
  # @param [CommandHandler] handler
47
- # @return [undefined]
47
+ # @return [Boolean] True if command handler was unsubscribed from command handler
48
48
  def unsubscribe(command_type, handler)
49
49
  raise NotImplementedError
50
50
  end
@@ -1,18 +1,25 @@
1
1
  module Synapse
2
2
  module Command
3
3
  # Callback that is notified of the outcome of the dispatch of a command
4
+ # @abstract
4
5
  class CommandCallback
5
6
  # Called when a dispatch is successful
6
7
  #
8
+ # @abstract
7
9
  # @param [Object] result The result from the command handler
8
10
  # @return [undefined]
9
- def on_success(result); end
11
+ def on_success(result)
12
+ raise NotImplementedError
13
+ end
10
14
 
11
15
  # Called when a dispatch fails due to an exception
12
16
  #
17
+ # @abstract
13
18
  # @param [Exception] exception The cause of the failure
14
19
  # @return [undefined]
15
- def on_failure(exception); end
20
+ def on_failure(exception)
21
+ raise NotImplementedError
22
+ end
16
23
  end # CommandCallback
17
24
  end # Command
18
25
  end
@@ -3,7 +3,7 @@ module Synapse
3
3
  # Simplified interface to the command bus
4
4
  # @api public
5
5
  class CommandGateway
6
- # @return [Array]
6
+ # @return [Array<CommandFilter>]
7
7
  attr_reader :filters
8
8
 
9
9
  # @return [RetryScheduler]
@@ -8,17 +8,17 @@ module Synapse
8
8
  # This implementation uses EventMachine to schedule one-shot timers.
9
9
  class IntervalRetryScheduler < RetryScheduler
10
10
  # @param [Float] interval
11
- # @param [Integer] maxRetries
11
+ # @param [Integer] max_retries
12
12
  # @return [undefined]
13
- def initialize(interval, maxRetries)
13
+ def initialize(interval, max_retries)
14
14
  @interval = interval
15
- @maxRetries = maxRetries
15
+ @max_retries = max_retries
16
16
 
17
17
  @logger = Logging.logger[self.class]
18
18
  end
19
19
 
20
20
  # @param [CommandMessage] command
21
- # @param [Array] failures
21
+ # @param [Array<Exception>] failures
22
22
  # @param [Proc] dispatcher
23
23
  # @return [Boolean]
24
24
  def schedule(command, failures, dispatcher)
@@ -33,7 +33,7 @@ module Synapse
33
33
 
34
34
  failureCount = failures.size
35
35
 
36
- if failureCount > @maxRetries
36
+ if failureCount > @max_retries
37
37
  @logger.info 'Dispatch of command [%s] [%s] resulted in exception [%s] times' %
38
38
  [command.payload_type, command.id, failureCount]
39
39
 
@@ -42,7 +42,7 @@ module Synapse
42
42
 
43
43
  if @logger.info?
44
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]
45
+ [command.payload_type, command.id, @max_retries - failureCount]
46
46
  end
47
47
 
48
48
  perform_schedule command, dispatcher
@@ -16,11 +16,14 @@ module Synapse
16
16
  # the failure will be interpreted as terminal and the callback will be invoked with the
17
17
  # last recorded failure.
18
18
  #
19
+ # @abstract
19
20
  # @param [CommandMessage] command
20
- # @param [Array] failures
21
+ # @param [Array<Exception>] failures
21
22
  # @param [Proc] dispatcher
22
23
  # @return [Boolean]
23
- def schedule(command, failures, dispatcher); end
24
- end
25
- end
24
+ def schedule(command, failures, dispatcher)
25
+ raise NotImplementedError
26
+ end
27
+ end # RetryScheduler
28
+ end # Command
26
29
  end
@@ -8,7 +8,7 @@ module Synapse
8
8
  # command message that will be passed on in the chain.
9
9
  class InterceptorChain
10
10
  # @param [UnitOfWork] unit The current unit of work for this command dispatch
11
- # @param [Array] interceptors
11
+ # @param [Array<DispatchInterceptor>] interceptors
12
12
  # @param [CommandHandler] handler
13
13
  # @return [undefined]
14
14
  def initialize(unit, interceptors, handler)
@@ -4,6 +4,7 @@ module Synapse
4
4
  # serialization-aware message. This provides optimization in cases where storage (in an event
5
5
  # store) and publication (on the event bus) use the same serialization mechansim.
6
6
  class SerializationOptimizingInterceptor < DispatchInterceptor
7
+ # @return [undefined]
7
8
  def initialize
8
9
  @listener = SerializationOptimizingListener.new
9
10
  end
@@ -32,4 +33,4 @@ module Synapse
32
33
  end
33
34
  end # SerializationOptimizingListener
34
35
  end
35
- end
36
+ end
@@ -6,10 +6,10 @@ module Synapse
6
6
  # @return [RollbackPolicy]
7
7
  attr_accessor :rollback_policy
8
8
 
9
- # @return [Array]
9
+ # @return [Array<CommandFilter>]
10
10
  attr_reader :filters
11
11
 
12
- # @return [Array]
12
+ # @return [Array<DispatchInterceptor>]
13
13
  attr_reader :interceptors
14
14
 
15
15
  # @param [UnitOfWorkFactory] unit_factory
@@ -30,7 +30,7 @@ module Synapse
30
30
  # @param [CommandMessage] command
31
31
  # @return [undefined]
32
32
  def dispatch(command)
33
- dispatch_with_callback command, CommandCallback.new
33
+ dispatch_with_callback command, VoidCallback.new
34
34
  end
35
35
 
36
36
  # @api public
@@ -42,9 +42,9 @@ module Synapse
42
42
  result = perform_dispatch command
43
43
  callback.on_success result
44
44
  rescue => exception
45
- backtrace = exception.backtrace.join $/
46
- @logger.error 'Exception occured while dispatching command [%s] [%s]: %s %s' %
47
- [command.payload_type, command.id, exception.inspect, backtrace]
45
+ backtrace = exception.backtrace.join $RS
46
+ @logger.error "Exception occured while dispatching command {#{command.payload_type}} {#{command.id}}:\n" +
47
+ "#{exception.inspect} #{backtrace}"
48
48
 
49
49
  callback.on_failure exception
50
50
  end
@@ -53,39 +53,29 @@ module Synapse
53
53
  # @api public
54
54
  # @param [Class] command_type
55
55
  # @param [CommandHandler] handler
56
- # @return [undefined]
56
+ # @return [CommandHandler] The command handler being replaced, if any
57
57
  def subscribe(command_type, handler)
58
- if @handlers.has_key? command_type
59
- current_handler = @handlers.fetch command_type
60
- @logger.info 'Command handler [%s] is being replaced by [%s] for command type [%s]' %
61
- [current_handler.class, handler.class, command_type]
62
- else
63
- @logger.debug 'Command handler [%s] subscribed to command type [%s]' %
64
- [handler.class, command_type]
65
- end
58
+ current = @handlers.fetch command_type, nil
66
59
 
67
60
  @handlers.store command_type, handler
61
+ @logger.debug "Command handler {#{handler.class}} subscribed to command type {#{command_type}}"
62
+
63
+ current
68
64
  end
69
65
 
70
66
  # @api public
71
67
  # @param [Class] command_type
72
68
  # @param [CommandHandler] handler
73
- # @return [undefined]
69
+ # @return [Boolean] True if command handler was unsubscribed from command handler
74
70
  def unsubscribe(command_type, handler)
75
- if @handlers.has_key? command_type
76
- current_handler = @handlers.fetch command_type
77
- if current_handler.equal? handler
78
- @handlers.delete command_type
71
+ current = @handlers.fetch command_type, nil
79
72
 
80
- @logger.debug 'Command handler [%s] unsubscribed from command type [%s]' %
81
- [handler.class, command_type]
82
- else
83
- @logger.info 'Command type [%s] subscribed to handler [%s] not [%s]' %
84
- [command_type, current_handler.class, handler.class]
85
- end
86
- else
87
- @logger.info 'Command type [%s] not subscribed to any handler' % command_type
88
- end
73
+ return false unless current === handler
74
+
75
+ @handlers.delete command_type
76
+ @logger.debug "Command handler {#{handler.class}} unsubscribed from command type {#{command_type}}"
77
+
78
+ return true
89
79
  end
90
80
 
91
81
  protected
@@ -107,8 +97,7 @@ module Synapse
107
97
  chain = InterceptorChain.new unit, @interceptors, handler
108
98
 
109
99
  begin
110
- @logger.info 'Dispatching command [%s] [%s] to handler [%s]' %
111
- [command.id, command.payload_type, handler.class]
100
+ @logger.info "Dispatching command {#{command.id}} {#{command.payload_type}} to handler {#{handler.class}}"
112
101
 
113
102
  result = chain.proceed command
114
103
  rescue => exception
@@ -132,12 +121,12 @@ module Synapse
132
121
  # @param [CommandMessage] command
133
122
  # @return [CommandHandler]
134
123
  def handler_for(command)
135
- type = command.payload_type
124
+ command_type = command.payload_type
136
125
 
137
126
  begin
138
- @handlers.fetch type
127
+ @handlers.fetch command_type
139
128
  rescue KeyError
140
- raise NoHandlerError, 'No handler subscribed for command [%s]' % type
129
+ raise NoHandlerError, "No handler subscribed for command {#{command_type}}"
141
130
  end
142
131
  end
143
132
  end # SimpleCommandBus
@@ -3,7 +3,7 @@ require 'synapse/common/identifier'
3
3
  require 'synapse/common/message'
4
4
  require 'synapse/common/message_builder'
5
5
 
6
- require 'synapse/common/concurrency/identifier_lock'
7
- require 'synapse/common/concurrency/public_lock'
6
+ require 'synapse/common/concurrency/disposable_lock'
7
+ require 'synapse/common/concurrency/identifier_lock_manager'
8
8
 
9
9
  require 'synapse/common/duplication'
@@ -0,0 +1,157 @@
1
+ module Synapse
2
+ # @api private
3
+ class DisposableLock
4
+ # @return [Boolean]
5
+ attr_reader :closed
6
+
7
+ alias_method :closed?, :closed
8
+
9
+ # @return [Thread]
10
+ attr_reader :owner
11
+
12
+ # @return [Array]
13
+ attr_reader :waiters
14
+
15
+ # @return [undefined]
16
+ def initialize
17
+ @mutex = Mutex.new
18
+
19
+ @closed = false
20
+ @hold_count = 0
21
+ @owner = nil
22
+ @waiters = Array.new
23
+ end
24
+
25
+ # @return [Boolean]
26
+ def owned?
27
+ @owner == Thread.current
28
+ end
29
+
30
+ # @param [Thread] thread
31
+ # @return [Boolean]
32
+ def owned_by?(thread)
33
+ @owner == thread
34
+ end
35
+
36
+ # @return [Boolean]
37
+ def locked?
38
+ @hold_count > 0
39
+ end
40
+
41
+ # @return [Boolean] False if lock has been closed
42
+ def lock
43
+ @mutex.synchronize do
44
+ unless owned?
45
+ if @hold_count == 0
46
+ @owner = Thread.current
47
+ else
48
+ @waiters.push Thread.current
49
+
50
+ begin
51
+ wait_for_lock
52
+ ensure
53
+ @waiters.delete Thread.current
54
+ end
55
+
56
+ return unless owned?
57
+ end
58
+ end
59
+
60
+ @hold_count += 1
61
+ end
62
+
63
+ if closed?
64
+ unlock
65
+ return false
66
+ end
67
+
68
+ return true
69
+ end
70
+
71
+ # @return [Boolean]
72
+ def try_lock
73
+ @mutex.synchronize do
74
+ unless owned?
75
+ if @hold_count == 0
76
+ @owner = Thread.current
77
+ else
78
+ return false
79
+ end
80
+ end
81
+
82
+ @hold_count += 1
83
+ return true
84
+ end
85
+ end
86
+
87
+ # @raise [RuntimeError] If caller does not own the lock
88
+ # @return [undefined]
89
+ def unlock
90
+ @mutex.synchronize do
91
+ raise RuntimeError unless owned?
92
+
93
+ @hold_count -= 1
94
+
95
+ if @hold_count == 0
96
+ @owner = nil
97
+ wakeup_next_waiter
98
+ end
99
+ end
100
+ end
101
+
102
+ # @return [Boolean] True if lock was closed
103
+ def try_close
104
+ return false unless try_lock
105
+
106
+ begin
107
+ if @hold_count == 1
108
+ @closed = true
109
+ return true
110
+ end
111
+
112
+ return false
113
+ ensure
114
+ unlock
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # @raise [DeadlockError]
121
+ # @return [undefined]
122
+ def check_for_deadlock
123
+ return if owned?
124
+ return unless locked?
125
+
126
+ for waiter in IdentifierLockManager.waiters_for_locks_owned_by(Thread.current)
127
+ if owned_by? waiter
128
+ raise DeadlockError
129
+ end
130
+ end
131
+ end
132
+
133
+ # Mutex must be locked to perform this operation
134
+ # @return [undefined]
135
+ def wait_for_lock
136
+ loop do
137
+ if @hold_count == 0
138
+ @owner = Thread.current
139
+ return
140
+ end
141
+
142
+ check_for_deadlock
143
+ @mutex.sleep 0.1 # Sleep for 100 milliseconds
144
+ end
145
+ end
146
+
147
+ # @return [undefined]
148
+ def wakeup_next_waiter
149
+ begin
150
+ n = @waiters.shift
151
+ n.wakeup if n
152
+ rescue ThreadError
153
+ retry
154
+ end
155
+ end
156
+ end # DisposableLock
157
+ end