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
@@ -1,32 +1,6 @@
1
1
  module Synapse
2
2
  module ProcessManager
3
- # Combination key and value that is used to correlate incoming events with process instances
4
- class Correlation
5
- # @return [Symbol]
6
- attr_reader :key
7
-
8
- # @return [String]
9
- attr_reader :value
10
-
11
- # @param [Symbol] key
12
- # @param [String] value
13
- # @return [undefined]
14
- def initialize(key, value)
15
- @key = key.to_sym
16
- @value = value.to_s
17
- end
18
-
19
- def ==(other)
20
- self.class === other and
21
- other.key == @key and
22
- other.value == @value
23
- end
24
-
25
- alias eql? ==
26
-
27
- def hash
28
- @key.hash ^ @value.hash
29
- end
30
- end # Correlation
31
- end # ProcessManager
3
+ # Combination key and value that is used to correlate incoming events with process instance
4
+ Correlation = Struct.new :key, :value
5
+ end
32
6
  end
@@ -3,20 +3,20 @@ module Synapse
3
3
  # Lock manager that blocks until a lock can be obtained for a process
4
4
  class PessimisticLockManager
5
5
  def initialize
6
- @lock = IdentifierLock.new
6
+ @manager = IdentifierLockManager.new
7
7
  end
8
8
 
9
9
  # @param [String] process_id
10
10
  # @return [undefined]
11
11
  def obtain_lock(process_id)
12
- @lock.obtain_lock process_id
12
+ @manager.obtain_lock process_id
13
13
  end
14
14
 
15
15
  # @raise [ThreadError] If thread didn't previously hold the lock
16
16
  # @param [String] process_id
17
17
  # @return [undefined]
18
18
  def release_lock(process_id)
19
- @lock.release_lock process_id
19
+ @manager.release_lock process_id
20
20
  end
21
21
  end # PessimisticLockManager
22
22
  end # ProcessManager
@@ -19,16 +19,12 @@ module Synapse
19
19
  # @return [Boolean] True if this process is active
20
20
  attr_reader :active
21
21
 
22
- alias active? active
22
+ alias_method :active?, :active
23
23
 
24
24
  # @param [String] id
25
25
  # @return [undefined]
26
26
  def initialize(id = nil)
27
- unless id
28
- id = IdentifierFactory.instance.generate
29
- end
30
-
31
- @id = id
27
+ @id = id ||= IdentifierFactory.instance.generate
32
28
  @correlations = CorrelationSet.new
33
29
  @active = true
34
30
 
@@ -117,7 +117,7 @@ module Synapse
117
117
  def notify_current_process(process_id, event, correlation)
118
118
  process = @repository.load process_id
119
119
 
120
- unless process and process.active and process.correlations.include? correlation
120
+ unless process && process.active && process.correlations.include?(correlation)
121
121
  # Process has changed or was deleted between the time of the selection query and the
122
122
  # actual loading and locking of the process
123
123
  return
@@ -11,7 +11,7 @@ module Synapse
11
11
  # @abstract
12
12
  # @param [Class] type
13
13
  # @param [Correlation] correlation
14
- # @return [Set]
14
+ # @return [Set<String>]
15
15
  def find(type, correlation)
16
16
  raise NotImplementedError
17
17
  end
@@ -2,16 +2,17 @@ module Synapse
2
2
  module ProcessManager
3
3
  # Process repository that stores all processes in memory
4
4
  #
5
- # This implementation is not thread-safe -- use a lock manager for thread safety
5
+ # While the storage of processes are thread-safe, the processes themselves may not be. Use a
6
+ # lock manager if the processes are not thread-safe.
6
7
  class InMemoryProcessRepository < ProcessRepository
7
8
  def initialize
8
9
  @managed_processes = Hash.new
9
- @lock = Mutex.new
10
+ @mutex = Mutex.new
10
11
  end
11
12
 
12
13
  # @param [Class] type
13
14
  # @param [Correlation] correlation
14
- # @return [Set]
15
+ # @return [Set<String>]
15
16
  def find(type, correlation)
16
17
  matching = Array.new
17
18
 
@@ -35,7 +36,7 @@ module Synapse
35
36
  # @param [Process] process
36
37
  # @return [undefined]
37
38
  def commit(process)
38
- @lock.synchronize do
39
+ @mutex.synchronize do
39
40
  if process.active?
40
41
  @managed_processes.store process.id, process
41
42
  else
@@ -2,10 +2,10 @@ module Synapse
2
2
  module ProcessManager
3
3
  # Simple implementation of a process manager
4
4
  class SimpleProcessManager < ProcessManager
5
- # @return [Array] Types of events that will always result in the creation of a process
5
+ # @return [Array<Class>] Types of events that will always result in the creation of a process
6
6
  attr_accessor :always_create_events
7
7
 
8
- # @return [Array] Types of events that will result in the creation of a process if one
8
+ # @return [Array<Class>] Types of events that will result in the creation of a process if one
9
9
  # doesn't already exist
10
10
  attr_accessor :optionally_create_events
11
11
 
@@ -21,6 +21,6 @@ module Synapse
21
21
  def initialize(aggregate, expected_version)
22
22
  super 'Aggregate [%s] has version %s, expected %s' % [aggregate.id, aggregate.version, expected_version]
23
23
  end
24
- end
25
- end
24
+ end # ConflictingAggregateVersionError
25
+ end # Repository
26
26
  end
@@ -74,11 +74,59 @@ module Synapse
74
74
  raise NotImplementedError
75
75
  end
76
76
 
77
+ # Deletes the given aggregate from the underlying storage mechanism, ensuring that the lock
78
+ # for the aggregate is valid before doing so
79
+ #
80
+ # @abstract
81
+ # @param [AggregateRoot] aggregate
82
+ # @return [undefined]
83
+ def delete_aggregate_with_lock(aggregate)
84
+ raise NotImplementedError
85
+ end
86
+
87
+ # Saves the given aggregate using the underlying storage mechanism, ensuring that the lock
88
+ # for the aggregate is valid before doing so
89
+ #
90
+ # @abstract
91
+ # @param [AggregateRoot] aggregate
92
+ # @return [undefined]
93
+ def save_aggregate_with_lock(aggregate)
94
+ raise NotImplementedError
95
+ end
96
+
77
97
  # Hook that is called after an aggregate is registered to the current unit of work
78
98
  #
79
99
  # @param [AggregateRoot] aggregate
80
100
  # @return [undefined]
81
101
  def post_registration(aggregate); end
102
+
103
+ # @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
104
+ # the lock manager
105
+ # @param [AggregateRoot] aggregate
106
+ # @return [undefined]
107
+ def delete_aggregate(aggregate)
108
+ assert_valid_lock aggregate
109
+ delete_aggregate_with_lock aggregate
110
+ end
111
+
112
+ # @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
113
+ # the lock manager
114
+ # @param [AggregateRoot] aggregate
115
+ # @return [undefined]
116
+ def save_aggregate(aggregate)
117
+ assert_valid_lock aggregate
118
+ save_aggregate_with_lock aggregate
119
+ end
120
+
121
+ # @raise [ConcurrencyError] If aggregate is versioned and its lock has been invalidated by
122
+ # the lock manager
123
+ # @param [AggregateRoot] aggregate
124
+ # @return [undefined]
125
+ def assert_valid_lock(aggregate)
126
+ if aggregate.version && !@lock_manager.validate_lock(aggregate)
127
+ raise ConcurrencyError
128
+ end
129
+ end
82
130
  end # LockingRepository
83
131
 
84
132
  # Unit of work listener that releases the lock on an aggregate when the unit of work
@@ -5,15 +5,16 @@ module Synapse
5
5
  # This implementation uses the sequence number of an aggregate's last committed event to
6
6
  # detect concurrenct access.
7
7
  class OptimisticLockManager < LockManager
8
+ # @return [undefined]
8
9
  def initialize
9
10
  @aggregates = Hash.new
10
- @lock = Mutex.new
11
+ @mutex = Mutex.new
11
12
  end
12
13
 
13
14
  # @param [AggregateRoot] aggregate
14
15
  # @return [Boolean]
15
16
  def validate_lock(aggregate)
16
- @aggregates.has_key? aggregate.id and @aggregates[aggregate.id].validate aggregate
17
+ @aggregates.has_key?(aggregate.id) && @aggregates[aggregate.id].validate(aggregate)
17
18
  end
18
19
 
19
20
  # @param [Object] aggregate_id
@@ -22,7 +23,7 @@ module Synapse
22
23
  obtained = false
23
24
  until obtained
24
25
  lock = lock_for aggregate_id
25
- obtained = lock and lock.lock
26
+ obtained = lock && lock.lock
26
27
  unless obtained
27
28
  remove_lock aggregate_id, lock
28
29
  end
@@ -47,8 +48,8 @@ module Synapse
47
48
  # @param [OptimisticLock] lock
48
49
  # @return [undefined]
49
50
  def remove_lock(aggregate_id, lock)
50
- @lock.synchronize do
51
- if @aggregates.has_key? aggregate_id and @aggregates[aggregate_id].equal? lock
51
+ @mutex.synchronize do
52
+ if @aggregates.has_key?(aggregate_id) && @aggregates[aggregate_id] === lock
52
53
  @aggregates.delete aggregate_id
53
54
  end
54
55
  end
@@ -57,7 +58,7 @@ module Synapse
57
58
  # @param [Object] aggregate_id
58
59
  # @return [OptimisticLock]
59
60
  def lock_for(aggregate_id)
60
- @lock.synchronize do
61
+ @mutex.synchronize do
61
62
  if @aggregates.has_key? aggregate_id
62
63
  @aggregates[aggregate_id]
63
64
  else
@@ -73,7 +74,7 @@ module Synapse
73
74
  # @return [Boolean] True if this lock can be disposed
74
75
  attr_reader :closed
75
76
 
76
- alias closed? closed
77
+ alias_method :closed?, :closed
77
78
 
78
79
  # @return [Hash] Hash of threads to the number of times they hold the lock
79
80
  attr_reader :threads
@@ -87,8 +88,8 @@ module Synapse
87
88
  # @return [Boolean]
88
89
  def validate(aggregate)
89
90
  last_committed = aggregate.version
90
- if @version.nil? or @version.eql? last_committed
91
- @version = (last_committed or 0) + aggregate.uncommitted_event_count
91
+ if @version.nil? || @version == last_committed
92
+ @version = (last_committed || 0) + aggregate.uncommitted_event_count
92
93
  true
93
94
  else
94
95
  false
@@ -2,26 +2,27 @@ module Synapse
2
2
  module Repository
3
3
  # Implementation of a lock manager that blocks until a lock can be obtained
4
4
  class PessimisticLockManager < LockManager
5
+ # @return [undefined]
5
6
  def initialize
6
- @aggregates = IdentifierLock.new
7
+ @manager = IdentifierLockManager.new
7
8
  end
8
9
 
9
10
  # @param [AggregateRoot] aggregate
10
11
  # @return [Boolean]
11
12
  def validate_lock(aggregate)
12
- @aggregates.owned? aggregate.id
13
+ @manager.owned? aggregate.id
13
14
  end
14
15
 
15
16
  # @param [Object] aggregate_id
16
17
  # @return [undefined]
17
18
  def obtain_lock(aggregate_id)
18
- @aggregates.obtain_lock aggregate_id
19
+ @manager.obtain_lock aggregate_id
19
20
  end
20
21
 
21
22
  # @param [Object] aggregate_id
22
23
  # @return [undefined]
23
24
  def release_lock(aggregate_id)
24
- @aggregates.release_lock aggregate_id
25
+ @manager.release_lock aggregate_id
25
26
  end
26
27
  end # PessimisticLockManager
27
28
  end # Repository
@@ -97,7 +97,7 @@ module Synapse
97
97
  # @param [Integer] expected_version
98
98
  # @return [undefined]
99
99
  def assert_version_expected(aggregate, expected_version)
100
- if expected_version and aggregate.version and aggregate.version > expected_version
100
+ if expected_version && aggregate.version && aggregate.version > expected_version
101
101
  raise ConflictingAggregateVersionError.new aggregate, expected_version
102
102
  end
103
103
  end
@@ -30,13 +30,14 @@ module Synapse
30
30
  # Most ORMs that I can think of use #find like this -- no need for orm_adapter or anything
31
31
  # crazy like that
32
32
  aggregate = @aggregate_type.find aggregate_id
33
- aggregate.tap do
34
- unless aggregate
35
- raise AggregateNotFoundError
36
- end
37
33
 
38
- assert_version_expected aggregate, expected_version
34
+ unless aggregate
35
+ raise AggregateNotFoundError
39
36
  end
37
+
38
+ assert_version_expected aggregate, expected_version
39
+
40
+ aggregate
40
41
  end
41
42
 
42
43
  # @return [Class]
@@ -46,13 +47,13 @@ module Synapse
46
47
 
47
48
  # @param [AggregateRoot] aggregate
48
49
  # @return [undefined]
49
- def delete_aggregate(aggregate)
50
+ def delete_aggregate_with_lock(aggregate)
50
51
  aggregate.destroy
51
52
  end
52
53
 
53
54
  # @param [AggregateRoot] aggregate
54
55
  # @return [undefined]
55
- def save_aggregate(aggregate)
56
+ def save_aggregate_with_lock(aggregate)
56
57
  aggregate.save
57
58
  end
58
59
  end # SimpleRepository
@@ -6,7 +6,10 @@ module Synapse
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ # @return [Class]
9
10
  class_attribute :source_type
11
+
12
+ # @return [Class]
10
13
  class_attribute :target_type
11
14
  end
12
15
 
@@ -3,10 +3,12 @@ module Synapse
3
3
  # Represents a mechanism for storing and retrieving converters capable of converting content
4
4
  # of one type to another type, for the purpose of serialization and upcasting.
5
5
  class ConverterFactory
6
+ # @return [Set<Converter>]
6
7
  attr_reader :converters
7
8
 
9
+ # @return [undefined]
8
10
  def initialize
9
- @converters = Array.new
11
+ @converters = Set.new
10
12
  end
11
13
 
12
14
  # Adds the given converter to this converter factory
@@ -14,7 +16,7 @@ module Synapse
14
16
  # @param [Converter] converter
15
17
  # @return [undefined]
16
18
  def register(converter)
17
- @converters.push converter
19
+ @converters.add converter
18
20
  end
19
21
 
20
22
  # Convenience method for converting a given serialized object to the given target type
@@ -40,9 +42,8 @@ module Synapse
40
42
  end
41
43
 
42
44
  @converters.each do |converter|
43
- if converter.source_type == source_type && converter.target_type == target_type
44
- return converter
45
- end
45
+ return converter if converter.source_type == source_type &&
46
+ converter.target_type == target_type
46
47
  end
47
48
 
48
49
  raise ConversionError, 'No converter capable of [%s] -> [%s]' % [source_type, target_type]
@@ -21,13 +21,13 @@ module Synapse
21
21
  end
22
22
 
23
23
  def ==(other)
24
- self.class === other and
25
- other.content == @content and
26
- other.content_type == @content_type and
24
+ self.class === other &&
25
+ other.content == @content &&
26
+ other.content_type == @content_type &&
27
27
  other.type == @type
28
28
  end
29
29
 
30
- alias eql? ==
30
+ alias_method :eql?, :==
31
31
 
32
32
  def hash
33
33
  @content.hash ^ @content_type.hash ^ @type.hash
@@ -16,16 +16,16 @@ module Synapse
16
16
  end
17
17
 
18
18
  def ==(other)
19
- self.class === other and
20
- other.name == @name and
19
+ self.class === other &&
20
+ other.name == @name &&
21
21
  other.revision == @revision
22
22
  end
23
23
 
24
- alias eql? ==
24
+ alias_method :eql?, :==
25
25
 
26
26
  def hash
27
27
  @name.hash ^ @revision.hash
28
28
  end
29
29
  end # SerializedType
30
30
  end # Serialization
31
- end
31
+ end