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.
- data/lib/synapse/command.rb +2 -0
- data/lib/synapse/command/callbacks/future.rb +50 -0
- data/lib/synapse/command/command_callback.rb +0 -47
- data/lib/synapse/command/message.rb +1 -1
- data/lib/synapse/common.rb +1 -0
- data/lib/synapse/common/concurrency/executor.rb +13 -0
- data/lib/synapse/common/message.rb +9 -2
- data/lib/synapse/common/message_builder.rb +5 -1
- data/lib/synapse/configuration.rb +1 -0
- data/lib/synapse/configuration/component/command_bus/async_command_bus.rb +2 -8
- data/lib/synapse/configuration/component/event_sourcing.rb +3 -3
- data/lib/synapse/configuration/component/event_sourcing/repository.rb +19 -2
- data/lib/synapse/configuration/component/event_sourcing/{aggregate_snapshot_taker.rb → snapshot/aggregate_taker.rb} +4 -4
- data/lib/synapse/configuration/component/event_sourcing/{interval_snapshot_policy.rb → snapshot/interval_policy.rb} +0 -0
- data/lib/synapse/configuration/component/mixin/thread_pool.rb +26 -0
- data/lib/synapse/domain/message.rb +0 -24
- data/lib/synapse/domain/message_builder.rb +0 -9
- data/lib/synapse/event_sourcing.rb +1 -1
- data/lib/synapse/event_sourcing/aggregate_root.rb +2 -1
- data/lib/synapse/event_sourcing/caching.rb +66 -0
- data/lib/synapse/event_sourcing/repository.rb +22 -15
- data/lib/synapse/event_sourcing/snapshot/aggregate_taker.rb +8 -9
- data/lib/synapse/event_sourcing/snapshot/policy.rb +1 -0
- data/lib/synapse/event_sourcing/snapshot/taker.rb +31 -2
- data/lib/synapse/repository/repository.rb +18 -4
- data/lib/synapse/repository/simple_repository.rb +8 -17
- data/lib/synapse/serialization/converter.rb +2 -2
- data/lib/synapse/serialization/converter/identity.rb +2 -2
- data/lib/synapse/serialization/converter/json.rb +3 -3
- data/lib/synapse/serialization/converter/ox.rb +3 -3
- data/lib/synapse/serialization/message/metadata.rb +2 -2
- data/lib/synapse/serialization/message/serialization_aware.rb +2 -2
- data/lib/synapse/serialization/message/serialized_message.rb +7 -24
- data/lib/synapse/serialization/message/serialized_message_builder.rb +4 -4
- data/lib/synapse/uow.rb +0 -1
- data/lib/synapse/uow/nesting.rb +2 -2
- data/lib/synapse/uow/uow.rb +12 -13
- data/lib/synapse/version.rb +1 -1
- data/test/configuration/component/event_sourcing/repository_test.rb +24 -1
- data/test/event_sourcing/caching_test.rb +118 -0
- data/test/event_sourcing/repository_test.rb +2 -1
- data/test/event_sourcing/snapshot/aggregate_taker_test.rb +46 -16
- data/test/repository/locking_test.rb +1 -10
- data/test/serialization/serializer/attribute_test.rb +51 -0
- data/test/uow/uow_test.rb +9 -12
- metadata +12 -9
- data/lib/synapse/event_sourcing/storage_listener.rb +0 -34
- data/lib/synapse/uow/storage_listener.rb +0 -14
- data/test/event_sourcing/storage_listener_test.rb +0 -81
data/lib/synapse/command.rb
CHANGED
@@ -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
|
data/lib/synapse/common.rb
CHANGED
@@ -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
|
|
@@ -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
|
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
|
-
|
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 =
|
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/
|
2
|
-
require 'synapse/configuration/component/event_sourcing/
|
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 :
|
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
|
-
|
101
|
+
factory = resolve @aggregate_factory
|
90
102
|
event_store = resolve @event_store
|
91
103
|
lock_manager = build_lock_manager
|
92
104
|
|
93
|
-
|
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
|
-
#
|
6
|
+
# snapshot_taker
|
7
7
|
#
|
8
8
|
# @example Build an aggregate snapshot taker using an alternate event store and factory tag
|
9
|
-
#
|
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
|
-
|
44
|
-
snapshot_taker =
|
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
|
File without changes
|
@@ -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/
|
4
|
+
require 'synapse/event_sourcing/caching'
|
5
5
|
require 'synapse/event_sourcing/stream_decorator'
|
6
6
|
|
7
7
|
require 'synapse/event_sourcing/member'
|
@@ -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
|