synapse-core 0.5.3 → 0.5.4

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