synapse-core 0.1.2
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.rb +351 -0
- data/lib/synapse/command/command_bus.rb +45 -0
- data/lib/synapse/command/command_callback.rb +18 -0
- data/lib/synapse/command/command_filter.rb +17 -0
- data/lib/synapse/command/command_handler.rb +13 -0
- data/lib/synapse/command/dispatch_interceptor.rb +16 -0
- data/lib/synapse/command/duplication.rb +43 -0
- data/lib/synapse/command/errors.rb +27 -0
- data/lib/synapse/command/filters/validation.rb +32 -0
- data/lib/synapse/command/gateway.rb +34 -0
- data/lib/synapse/command/interceptor_chain.rb +31 -0
- data/lib/synapse/command/interceptors/serialization.rb +35 -0
- data/lib/synapse/command/message.rb +19 -0
- data/lib/synapse/command/rollback_policy.rb +22 -0
- data/lib/synapse/command/simple_command_bus.rb +138 -0
- data/lib/synapse/command/wiring.rb +47 -0
- data/lib/synapse/domain/aggregate_root.rb +121 -0
- data/lib/synapse/domain/event_container.rb +127 -0
- data/lib/synapse/domain/message.rb +82 -0
- data/lib/synapse/domain/message_builder.rb +34 -0
- data/lib/synapse/domain/stream.rb +108 -0
- data/lib/synapse/duplication.rb +60 -0
- data/lib/synapse/errors.rb +13 -0
- data/lib/synapse/event_bus/event_bus.rb +40 -0
- data/lib/synapse/event_bus/event_listener.rb +16 -0
- data/lib/synapse/event_bus/event_listener_proxy.rb +12 -0
- data/lib/synapse/event_bus/simple_event_bus.rb +69 -0
- data/lib/synapse/event_bus/wiring.rb +23 -0
- data/lib/synapse/event_sourcing/aggregate_factory.rb +69 -0
- data/lib/synapse/event_sourcing/aggregate_root.rb +104 -0
- data/lib/synapse/event_sourcing/conflict_resolver.rb +80 -0
- data/lib/synapse/event_sourcing/entity.rb +64 -0
- data/lib/synapse/event_sourcing/member.rb +72 -0
- data/lib/synapse/event_sourcing/repository.rb +119 -0
- data/lib/synapse/event_sourcing/snapshot/count_stream.rb +86 -0
- data/lib/synapse/event_sourcing/snapshot/count_trigger.rb +91 -0
- data/lib/synapse/event_sourcing/snapshot/taker.rb +73 -0
- data/lib/synapse/event_sourcing/storage_listener.rb +34 -0
- data/lib/synapse/event_sourcing/stream_decorator.rb +25 -0
- data/lib/synapse/event_store/errors.rb +16 -0
- data/lib/synapse/event_store/event_store.rb +43 -0
- data/lib/synapse/event_store/in_memory.rb +59 -0
- data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
- data/lib/synapse/event_store/mongo/event_store.rb +86 -0
- data/lib/synapse/event_store/mongo/per_commit_strategy.rb +253 -0
- data/lib/synapse/event_store/mongo/per_event_strategy.rb +143 -0
- data/lib/synapse/event_store/mongo/storage_strategy.rb +113 -0
- data/lib/synapse/event_store/mongo/template.rb +73 -0
- data/lib/synapse/identifier.rb +23 -0
- data/lib/synapse/message.rb +101 -0
- data/lib/synapse/message_builder.rb +38 -0
- data/lib/synapse/process_manager/correlation.rb +32 -0
- data/lib/synapse/process_manager/correlation_resolver.rb +14 -0
- data/lib/synapse/process_manager/correlation_set.rb +58 -0
- data/lib/synapse/process_manager/process.rb +71 -0
- data/lib/synapse/repository/errors.rb +26 -0
- data/lib/synapse/repository/lock_manager.rb +40 -0
- data/lib/synapse/repository/locking.rb +97 -0
- data/lib/synapse/repository/pessimistic_lock_manager.rb +61 -0
- data/lib/synapse/repository/repository.rb +109 -0
- data/lib/synapse/serialization/converter.rb +39 -0
- data/lib/synapse/serialization/converter/chain.rb +45 -0
- data/lib/synapse/serialization/converter/factory.rb +68 -0
- data/lib/synapse/serialization/converter/identity.rb +29 -0
- data/lib/synapse/serialization/converter/json.rb +31 -0
- data/lib/synapse/serialization/converter/ox.rb +31 -0
- data/lib/synapse/serialization/errors.rb +12 -0
- data/lib/synapse/serialization/lazy_object.rb +61 -0
- data/lib/synapse/serialization/message/data.rb +25 -0
- data/lib/synapse/serialization/message/metadata.rb +13 -0
- data/lib/synapse/serialization/message/serialization_aware.rb +17 -0
- data/lib/synapse/serialization/message/serialization_aware_message.rb +66 -0
- data/lib/synapse/serialization/message/serialized_message.rb +201 -0
- data/lib/synapse/serialization/message/serialized_message_builder.rb +64 -0
- data/lib/synapse/serialization/message/serialized_object_cache.rb +50 -0
- data/lib/synapse/serialization/message/serializer.rb +47 -0
- data/lib/synapse/serialization/revision_resolver.rb +30 -0
- data/lib/synapse/serialization/serialized_object.rb +37 -0
- data/lib/synapse/serialization/serialized_type.rb +31 -0
- data/lib/synapse/serialization/serializer.rb +98 -0
- data/lib/synapse/serialization/serializer/marshal.rb +32 -0
- data/lib/synapse/serialization/serializer/oj.rb +34 -0
- data/lib/synapse/serialization/serializer/ox.rb +31 -0
- data/lib/synapse/uow/factory.rb +28 -0
- data/lib/synapse/uow/listener.rb +79 -0
- data/lib/synapse/uow/listener_collection.rb +93 -0
- data/lib/synapse/uow/nesting.rb +262 -0
- data/lib/synapse/uow/provider.rb +71 -0
- data/lib/synapse/uow/storage_listener.rb +14 -0
- data/lib/synapse/uow/transaction_manager.rb +27 -0
- data/lib/synapse/uow/uow.rb +178 -0
- data/lib/synapse/upcasting/chain.rb +78 -0
- data/lib/synapse/upcasting/context.rb +58 -0
- data/lib/synapse/upcasting/data.rb +30 -0
- data/lib/synapse/upcasting/single_upcaster.rb +57 -0
- data/lib/synapse/upcasting/upcaster.rb +55 -0
- data/lib/synapse/version.rb +3 -0
- data/lib/synapse/wiring/message_wiring.rb +41 -0
- data/lib/synapse/wiring/wire.rb +55 -0
- data/lib/synapse/wiring/wire_registry.rb +61 -0
- data/test/command/duplication_test.rb +54 -0
- data/test/command/gateway_test.rb +25 -0
- data/test/command/interceptor_chain_test.rb +26 -0
- data/test/command/serialization_test.rb +37 -0
- data/test/command/simple_command_bus_test.rb +141 -0
- data/test/command/validation_test.rb +42 -0
- data/test/command/wiring_test.rb +73 -0
- data/test/domain/aggregate_root_test.rb +57 -0
- data/test/domain/fixtures.rb +31 -0
- data/test/domain/message_test.rb +61 -0
- data/test/domain/stream_test.rb +35 -0
- data/test/duplication_test.rb +40 -0
- data/test/event_bus/wiring_test.rb +46 -0
- data/test/event_sourcing/aggregate_factory_test.rb +28 -0
- data/test/event_sourcing/aggregate_root_test.rb +76 -0
- data/test/event_sourcing/entity_test.rb +34 -0
- data/test/event_sourcing/fixtures.rb +85 -0
- data/test/event_sourcing/repository_test.rb +102 -0
- data/test/event_sourcing/snapshot/aggregate_taker_test.rb +39 -0
- data/test/event_sourcing/snapshot/deferred_taker_test.rb +19 -0
- data/test/event_sourcing/snapshot/integration_test.rb +65 -0
- data/test/event_sourcing/storage_listener_test.rb +77 -0
- data/test/event_store/in_memory_test.rb +47 -0
- data/test/process_manager/correlation_set_test.rb +49 -0
- data/test/process_manager/correlation_test.rb +24 -0
- data/test/process_manager/process_test.rb +52 -0
- data/test/repository/locking_test.rb +101 -0
- data/test/serialization/converter/factory_test.rb +33 -0
- data/test/serialization/converter/identity_test.rb +17 -0
- data/test/serialization/converter/json_test.rb +31 -0
- data/test/serialization/converter/ox_test.rb +40 -0
- data/test/serialization/fixtures.rb +17 -0
- data/test/serialization/lazy_object_test.rb +32 -0
- data/test/serialization/message/metadata_test.rb +19 -0
- data/test/serialization/message/serialization_aware_message_test.rb +88 -0
- data/test/serialization/message/serialized_message_builder_test.rb +41 -0
- data/test/serialization/message/serialized_message_test.rb +140 -0
- data/test/serialization/message/serializer_test.rb +50 -0
- data/test/serialization/revision_resolver_test.rb +12 -0
- data/test/serialization/serialized_object_test.rb +36 -0
- data/test/serialization/serialized_type_test.rb +27 -0
- data/test/serialization/serializer/marshal_test.rb +22 -0
- data/test/serialization/serializer/oj_test.rb +24 -0
- data/test/serialization/serializer/ox_test.rb +36 -0
- data/test/serialization/serializer_test.rb +20 -0
- data/test/test_helper.rb +19 -0
- data/test/uow/factory_test.rb +23 -0
- data/test/uow/outer_commit_listener_test.rb +50 -0
- data/test/uow/provider_test.rb +70 -0
- data/test/uow/uow_test.rb +337 -0
- data/test/upcasting/chain_test.rb +29 -0
- data/test/upcasting/fixtures.rb +66 -0
- data/test/wiring/wire_registry_test.rb +60 -0
- data/test/wiring/wire_test.rb +51 -0
- metadata +263 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Storage strategy that stores each event as its own document
|
|
5
|
+
class DocumentPerEventStrategy < StorageStrategy
|
|
6
|
+
# @param [String] type_identifier Type identifier for the aggregate
|
|
7
|
+
# @param [Array] events Domain events to be committed
|
|
8
|
+
# @return [Array]
|
|
9
|
+
def create_documents(type_identifier, events)
|
|
10
|
+
documents = Array.new
|
|
11
|
+
|
|
12
|
+
events.each do |event|
|
|
13
|
+
document = EventDocument.new
|
|
14
|
+
document.from_event event, type_identifier, @serializer
|
|
15
|
+
|
|
16
|
+
documents.push document.to_hash
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
documents
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param [Hash] hash
|
|
23
|
+
# @param [Object] aggregate_id
|
|
24
|
+
# @return [Array]
|
|
25
|
+
def extract_events(hash, aggregate_id)
|
|
26
|
+
document = EventDocument.new
|
|
27
|
+
document.from_hash(hash).to_events(aggregate_id, @serializer, @upcaster_chain)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Mongo document that represents a single domain event
|
|
31
|
+
class EventDocument < Serialization::SerializedDomainEventData
|
|
32
|
+
# @return [String]
|
|
33
|
+
attr_reader :id
|
|
34
|
+
|
|
35
|
+
# @return [Time]
|
|
36
|
+
attr_reader :timestamp
|
|
37
|
+
|
|
38
|
+
# @return [Object]
|
|
39
|
+
attr_reader :aggregate_id
|
|
40
|
+
|
|
41
|
+
# @return [Integer]
|
|
42
|
+
attr_reader :sequence_number
|
|
43
|
+
|
|
44
|
+
# @param [SerializedObject]
|
|
45
|
+
def metadata
|
|
46
|
+
Serialization::SerializedMetadata.new @metadata, @metadata.class
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param [SerializedObject]
|
|
50
|
+
def payload
|
|
51
|
+
Serialization::SerializedObject.new @payload, @payload.class,
|
|
52
|
+
Serialization::SerializedType.new(@payload_type, @payload_revision)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param [DomainEventMessage] event
|
|
56
|
+
# @param [String] type_identifier
|
|
57
|
+
# @param [Serializer] serializer
|
|
58
|
+
# @return [EventDocument]
|
|
59
|
+
def from_event(event, type_identifier, serializer)
|
|
60
|
+
serialization_target = String
|
|
61
|
+
if serializer.can_serialize_to? Hash
|
|
62
|
+
serialization_target = Hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
serialized_metadata = serializer.serialize_metadata event, serialization_target
|
|
66
|
+
serialized_payload = serializer.serialize_payload event, serialization_target
|
|
67
|
+
|
|
68
|
+
@id = event.id
|
|
69
|
+
@metadata = serialized_metadata.content
|
|
70
|
+
@payload = serialized_payload.content
|
|
71
|
+
@payload_type = serialized_payload.type.name
|
|
72
|
+
@payload_revision = serialized_payload.type.revision
|
|
73
|
+
@timestamp = event.timestamp
|
|
74
|
+
@aggregate_id = event.aggregate_id
|
|
75
|
+
@aggregate_type = type_identifier
|
|
76
|
+
@sequence_number = event.sequence_number
|
|
77
|
+
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @param [Hash] hash
|
|
82
|
+
# @return [EventDocument]
|
|
83
|
+
def from_hash(hash)
|
|
84
|
+
hash.symbolize_keys!
|
|
85
|
+
|
|
86
|
+
@id = hash.fetch :_id
|
|
87
|
+
@metadata = hash.fetch :metadata
|
|
88
|
+
@payload = hash.fetch :payload
|
|
89
|
+
@payload_type = hash.fetch :payload_type
|
|
90
|
+
@payload_revision = hash.fetch :payload_revision
|
|
91
|
+
@timestamp = hash.fetch :timestamp
|
|
92
|
+
@aggregate_id = hash.fetch :aggregate_id
|
|
93
|
+
@aggregate_type = hash.fetch :aggregate_type
|
|
94
|
+
@sequence_number = hash.fetch :sequence_number
|
|
95
|
+
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# @return [Hash]
|
|
100
|
+
def to_hash
|
|
101
|
+
{ _id: @id,
|
|
102
|
+
metadata: @metadata,
|
|
103
|
+
payload: @payload,
|
|
104
|
+
payload_type: @payload_type,
|
|
105
|
+
payload_revision: @payload_revision,
|
|
106
|
+
timestamp: @timestamp,
|
|
107
|
+
aggregate_id: @aggregate_id,
|
|
108
|
+
aggregate_type: @aggregate_type,
|
|
109
|
+
sequence_number: @sequence_number }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @param [Object] aggregate_id
|
|
113
|
+
# @param [Serializer] serializer
|
|
114
|
+
# @param [UpcasterChain] upcaster_chain
|
|
115
|
+
# @return [Array]
|
|
116
|
+
def to_events(aggregate_id, serializer, upcaster_chain)
|
|
117
|
+
events = Array.new
|
|
118
|
+
|
|
119
|
+
context = Upcasting::SerializedDomainEventUpcastingContext.new self, aggregate_id, serializer
|
|
120
|
+
upcast_objects = upcaster_chain.upcast payload, context
|
|
121
|
+
upcast_objects.each do |upcast_object|
|
|
122
|
+
upcast_data = Upcasting::UpcastSerializedDomainEventData.new self, aggregate_id, upcast_object
|
|
123
|
+
|
|
124
|
+
builder = Serialization::SerializedDomainEventMessageBuilder.new
|
|
125
|
+
|
|
126
|
+
# Prevent duplicate serialization of metadata if it was accessed during upcasting
|
|
127
|
+
metadata = context.serialized_metadata
|
|
128
|
+
if metadata.deserialized?
|
|
129
|
+
builder.metadata = Serialization::DeserializedObject.new metadata.deserialized
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
builder.from_data upcast_data, serializer
|
|
133
|
+
|
|
134
|
+
events.push builder.build
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
events
|
|
138
|
+
end
|
|
139
|
+
end # EventDocument
|
|
140
|
+
end # DocumentPerEventStrategy
|
|
141
|
+
end # Mongo
|
|
142
|
+
end # EventStore
|
|
143
|
+
end # Synapse
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Represents a mechanism used to structure how events are stored in the database
|
|
5
|
+
# @abstract
|
|
6
|
+
class StorageStrategy
|
|
7
|
+
# @param [MongoTemplate] template
|
|
8
|
+
# @param [Serializer] serializer
|
|
9
|
+
# @param [UpcasterChain] upcaster_chain
|
|
10
|
+
# @return [undefined]
|
|
11
|
+
def initialize(template, serializer, upcaster_chain)
|
|
12
|
+
@template = template
|
|
13
|
+
@serializer = Serialization::MessageSerializer.new serializer
|
|
14
|
+
@upcaster_chain = upcaster_chain
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Creates documents that will represent the events being committed to the event store
|
|
18
|
+
#
|
|
19
|
+
# @abstract
|
|
20
|
+
# @param [String] type_identifier Type identifier for the aggregate
|
|
21
|
+
# @param [Array] events Domain events to be committed
|
|
22
|
+
# @return [Array]
|
|
23
|
+
def create_documents(type_identifier, events); end
|
|
24
|
+
|
|
25
|
+
# Extracts individual event messages from the given document
|
|
26
|
+
#
|
|
27
|
+
# The given aggregate identifier is passed so that event messages can have the actual
|
|
28
|
+
# identifier object instead of the serialized aggregate identifier.
|
|
29
|
+
#
|
|
30
|
+
# @abstract
|
|
31
|
+
# @param [Hash] document
|
|
32
|
+
# @param [Object] aggregate_id
|
|
33
|
+
# @return [Array]
|
|
34
|
+
def extract_events(document, aggregate_id); end
|
|
35
|
+
|
|
36
|
+
# Aliases of the Mongo constants for ascending and descending
|
|
37
|
+
ASCENDING = ::Mongo::ASCENDING
|
|
38
|
+
DESCENDING = ::Mongo::DESCENDING
|
|
39
|
+
|
|
40
|
+
# Provides a cursor for accessing all events for an aggregate with the given identifier
|
|
41
|
+
# and type identifier, with a sequence number equal to or greater than the given first
|
|
42
|
+
# sequence number
|
|
43
|
+
#
|
|
44
|
+
# The returned documents should be ordered chronologically, typically by using the
|
|
45
|
+
# sequence number.
|
|
46
|
+
#
|
|
47
|
+
# @param [String] type_identifier
|
|
48
|
+
# @param [Object] aggregate_id
|
|
49
|
+
# @param [Integer] first_sequence_number
|
|
50
|
+
# @return [Mongo::Cursor]
|
|
51
|
+
def fetch_events(type_identifier, aggregate_id, first_sequence_number)
|
|
52
|
+
filter = {
|
|
53
|
+
aggregate_id: aggregate_id,
|
|
54
|
+
aggregate_type: type_identifier,
|
|
55
|
+
sequence_number: {
|
|
56
|
+
'$gte' => first_sequence_number
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
sort = {
|
|
61
|
+
sequence_number: ASCENDING
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@template.event_collection.find(filter).sort(sort)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Finds the document containing the most recent snapshot event for an aggregate with the
|
|
68
|
+
# given identifier and type identifier
|
|
69
|
+
#
|
|
70
|
+
# @param [String] type_identifier
|
|
71
|
+
# @param [Object] aggregate_id
|
|
72
|
+
# @return [Mongo::Cursor]
|
|
73
|
+
def fetch_last_snapshot(type_identifier, aggregate_id)
|
|
74
|
+
filter = {
|
|
75
|
+
aggregate_id: aggregate_id,
|
|
76
|
+
aggregate_type: type_identifier
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
sort = {
|
|
80
|
+
sequence_number: DESCENDING
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@template.snapshot_collection.find(filter).sort(sort).limit(1)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Ensures that the correct indexes are in place
|
|
87
|
+
# @return [undefined]
|
|
88
|
+
def ensure_indexes
|
|
89
|
+
options = {
|
|
90
|
+
name: 'unique_aggregate_index',
|
|
91
|
+
unique: true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
spec = {
|
|
95
|
+
aggregate_id: ASCENDING,
|
|
96
|
+
aggregate_type: ASCENDING,
|
|
97
|
+
sequence_number: ASCENDING
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@template.event_collection.ensure_index spec, options
|
|
101
|
+
|
|
102
|
+
spec = {
|
|
103
|
+
aggregate_id: ASCENDING,
|
|
104
|
+
aggregate_type: ASCENDING,
|
|
105
|
+
sequence_number: DESCENDING
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@template.snapshot_collection.ensure_index spec, options
|
|
109
|
+
end
|
|
110
|
+
end # StorageStrategy
|
|
111
|
+
end # Mongo
|
|
112
|
+
end # EventStore
|
|
113
|
+
end # Synapse
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Represents a mechanism for accessing collections required by the Mongo event store
|
|
5
|
+
# @abstract
|
|
6
|
+
class MongoTemplate
|
|
7
|
+
# Returns a reference to the collection containing domain events
|
|
8
|
+
#
|
|
9
|
+
# @abstract
|
|
10
|
+
# @return [Mongo::Collection]
|
|
11
|
+
def event_collection; end
|
|
12
|
+
|
|
13
|
+
# Returns a reference to the collection containing snapshot events
|
|
14
|
+
#
|
|
15
|
+
# @abstract
|
|
16
|
+
# @return [Mongo::Collection]
|
|
17
|
+
def snapshot_collection; end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class DefaultMongoTemplate
|
|
21
|
+
# @return [String] Name of the database to use
|
|
22
|
+
attr_accessor :database_name
|
|
23
|
+
|
|
24
|
+
# @return [String] Username to authenticate with (optional)
|
|
25
|
+
attr_accessor :username
|
|
26
|
+
|
|
27
|
+
# @return [String] Password to authenticate with (optional)
|
|
28
|
+
attr_accessor :password
|
|
29
|
+
|
|
30
|
+
# @return [String] Name of the collection containing domain events
|
|
31
|
+
attr_accessor :event_collection
|
|
32
|
+
|
|
33
|
+
# @return [String] Name of the collection containing snapshot events
|
|
34
|
+
attr_accessor :snapshot_collection
|
|
35
|
+
|
|
36
|
+
# @param [Mongo::MongoClient] client
|
|
37
|
+
# @return [undefined]
|
|
38
|
+
def initialize(client)
|
|
39
|
+
@client = client
|
|
40
|
+
|
|
41
|
+
@database_name = 'synapse'
|
|
42
|
+
@event_collection_name = 'domain_events'
|
|
43
|
+
@snapshot_collection_name = 'snapshot_events'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Mongo::Collection]
|
|
47
|
+
def event_collection
|
|
48
|
+
database.collection @event_collection_name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Mongo::Collection]
|
|
52
|
+
def snapshot_collection
|
|
53
|
+
database.collection @snapshot_collection_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @return [Mongo::DB]
|
|
59
|
+
def database
|
|
60
|
+
unless @database
|
|
61
|
+
@database = @client.db @database_name
|
|
62
|
+
|
|
63
|
+
if @username and @password
|
|
64
|
+
@database.authenticate @username, @password
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@database
|
|
69
|
+
end
|
|
70
|
+
end # DefaultMongoTemplate
|
|
71
|
+
end # Mongo
|
|
72
|
+
end # EventStore
|
|
73
|
+
end # Synapse
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'securerandom'
|
|
2
|
+
|
|
3
|
+
module Synapse
|
|
4
|
+
# Represents a mechanism for generating a unique identifier for domain objects
|
|
5
|
+
class IdentifierFactory
|
|
6
|
+
class_attribute :instance
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Implementation of an identifier factory that generates pseudo-random GUIDs
|
|
10
|
+
#
|
|
11
|
+
# @example The identifier format produced by this factory
|
|
12
|
+
# factory = GuidIdentifierFactory.new
|
|
13
|
+
# factory.generate # => "8f0c580b-5a0f-4fc7-9025-c39072ae073d"
|
|
14
|
+
class GuidIdentifierFactory
|
|
15
|
+
# Generates a pseudo-random GUID
|
|
16
|
+
# @return [String] The newly generated unique identifier
|
|
17
|
+
def generate
|
|
18
|
+
SecureRandom.uuid
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ActiveSupport.run_load_hooks :identifier_factory, IdentifierFactory
|
|
23
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
# Representation of a message containing a payload and metadata
|
|
3
|
+
#
|
|
4
|
+
# Instead of using this class directly, it is recommended to use a subclass specifically
|
|
5
|
+
# for commands, events or domain events.
|
|
6
|
+
#
|
|
7
|
+
# Two messages with the same identifier should be interpreted as different representations
|
|
8
|
+
# of the same conceptual message. In such case, the metadata may be different for both
|
|
9
|
+
# representations. The payload *may* be identical.
|
|
10
|
+
class Message
|
|
11
|
+
# Unique identifier of this message
|
|
12
|
+
# @return [String]
|
|
13
|
+
attr_reader :id
|
|
14
|
+
|
|
15
|
+
# Metadata attached to this message by the application
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
attr_reader :metadata
|
|
18
|
+
|
|
19
|
+
# Payload of this message; examples include commands and events. A payload is expected to
|
|
20
|
+
# be immutable to provide thread safety.
|
|
21
|
+
#
|
|
22
|
+
# @return [Object]
|
|
23
|
+
attr_reader :payload
|
|
24
|
+
|
|
25
|
+
# @param [String] id
|
|
26
|
+
# @param [Hash] metadata
|
|
27
|
+
# @param [Object] payload
|
|
28
|
+
# @return [undefined]
|
|
29
|
+
def initialize(id, metadata, payload)
|
|
30
|
+
@id = id
|
|
31
|
+
@metadata = metadata
|
|
32
|
+
@payload = payload
|
|
33
|
+
|
|
34
|
+
@metadata.freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the class of the payload of this message; use this instead of calling payload
|
|
38
|
+
# and class, in case of lazily deserializing messages.
|
|
39
|
+
#
|
|
40
|
+
# @return [Class]
|
|
41
|
+
def payload_type
|
|
42
|
+
@payload.class
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a copy of this message with the given metadata merged in
|
|
46
|
+
#
|
|
47
|
+
# @param [Hash] additional_metadata
|
|
48
|
+
# @return [Message]
|
|
49
|
+
def and_metadata(additional_metadata)
|
|
50
|
+
if additional_metadata.empty?
|
|
51
|
+
return self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
builder = self.class.builder.new
|
|
55
|
+
build_duplicate builder, @metadata.merge(additional_metadata)
|
|
56
|
+
builder.build
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns a copy of this message with the metadata replaced with the given metadata
|
|
60
|
+
#
|
|
61
|
+
# @param [Hash] replacement_metadata
|
|
62
|
+
# @return [Message]
|
|
63
|
+
def with_metadata(replacement_metadata)
|
|
64
|
+
if @metadata == replacement_metadata
|
|
65
|
+
return self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
builder = self.class.builder.new
|
|
69
|
+
build_duplicate builder, replacement_metadata
|
|
70
|
+
builder.build
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Yields a message builder that can be used to produce a message
|
|
74
|
+
#
|
|
75
|
+
# @see MessageBuilder#build
|
|
76
|
+
# @yield [MessageBuilder]
|
|
77
|
+
# @return [Message]
|
|
78
|
+
def self.build(&block)
|
|
79
|
+
builder.build(&block)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the type of builder that can be used to build this type of message
|
|
83
|
+
# @return [Class]
|
|
84
|
+
def self.builder
|
|
85
|
+
MessageBuilder
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
protected
|
|
89
|
+
|
|
90
|
+
# Populates a duplicated message with attributes from this message
|
|
91
|
+
#
|
|
92
|
+
# @param [MessageBuilder] builder
|
|
93
|
+
# @param [Hash] metadata
|
|
94
|
+
# @return [undefined]
|
|
95
|
+
def build_duplicate(builder, metadata)
|
|
96
|
+
builder.id = @id
|
|
97
|
+
builder.metadata = metadata
|
|
98
|
+
builder.payload = @payload
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|