synapse-core 0.2.0 → 0.4.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 (69) hide show
  1. data/lib/synapse.rb +3 -0
  2. data/lib/synapse/command/simple_command_bus.rb +2 -2
  3. data/lib/synapse/common/concurrency/identifier_lock.rb +71 -0
  4. data/lib/synapse/common/concurrency/public_lock.rb +96 -0
  5. data/lib/synapse/event_bus/simple_event_bus.rb +1 -1
  6. data/lib/synapse/event_bus/wiring.rb +0 -4
  7. data/lib/synapse/event_sourcing/member.rb +0 -4
  8. data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +2 -2
  9. data/lib/synapse/event_store.rb +1 -9
  10. data/lib/synapse/partitioning.rb +0 -2
  11. data/lib/synapse/process_manager.rb +12 -0
  12. data/lib/synapse/process_manager/lock_manager.rb +22 -0
  13. data/lib/synapse/process_manager/pessimistic_lock_manager.rb +23 -0
  14. data/lib/synapse/process_manager/process.rb +2 -0
  15. data/lib/synapse/process_manager/process_factory.rb +52 -0
  16. data/lib/synapse/process_manager/process_manager.rb +170 -0
  17. data/lib/synapse/process_manager/process_repository.rb +53 -0
  18. data/lib/synapse/process_manager/repository/in_memory.rb +63 -0
  19. data/lib/synapse/process_manager/resource_injector.rb +12 -0
  20. data/lib/synapse/process_manager/simple_process_manager.rb +48 -0
  21. data/lib/synapse/process_manager/wiring/process.rb +27 -0
  22. data/lib/synapse/process_manager/wiring/process_manager.rb +72 -0
  23. data/lib/synapse/repository.rb +1 -0
  24. data/lib/synapse/repository/locking.rb +1 -1
  25. data/lib/synapse/repository/optimistic_lock_manager.rb +128 -0
  26. data/lib/synapse/repository/pessimistic_lock_manager.rb +4 -37
  27. data/lib/synapse/serialization.rb +1 -1
  28. data/lib/synapse/serialization/{converter/factory.rb → converter_factory.rb} +0 -0
  29. data/lib/synapse/serialization/serializer.rb +5 -3
  30. data/lib/synapse/uow/listener_collection.rb +59 -1
  31. data/lib/synapse/version.rb +1 -1
  32. data/lib/synapse/wiring/message_wiring.rb +7 -3
  33. data/lib/synapse/wiring/wire.rb +7 -2
  34. data/test/common/concurrency/identifier_lock_test.rb +36 -0
  35. data/test/common/concurrency/public_lock_test.rb +83 -0
  36. data/test/partitioning/packing/json_test.rb +2 -1
  37. data/test/process_manager/in_memory_test.rb +57 -0
  38. data/test/process_manager/process_factory_test.rb +31 -0
  39. data/test/process_manager/simple_process_manager_test.rb +130 -0
  40. data/test/process_manager/wiring/fixtures.rb +42 -0
  41. data/test/process_manager/wiring/process_manager_test.rb +73 -0
  42. data/test/process_manager/wiring/process_test.rb +35 -0
  43. data/test/repository/optimistic_test.rb +41 -0
  44. data/test/repository/pessimistic_test.rb +20 -0
  45. data/test/serialization/converter/chain_test.rb +31 -0
  46. data/test/serialization/lazy_object_test.rb +1 -1
  47. data/test/serialization/message/serialization_aware_message_test.rb +4 -2
  48. data/test/serialization/message/serialized_message_builder_test.rb +1 -1
  49. data/test/serialization/message/serialized_message_test.rb +3 -2
  50. data/test/serialization/serializer/marshal_test.rb +1 -1
  51. data/test/serialization/serializer/oj_test.rb +1 -1
  52. data/test/serialization/serializer/ox_test.rb +1 -1
  53. data/test/serialization/serializer_test.rb +1 -1
  54. data/test/test_ext.rb +5 -2
  55. data/test/wiring/wire_registry_test.rb +10 -10
  56. data/test/wiring/wire_test.rb +5 -5
  57. metadata +29 -16
  58. data/lib/synapse/event_store/mongo.rb +0 -8
  59. data/lib/synapse/event_store/mongo/cursor_event_stream.rb +0 -63
  60. data/lib/synapse/event_store/mongo/event_store.rb +0 -86
  61. data/lib/synapse/event_store/mongo/per_commit_strategy.rb +0 -253
  62. data/lib/synapse/event_store/mongo/per_event_strategy.rb +0 -143
  63. data/lib/synapse/event_store/mongo/storage_strategy.rb +0 -113
  64. data/lib/synapse/event_store/mongo/template.rb +0 -73
  65. data/lib/synapse/partitioning/amqp.rb +0 -3
  66. data/lib/synapse/partitioning/amqp/amqp_queue_reader.rb +0 -50
  67. data/lib/synapse/partitioning/amqp/amqp_queue_writer.rb +0 -31
  68. data/lib/synapse/partitioning/amqp/key_resolver.rb +0 -26
  69. data/lib/synapse/serialization/converter/bson.rb +0 -28
@@ -23,9 +23,9 @@ module Synapse
23
23
  end
24
24
  end
25
25
 
26
+ require 'synapse/serialization/converter_factory'
26
27
  require 'synapse/serialization/converter'
27
28
  require 'synapse/serialization/converter/chain'
28
- require 'synapse/serialization/converter/factory'
29
29
  require 'synapse/serialization/converter/identity'
30
30
 
31
31
  require 'synapse/serialization/errors'
@@ -4,13 +4,15 @@ module Synapse
4
4
  # @abstract
5
5
  class Serializer
6
6
  # @return [ConverterFactory]
7
- attr_accessor :converter_factory
7
+ attr_reader :converter_factory
8
8
 
9
9
  # @return [RevisionResolver]
10
10
  attr_accessor :revision_resolver
11
11
 
12
- def initialize
13
- @converter_factory = ConverterFactory.new
12
+ # @param [ConverterFactory] converter_factory
13
+ # @return [undefined]
14
+ def initialize(converter_factory)
15
+ @converter_factory = converter_factory
14
16
  end
15
17
 
16
18
  # @param [Object] object
@@ -14,6 +14,7 @@ module Synapse
14
14
  class UnitOfWorkListenerCollection < UnitOfWorkListener
15
15
  def initialize
16
16
  @listeners = Array.new
17
+ @logger = Logging.logger[self.class]
17
18
  end
18
19
 
19
20
  # Pushes a unit of work listener onto the end of this collection
@@ -21,6 +22,10 @@ module Synapse
21
22
  # @param [UnitOfWorkListener] listener
22
23
  # @return [undefined]
23
24
  def push(listener)
25
+ if @logger.debug?
26
+ @logger.debug 'Registering listener [%s]' % listener.class
27
+ end
28
+
24
29
  @listeners.push listener
25
30
  end
26
31
 
@@ -29,9 +34,17 @@ module Synapse
29
34
  # @param [UnitOfWork] unit
30
35
  # @return [undefined]
31
36
  def on_start(unit)
37
+ @logger.debug 'Notifying listeners that unit of work is starting'
38
+
32
39
  @listeners.each do |listener|
40
+ if @logger.debug?
41
+ @logger.debug 'Notifying [%s] of start' % listener.class
42
+ end
43
+
33
44
  listener.on_start unit
34
45
  end
46
+
47
+ @logger.debug 'Listeners successfully notified'
35
48
  end
36
49
 
37
50
  # @param [UnitOfWork] unit
@@ -50,43 +63,88 @@ module Synapse
50
63
  # @param [Hash<EventBus, Array>] events
51
64
  # @return [undefined]
52
65
  def on_prepare_commit(unit, aggregates, events)
66
+ @logger.debug 'Notifying listeners that commit was requested'
67
+
53
68
  @listeners.each do |listener|
69
+ if @logger.debug?
70
+ @logger.debug 'Notifying [%s] of commit' % listener.class
71
+ end
72
+
54
73
  listener.on_prepare_commit unit, aggregates, events
55
74
  end
75
+
76
+ @logger.debug 'Listeners successfully notified'
56
77
  end
57
78
 
58
79
  # @param [UnitOfWork] unit
59
80
  # @param [Object] transaction
60
81
  # @return [undefined]
61
82
  def on_prepare_transaction_commit(unit, transaction)
83
+ @logger.debug 'Notifying listeners that transactional commit was requested'
84
+
62
85
  @listeners.each do |listener|
86
+ if @logger.debug?
87
+ @logger.debug 'Notifying [%s] of transactional commit' % listener.class
88
+ end
89
+
63
90
  listener.on_prepare_transaction_commit unit, transaction
64
91
  end
92
+
93
+ @logger.debug 'Listeners successfully notified'
65
94
  end
66
95
 
67
96
  # @param [UnitOfWork] unit
68
97
  # @return [undefined]
69
98
  def after_commit(unit)
99
+ @logger.debug 'Notifying listeners that commit has finished'
100
+
70
101
  @listeners.reverse_each do |listener|
102
+ if @logger.debug?
103
+ @logger.debug 'Notifying [%s] of finished commit' % listener.class
104
+ end
105
+
71
106
  listener.after_commit unit
72
107
  end
108
+
109
+ @logger.debug 'Listeners successfully notified'
73
110
  end
74
111
 
75
112
  # @param [UnitOfWork] unit
76
113
  # @param [Error] cause
77
114
  # @return [undefined]
78
115
  def on_rollback(unit, cause = nil)
116
+ @logger.debug 'Notifying listeners of rollback'
117
+
79
118
  @listeners.reverse_each do |listener|
119
+ if @logger.debug?
120
+ @logger.debug 'Notifying [%s] of rollback' % listener.class
121
+ end
122
+
80
123
  listener.on_rollback unit, cause
81
124
  end
125
+
126
+ @logger.debug 'Listeners successfully notified'
82
127
  end
83
128
 
84
129
  # @param [UnitOfWork] unit
85
130
  # @return [undefined]
86
131
  def on_cleanup(unit)
132
+ @logger.debug 'Notifying listeners of cleanup'
133
+
87
134
  @listeners.reverse_each do |listener|
88
- listener.on_cleanup unit
135
+ if @logger.debug?
136
+ @logger.debug 'Notifying [%s] of cleanup' % listener.class
137
+ end
138
+
139
+ begin
140
+ listener.on_cleanup unit
141
+ rescue => exception
142
+ # Ignore this exception so that we can continue cleaning up
143
+ @logger.warn 'Listener raised an exception during cleanup: %s' % exception.inspect
144
+ end
89
145
  end
146
+
147
+ @logger.debug 'Listeners successfully notified'
90
148
  end
91
149
  end
92
150
  end
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -8,21 +8,25 @@ module Synapse
8
8
  included do
9
9
  # @return [WireRegistry]
10
10
  class_attribute :wire_registry
11
+
12
+ # By default, the wire registry allows duplicates
13
+ self.wire_registry = Wiring::WireRegistry.new true
11
14
  end
12
15
 
13
16
  module ClassMethods
14
17
  def wire(type, *args, &block)
15
18
  options = args.extract_options!
16
19
 
17
- unless options[:to]
20
+ to = options.delete :to
21
+ unless to
18
22
  unless block
19
23
  raise ArgumentError, 'Expected block or option :to'
20
24
  end
21
25
 
22
- options[:to] = block
26
+ to = block
23
27
  end
24
28
 
25
- wire = Wire.new type, options[:to]
29
+ wire = Wire.new type, options, to
26
30
 
27
31
  self.wire_registry.register wire
28
32
  end
@@ -11,11 +11,16 @@ module Synapse
11
11
  # @return [Object] Either a method symbol or block
12
12
  attr_reader :handler
13
13
 
14
+ # @return [Hash] Options specific to the component being wired
15
+ attr_reader :options
16
+
14
17
  # @param [Class] type
18
+ # @param [Hash] options
15
19
  # @param [Object] handler Either a method symbol or block
16
- # @return [undefined]]
17
- def initialize(type, handler)
20
+ # @return [undefined]
21
+ def initialize(type, options, handler)
18
22
  @type = type
23
+ @options = options
19
24
  @handler = handler
20
25
  end
21
26
 
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ class IdentifierLockTest < Test::Unit::TestCase
5
+ def test_disposal
6
+ lock = IdentifierLock.new
7
+ identifier = 'some_id'
8
+
9
+ lock.obtain_lock identifier
10
+ lock.release_lock identifier
11
+
12
+ identifiers = lock.instance_variable_get :@identifiers
13
+ refute identifiers.has_key? identifier
14
+ end
15
+
16
+ def test_owned?
17
+ lock = IdentifierLock.new
18
+ identifier = 'some_id'
19
+
20
+ refute lock.owned? identifier
21
+
22
+ lock.obtain_lock identifier
23
+ assert lock.owned? identifier
24
+
25
+ lock.release_lock identifier
26
+ refute lock.owned? identifier
27
+ end
28
+
29
+ def test_release_lock
30
+ lock = IdentifierLock.new
31
+ assert_raise ThreadError do
32
+ lock.release_lock 'derp'
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ class PublicLockTest < Test::Unit::TestCase
5
+ def test_lock_raises
6
+ @lock = PublicLock.new
7
+ @lock.lock
8
+
9
+ assert_raise ThreadError do
10
+ @lock.lock
11
+ end
12
+ end
13
+
14
+ def test_lock_removes_waiting
15
+ @lock = PublicLock.new
16
+ @lock.lock
17
+
18
+ t = Thread.new do
19
+ @lock.lock
20
+ end
21
+
22
+ wait_until do
23
+ @lock.waiting == [t]
24
+ end
25
+
26
+ t.kill
27
+
28
+ wait_until do
29
+ @lock.waiting == []
30
+ end
31
+ end
32
+
33
+ def test_synchronize
34
+ @lock = PublicLock.new
35
+
36
+ refute @lock.owned?
37
+ refute @lock.owned_by? Thread.current
38
+
39
+ @lock.synchronize do
40
+ assert @lock.owned?
41
+ assert @lock.owned_by? Thread.current
42
+ end
43
+
44
+ refute @lock.owned?
45
+ refute @lock.owned_by? Thread.current
46
+ end
47
+
48
+ def test_unlock_raises
49
+ @lock = PublicLock.new
50
+
51
+ assert_raise ThreadError do
52
+ @lock.unlock
53
+ end
54
+ end
55
+
56
+ def test_try_lock
57
+ @lock = PublicLock.new
58
+
59
+ t = Thread.new do
60
+ assert @lock.try_lock
61
+ Thread.stop
62
+ @lock.unlock
63
+ end
64
+
65
+ wait_until do
66
+ @lock.owned_by? t
67
+ end
68
+
69
+ refute @lock.try_lock
70
+
71
+ t.wakeup
72
+ end
73
+
74
+ def test_try_lock_raises
75
+ @lock = PublicLock.new
76
+ @lock.try_lock
77
+
78
+ assert_raise ThreadError do
79
+ @lock.try_lock
80
+ end
81
+ end
82
+ end
83
+ end
@@ -5,7 +5,8 @@ module Synapse
5
5
 
6
6
  class JsonPackingTest < Test::Unit::TestCase
7
7
  def setup
8
- @serializer = Serialization::MarshalSerializer.new
8
+ @converter_factory = Serialization::ConverterFactory.new
9
+ @serializer = Serialization::MarshalSerializer.new @converter_factory
9
10
  @packer = JsonMessagePacker.new @serializer
10
11
  @unpacker = JsonMessageUnpacker.new @serializer
11
12
  end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module ProcessManager
5
+
6
+ class InMemoryProcessRepositoryTest < Test::Unit::TestCase
7
+ def test_find
8
+ correlation_a = Correlation.new :order_id, 1
9
+ correlation_b = Correlation.new :order_id, 2
10
+
11
+ process_a = Process.new
12
+ process_a.correlations.add correlation_a
13
+ process_b = Process.new
14
+ process_b.correlations.add correlation_b
15
+
16
+ repository = InMemoryProcessRepository.new
17
+ repository.add process_a
18
+ repository.add process_b
19
+
20
+ assert_equal [process_a.id], repository.find(Process, correlation_a)
21
+ assert_equal [process_b.id], repository.find(Process, correlation_b)
22
+ end
23
+
24
+ def test_load
25
+ repository = InMemoryProcessRepository.new
26
+
27
+ process_a = Process.new
28
+ process_b = Process.new
29
+
30
+ repository.add process_a
31
+ repository.add process_b
32
+
33
+ assert_equal process_a, repository.load(process_a.id)
34
+ assert_equal process_b, repository.load(process_b.id)
35
+ end
36
+
37
+ def test_commit
38
+ repository = InMemoryProcessRepository.new
39
+
40
+ process = Process.new
41
+ repository.commit process
42
+
43
+ assert_equal 1, repository.count
44
+ # Make sure the correlation set was marked as committed
45
+ assert_equal 0, process.correlations.additions.count
46
+ assert_equal 0, process.correlations.deletions.count
47
+
48
+ process.send :finish
49
+
50
+ repository.commit process
51
+
52
+ assert_equal 0, repository.count
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module ProcessManager
5
+ class GenericProcessFactoryTest < Test::Unit::TestCase
6
+ def test_create
7
+ injector = Object.new
8
+
9
+ mock(injector).inject_resources(is_a(Process))
10
+
11
+ factory = GenericProcessFactory.new
12
+ factory.resource_injector = injector
13
+
14
+ process = factory.create Process
15
+
16
+ assert process.is_a? Process
17
+ end
18
+
19
+ def test_supports
20
+ factory = GenericProcessFactory.new
21
+
22
+ assert factory.supports Process
23
+ refute factory.supports StubProcessWithArguments
24
+ end
25
+ end
26
+
27
+ class StubProcessWithArguments < Process
28
+ def initialize(some_resource); end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,130 @@
1
+ require 'test_helper'
2
+
3
+ module Synapse
4
+ module ProcessManager
5
+ class SimpleProcessManagerTest < Test::Unit::TestCase
6
+ def setup
7
+ @repository = InMemoryProcessRepository.new
8
+ @factory = GenericProcessFactory.new
9
+ @resolver = MetadataCorrelationResolver.new :order_id
10
+ @lock_manager = Object.new
11
+ @manager = SimpleProcessManager.new @repository, @factory, @lock_manager, @resolver, TestProcess
12
+ end
13
+
14
+ def test_notify_new_process
15
+ @manager.optionally_create_events << CauseProcessCreationEvent
16
+
17
+ correlation = Correlation.new :order_id, 123
18
+
19
+ mock(@lock_manager).obtain_lock(is_a(String))
20
+ mock(@lock_manager).release_lock(is_a(String))
21
+
22
+ @manager.notify create_event 123, CauseProcessCreationEvent.new
23
+
24
+ assert_equal 1, @repository.count
25
+ end
26
+
27
+ def test_notify_existing_process
28
+ process = TestProcess.new
29
+ process.correlations.add Correlation.new :order_id, 123
30
+
31
+ @repository.add process
32
+
33
+ mock(@lock_manager).obtain_lock(process.id)
34
+ mock(@lock_manager).release_lock(process.id)
35
+
36
+ @manager.notify create_event 123, CauseProcessNotificationEvent.new
37
+
38
+ assert_equal 1, @repository.count
39
+ end
40
+
41
+ def test_notify_process_raises_but_suppressed
42
+ process = TestProcess.new
43
+ process.correlations.add Correlation.new :order_id, 123
44
+
45
+ @repository.add process
46
+
47
+ mock(@lock_manager).obtain_lock(process.id)
48
+ mock(@lock_manager).release_lock(process.id)
49
+
50
+ @manager.notify create_event 123, CauseProcessRaiseExceptionEvent.new
51
+
52
+ assert_equal 1, @repository.count
53
+ end
54
+
55
+ def test_notify_process_raises
56
+ process = TestProcess.new
57
+ process.correlations.add Correlation.new :order_id, 123
58
+
59
+ @repository.add process
60
+
61
+ mock(@lock_manager).obtain_lock(process.id)
62
+ mock(@lock_manager).release_lock(process.id)
63
+
64
+ @manager.suppress_exceptions = false
65
+
66
+ assert_raise RuntimeError do
67
+ @manager.notify create_event 123, CauseProcessRaiseExceptionEvent.new
68
+ end
69
+ end
70
+
71
+ def test_always_create
72
+ @manager.always_create_events << CauseProcessCreationEvent
73
+
74
+ correlation = Correlation.new :order_id, 123
75
+
76
+ # Lock is obtain/released for the 2 being created and once for the first being changed
77
+ mock(@lock_manager).obtain_lock(is_a(String)).times(3)
78
+ mock(@lock_manager).release_lock(is_a(String)).times(3)
79
+
80
+ @manager.notify create_event 123, CauseProcessCreationEvent.new
81
+ @manager.notify create_event 123, CauseProcessCreationEvent.new
82
+
83
+ assert_equal 2, @repository.count
84
+ end
85
+
86
+ private
87
+
88
+ def create_event(order_id, event)
89
+ Domain::EventMessage.build do |builder|
90
+ builder.payload = event
91
+ builder.metadata = {
92
+ order_id: order_id
93
+ }
94
+ end
95
+ end
96
+ end
97
+
98
+ # Example correlation resolver
99
+ class MetadataCorrelationResolver < CorrelationResolver
100
+ def initialize(property)
101
+ @property = property
102
+ end
103
+
104
+ def resolve(event)
105
+ value = event.metadata[@property]
106
+ if value
107
+ Correlation.new @property, value
108
+ end
109
+ end
110
+ end
111
+
112
+ # Test process that has all sorts of trigger events
113
+ class TestProcess < Process
114
+ attr_accessor :handled
115
+
116
+ def handle(event)
117
+ if event.payload_type == CauseProcessRaiseExceptionEvent
118
+ raise 'ohgodimnotgoodwithcomputers'
119
+ end
120
+
121
+ @handled ||= 0
122
+ @handled = @handled.next
123
+ end
124
+ end
125
+
126
+ class CauseProcessCreationEvent; end
127
+ class CauseProcessNotificationEvent; end
128
+ class CauseProcessRaiseExceptionEvent; end
129
+ end
130
+ end