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
@@ -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