synapse-core 0.5.3 → 0.5.4

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 (49) hide show
  1. data/lib/synapse/command.rb +2 -0
  2. data/lib/synapse/command/callbacks/future.rb +50 -0
  3. data/lib/synapse/command/command_callback.rb +0 -47
  4. data/lib/synapse/command/message.rb +1 -1
  5. data/lib/synapse/common.rb +1 -0
  6. data/lib/synapse/common/concurrency/executor.rb +13 -0
  7. data/lib/synapse/common/message.rb +9 -2
  8. data/lib/synapse/common/message_builder.rb +5 -1
  9. data/lib/synapse/configuration.rb +1 -0
  10. data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +2 -8
  11. data/lib/synapse/configuration/component/event_sourcing.rb +3 -3
  12. data/lib/synapse/configuration/component/event_sourcing/repository.rb +19 -2
  13. data/lib/synapse/configuration/component/event_sourcing/{aggregate_snapshot_taker.rb → snapshot/aggregate_taker.rb} +4 -4
  14. data/lib/synapse/configuration/component/event_sourcing/{interval_snapshot_policy.rb → snapshot/interval_policy.rb} +0 -0
  15. data/lib/synapse/configuration/component/mixin/thread_pool.rb +26 -0
  16. data/lib/synapse/domain/message.rb +0 -24
  17. data/lib/synapse/domain/message_builder.rb +0 -9
  18. data/lib/synapse/event_sourcing.rb +1 -1
  19. data/lib/synapse/event_sourcing/aggregate_root.rb +2 -1
  20. data/lib/synapse/event_sourcing/caching.rb +66 -0
  21. data/lib/synapse/event_sourcing/repository.rb +22 -15
  22. data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +8 -9
  23. data/lib/synapse/event_sourcing/snapshot/policy.rb +1 -0
  24. data/lib/synapse/event_sourcing/snapshot/taker.rb +31 -2
  25. data/lib/synapse/repository/repository.rb +18 -4
  26. data/lib/synapse/repository/simple_repository.rb +8 -17
  27. data/lib/synapse/serialization/converter.rb +2 -2
  28. data/lib/synapse/serialization/converter/identity.rb +2 -2
  29. data/lib/synapse/serialization/converter/json.rb +3 -3
  30. data/lib/synapse/serialization/converter/ox.rb +3 -3
  31. data/lib/synapse/serialization/message/metadata.rb +2 -2
  32. data/lib/synapse/serialization/message/serialization_aware.rb +2 -2
  33. data/lib/synapse/serialization/message/serialized_message.rb +7 -24
  34. data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
  35. data/lib/synapse/uow.rb +0 -1
  36. data/lib/synapse/uow/nesting.rb +2 -2
  37. data/lib/synapse/uow/uow.rb +12 -13
  38. data/lib/synapse/version.rb +1 -1
  39. data/test/configuration/component/event_sourcing/repository_test.rb +24 -1
  40. data/test/event_sourcing/caching_test.rb +118 -0
  41. data/test/event_sourcing/repository_test.rb +2 -1
  42. data/test/event_sourcing/snapshot/aggregate_taker_test.rb +46 -16
  43. data/test/repository/locking_test.rb +1 -10
  44. data/test/serialization/serializer/attribute_test.rb +51 -0
  45. data/test/uow/uow_test.rb +9 -12
  46. metadata +12 -9
  47. data/lib/synapse/event_sourcing/storage_listener.rb +0 -34
  48. data/lib/synapse/uow/storage_listener.rb +0 -14
  49. data/test/event_sourcing/storage_listener_test.rb +0 -81
@@ -38,3 +38,5 @@ require 'synapse/command/gateway'
38
38
  require 'synapse/command/gateway/retry_scheduler'
39
39
  require 'synapse/command/gateway/interval_retry_scheduler'
40
40
  require 'synapse/command/gateway/retrying_callback'
41
+
42
+ require 'synapse/command/callbacks/future'
@@ -0,0 +1,50 @@
1
+ module Synapse
2
+ module Command
3
+ # Callback that provides a deferred result or exception from the execution of a command
4
+ class FutureCallback < CommandCallback
5
+ def initialize
6
+ @mutex = Mutex.new
7
+ @condition = ConditionVariable.new
8
+ end
9
+
10
+ # @raise [Exception] If an exception occured during command execution
11
+ # @param [Float] timeout
12
+ # @return [Object] The result from the command handler
13
+ def result(timeout = nil)
14
+ @mutex.synchronize do
15
+ unless @dispatched
16
+ @condition.wait @mutex, timeout
17
+ end
18
+
19
+ if @exception
20
+ raise @exception
21
+ end
22
+
23
+ @result
24
+ end
25
+ end
26
+
27
+ # @param [Object] result The result from the command handler
28
+ # @return [undefined]
29
+ def on_success(result)
30
+ @mutex.synchronize do
31
+ @dispatched = true
32
+ @result = result
33
+
34
+ @condition.broadcast
35
+ end
36
+ end
37
+
38
+ # @param [Exception] exception The cause of the failure
39
+ # @return [undefined]
40
+ def on_failure(exception)
41
+ @mutex.synchronize do
42
+ @dispatched = true
43
+ @exception = exception
44
+
45
+ @condition.broadcast
46
+ end
47
+ end
48
+ end # FutureCallback
49
+ end # Command
50
+ end
@@ -14,52 +14,5 @@ module Synapse
14
14
  # @return [undefined]
15
15
  def on_failure(exception); end
16
16
  end # CommandCallback
17
-
18
- # Callback that provides a deferred result or exception from the execution of a command
19
- class FutureCallback < CommandCallback
20
- def initialize
21
- @mutex = Mutex.new
22
- @condition = ConditionVariable.new
23
- end
24
-
25
- # @raise [Exception] If an exception occured during command execution
26
- # @param [Float] timeout
27
- # @return [Object] The result from the command handler
28
- def result(timeout = nil)
29
- @mutex.synchronize do
30
- unless @dispatched
31
- @condition.wait @mutex, timeout
32
- end
33
-
34
- if @exception
35
- raise @exception
36
- end
37
-
38
- @result
39
- end
40
- end
41
-
42
- # @param [Object] result The result from the command handler
43
- # @return [undefined]
44
- def on_success(result)
45
- @mutex.synchronize do
46
- @dispatched = true
47
- @result = result
48
-
49
- @condition.broadcast
50
- end
51
- end
52
-
53
- # @param [Exception] exception The cause of the failure
54
- # @return [undefined]
55
- def on_failure(exception)
56
- @mutex.synchronize do
57
- @dispatched = true
58
- @exception = exception
59
-
60
- @condition.broadcast
61
- end
62
- end
63
- end # FutureCallback
64
17
  end # Command
65
18
  end
@@ -12,7 +12,7 @@ module Synapse
12
12
  class CommandMessageBuilder < MessageBuilder
13
13
  # @return [CommandMessage]
14
14
  def build
15
- CommandMessage.new @id, @metadata, @payload
15
+ CommandMessage.new @id, @metadata, @payload, @timestamp
16
16
  end
17
17
  end
18
18
  end
@@ -3,6 +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/executor'
6
7
  require 'synapse/common/concurrency/identifier_lock'
7
8
  require 'synapse/common/concurrency/public_lock'
8
9
 
@@ -0,0 +1,13 @@
1
+ module Synapse
2
+ class Executor
3
+ # @return [undefined]
4
+ def execute(&block); end
5
+ end
6
+
7
+ class DirectExecutor < Executor
8
+ # @return [undefined]
9
+ def execute(&block)
10
+ block.call
11
+ end
12
+ end
13
+ end
@@ -22,14 +22,20 @@ module Synapse
22
22
  # @return [Object]
23
23
  attr_reader :payload
24
24
 
25
+ # Timestamp recorded when the message was created
26
+ # @return [Time]
27
+ attr_reader :timestamp
28
+
25
29
  # @param [String] id
26
30
  # @param [Hash] metadata
27
31
  # @param [Object] payload
32
+ # @param [Time] timestamp
28
33
  # @return [undefined]
29
- def initialize(id, metadata, payload)
34
+ def initialize(id, metadata, payload, timestamp)
30
35
  @id = id
31
36
  @metadata = metadata
32
37
  @payload = payload
38
+ @timestamp = timestamp
33
39
 
34
40
  @metadata.freeze
35
41
  end
@@ -88,7 +94,7 @@ module Synapse
88
94
  # @yield [MessageBuilder]
89
95
  # @return [Message]
90
96
  def self.build(&block)
91
- builder.build(&block)
97
+ builder.build &block
92
98
  end
93
99
 
94
100
  # Returns the type of builder that can be used to build this type of message
@@ -108,6 +114,7 @@ module Synapse
108
114
  builder.id = @id
109
115
  builder.metadata = metadata
110
116
  builder.payload = @payload
117
+ builder.timestamp = @timestamp
111
118
  end
112
119
  end
113
120
  end
@@ -10,6 +10,9 @@ module Synapse
10
10
  # @return [Object]
11
11
  attr_accessor :payload
12
12
 
13
+ # @return [Time]
14
+ attr_accessor :timestamp
15
+
13
16
  # Convenience method that yields a new builder, populates defaults and returns the newly
14
17
  # built message instance
15
18
  #
@@ -26,13 +29,14 @@ module Synapse
26
29
 
27
30
  # @return [Message]
28
31
  def build
29
- Message.new @id, @metadata, @payload
32
+ Message.new @id, @metadata, @payload, @timestamp
30
33
  end
31
34
 
32
35
  # @return [undefined]
33
36
  def populate_defaults
34
37
  @id ||= IdentifierFactory.instance.generate
35
38
  @metadata ||= Hash.new
39
+ @timestamp ||= Time.now
36
40
  end
37
41
  end
38
42
  end
@@ -5,6 +5,7 @@ require 'synapse/configuration/definition_builder'
5
5
  require 'synapse/configuration/dependent'
6
6
  require 'synapse/configuration/ext'
7
7
 
8
+ require 'synapse/configuration/component/mixin/thread_pool'
8
9
  require 'synapse/configuration/component/command_bus'
9
10
  require 'synapse/configuration/component/event_bus'
10
11
  # Has to be loaded before event sourcing
@@ -12,13 +12,7 @@ module Synapse
12
12
  # use_threads 8, 12
13
13
  # end
14
14
  class AsynchronousCommandBusDefinitionBuilder < SimpleCommandBusDefinitionBuilder
15
- # @param [Integer] min_threads
16
- # @param [Integer] max_threads
17
- # @return [undefined]
18
- def use_threads(min_threads, max_threads = nil)
19
- @min_threads = min_threads
20
- @max_threads = max_threads
21
- end
15
+ include ThreadPoolDefinitionBuilder
22
16
 
23
17
  protected
24
18
 
@@ -32,7 +26,7 @@ module Synapse
32
26
  # @return [AsynchronousCommandBus]
33
27
  def create_command_bus(unit_factory)
34
28
  command_bus = Command::AsynchronousCommandBus.new unit_factory
35
- command_bus.thread_pool = Thread.pool @min_threads, @max_threads
29
+ command_bus.thread_pool = create_thread_pool
36
30
 
37
31
  command_bus
38
32
  end
@@ -1,5 +1,5 @@
1
- require 'synapse/configuration/component/event_sourcing/aggregate_snapshot_taker'
2
- require 'synapse/configuration/component/event_sourcing/interval_snapshot_policy'
1
+ require 'synapse/configuration/component/event_sourcing/snapshot/aggregate_taker'
2
+ require 'synapse/configuration/component/event_sourcing/snapshot/interval_policy'
3
3
  require 'synapse/configuration/component/event_sourcing/repository'
4
4
 
5
5
  module Synapse
@@ -9,7 +9,7 @@ module Synapse
9
9
  builder :es_repository, EventSourcingRepositoryDefinitionBuilder
10
10
 
11
11
  # Creates and configures an aggregate snapshot taker
12
- builder :aggregate_snapshot_taker, AggregateSnapshotTakerDefinitionBuilder
12
+ builder :snapshot_taker, AggregateSnapshotTakerDefinitionBuilder
13
13
 
14
14
  # Creates and configures an interval-based snapshot policy
15
15
  builder :interval_snapshot_policy, IntervalSnapshotPolicyDefinitionBuilder
@@ -46,6 +46,18 @@ module Synapse
46
46
  @aggregate_factory = aggregate_factory
47
47
  end
48
48
 
49
+ # Changes the cache store that will be used to cache aggregates
50
+ #
51
+ # Note that if this is set, a caching implementation of the repository will be used in place
52
+ # of the non-caching implementation
53
+ #
54
+ # @see ActiveSupport::Cache::Store
55
+ # @param [Symbol] cache
56
+ # @return [undefined]
57
+ def use_cache(cache)
58
+ @cache = cache
59
+ end
60
+
49
61
  # @see EventSourcing::ConflictResolver
50
62
  # @param [Symbol] conflict_resolver
51
63
  # @return [undefined]
@@ -86,11 +98,16 @@ module Synapse
86
98
  use_snapshot_taker :snapshot_taker
87
99
 
88
100
  use_factory do
89
- aggregate_factory = resolve @aggregate_factory
101
+ factory = resolve @aggregate_factory
90
102
  event_store = resolve @event_store
91
103
  lock_manager = build_lock_manager
92
104
 
93
- repository = EventSourcing::EventSourcingRepository.new aggregate_factory, event_store, lock_manager
105
+ if @cache
106
+ repository = EventSourcing::CachingEventSourcingRepository.new factory, event_store, lock_manager
107
+ repository.cache = resolve @cache
108
+ else
109
+ repository = EventSourcing::EventSourcingRepository.new factory, event_store, lock_manager
110
+ end
94
111
 
95
112
  # Optional dependencies
96
113
  repository.conflict_resolver = resolve @conflict_resolver, true
@@ -3,10 +3,10 @@ module Synapse
3
3
  # Definition builder used to create aggregate snapshot takers
4
4
  #
5
5
  # @example The minimum possible effort to build an aggregate snapshot taker
6
- # aggregate_snapshot_taker
6
+ # snapshot_taker
7
7
  #
8
8
  # @example Build an aggregate snapshot taker using an alternate event store and factory tag
9
- # aggregate_snapshot_taker :alt_snapshot_taker do
9
+ # snapshot_taker :alt_snapshot_taker do
10
10
  # use_aggregate_factory_tag :alt_factory_tag
11
11
  # use_event_store :alt_event_store
12
12
  # end
@@ -40,8 +40,8 @@ module Synapse
40
40
  use_event_store :event_store
41
41
 
42
42
  use_factory do
43
- event_store = resolve @event_store
44
- snapshot_taker = EventSourcing::AggregateSnapshotTaker.new event_store
43
+ snapshot_taker = EventSourcing::AggregateSnapshotTaker.new
44
+ snapshot_taker.event_store = resolve @event_store
45
45
 
46
46
  with_tagged @aggregate_factory_tag do |factory|
47
47
  snapshot_taker.register_factory factory
@@ -0,0 +1,26 @@
1
+ module Synapse
2
+ module Configuration
3
+ # Mixin for a definition builder that creates a service that is backed by a thread pool
4
+ module ThreadPoolDefinitionBuilder
5
+ extend ActiveSupport::Concern
6
+
7
+ # Sets the upper and lower limits of the size of the thread pool
8
+ #
9
+ # @param [Integer] min_threads
10
+ # @param [Integer] max_threads
11
+ # @return [undefined]
12
+ def use_threads(min_threads, max_threads = nil)
13
+ @min_threads = min_threads
14
+ @max_threads = max_threads
15
+ end
16
+
17
+ protected
18
+
19
+ # Creates a thread pool with the configured options
20
+ # @return [Thread::Pool]
21
+ def create_thread_pool
22
+ Thread.pool @min_threads, @max_threads
23
+ end
24
+ end # ThreadPoolDefinitionBuilder
25
+ end # Configuration
26
+ end
@@ -4,34 +4,10 @@ module Synapse
4
4
  # to another component of the application. It contains the relevant data for other
5
5
  # components to act upon.
6
6
  class EventMessage < Message
7
- # The timestamp of when the event was reported
8
- # @return [Time]
9
- attr_reader :timestamp
10
-
11
- # @param [String] id
12
- # @param [Hash] metadata
13
- # @param [Object] payload
14
- # @param [Time] timestamp
15
- # @return [undefined]
16
- def initialize(id, metadata, payload, timestamp)
17
- super id, metadata, payload
18
- @timestamp = timestamp
19
- end
20
-
21
7
  # @return [Class]
22
8
  def self.builder
23
9
  EventMessageBuilder
24
10
  end
25
-
26
- protected
27
-
28
- # @param [EventMessageBuilder] builder
29
- # @param [Hash] metadata
30
- # @return [undefined]
31
- def build_duplicate(builder, metadata)
32
- super
33
- builder.timestamp = @timestamp
34
- end
35
11
  end
36
12
 
37
13
  # Message that contains a domain event as a payload that represents a state change in the domain.
@@ -2,19 +2,10 @@ module Synapse
2
2
  module Domain
3
3
  # Message builder capable of producing EventMessage instances
4
4
  class EventMessageBuilder < MessageBuilder
5
- # @return [Time]
6
- attr_accessor :timestamp
7
-
8
5
  # @return [EventMessage]
9
6
  def build
10
7
  EventMessage.new @id, @metadata, @payload, @timestamp
11
8
  end
12
-
13
- # @return [undefined]
14
- def populate_defaults
15
- super
16
- @timestamp ||= Time.now
17
- end
18
9
  end
19
10
 
20
11
  # Message builder capable of producing DomainEventMessage instances
@@ -1,7 +1,7 @@
1
1
  require 'synapse/event_sourcing/aggregate_factory'
2
2
  require 'synapse/event_sourcing/conflict_resolver'
3
3
  require 'synapse/event_sourcing/repository'
4
- require 'synapse/event_sourcing/storage_listener'
4
+ require 'synapse/event_sourcing/caching'
5
5
  require 'synapse/event_sourcing/stream_decorator'
6
6
 
7
7
  require 'synapse/event_sourcing/member'
@@ -49,7 +49,8 @@ module Synapse
49
49
  raise 'Aggregate has already been initialized'
50
50
  end
51
51
 
52
- pre_initialize
52
+ # If this is loaded from a snapshot, don't pre-initialize
53
+ pre_initialize unless @initial_version
53
54
 
54
55
  @initial_version = stream.peek.sequence_number
55
56
 
@@ -0,0 +1,66 @@
1
+ module Synapse
2
+ module EventSourcing
3
+ # Implementation of an event sourcing repository that uses a cache to improve performance when
4
+ # loading aggregates
5
+ #
6
+ # Caching is not compatible with optimistic locking
7
+ #
8
+ # Note that if an error occurs while saving an aggregate, it will be invalidated from the cache
9
+ # to prevent aggregates being returned from the cache that were not fully persisted to disk.
10
+ class CachingEventSourcingRepository < EventSourcingRepository
11
+ # @return [ActiveSupport::Cache::Store]
12
+ attr_accessor :cache
13
+
14
+ protected
15
+
16
+ # @raise [AggregateNotFoundError]
17
+ # If the aggregate with the given identifier could not be found
18
+ # @raise [AggregateDeletedError]
19
+ # If the loaded aggregate has been marked as deleted
20
+ # @raise [ConflictingModificationError]
21
+ # If the expected version doesn't match the aggregate's actual version
22
+ # @param [Object] aggregate_id
23
+ # @param [Integer] expected_version
24
+ # @return [AggregateRoot]
25
+ def perform_load(aggregate_id, expected_version)
26
+ aggregate = @cache.fetch aggregate_id
27
+
28
+ if aggregate.nil?
29
+ aggregate = super aggregate_id, expected_version
30
+ elsif aggregate.deleted?
31
+ raise AggregateDeletedError
32
+ end
33
+
34
+ register_listener CacheClearingUnitOfWorkListener.new aggregate_id, @cache
35
+
36
+ aggregate
37
+ end
38
+
39
+ # @param [AggregateRoot] aggregate
40
+ # @return [undefined]
41
+ def save_aggregate(aggregate)
42
+ super aggregate
43
+ @cache.write aggregate.id, aggregate
44
+ end
45
+ end # CachingEventSourcingRepository
46
+
47
+ # Listener that removes an aggregate from the cache if the unit of work is rolled back
48
+ # @api private
49
+ class CacheClearingUnitOfWorkListener < UnitOfWork::UnitOfWorkListener
50
+ # @param [Object] aggregate_id
51
+ # @param [ActiveSupport::Cache::Store] cache
52
+ # @return [undefined]
53
+ def initialize(aggregate_id, cache)
54
+ @aggregate_id = aggregate_id
55
+ @cache = cache
56
+ end
57
+
58
+ # @param [UnitOfWork] unit
59
+ # @param [Error] cause
60
+ # @return [undefined]
61
+ def on_rollback(unit, cause = nil)
62
+ @cache.delete @aggregate_id
63
+ end
64
+ end # CacheClearingUnitOfWorkListener
65
+ end # EventSourcing
66
+ end