synapse-core 0.2.0 → 0.4.0

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