synapse-core 0.5.6 → 0.6.0

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