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