synapse-mongo 0.1.1
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-mongo.rb +24 -0
- data/lib/synapse/common/mongo/base_template.rb +40 -0
- data/lib/synapse/event_store/mongo.rb +8 -0
- data/lib/synapse/event_store/mongo/cursor_event_stream.rb +63 -0
- data/lib/synapse/event_store/mongo/event_store.rb +88 -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 +33 -0
- data/lib/synapse/mongo/version.rb +5 -0
- data/lib/synapse/serialization/converter/bson.rb +26 -0
- data/test/common/mongo/base_template_test.rb +35 -0
- data/test/event_store/mongo/event_store_test.rb +89 -0
- data/test/serialization/converter/bson_test.rb +28 -0
- data/test/test_helper.rb +14 -0
- metadata +92 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'active_support'
|
|
2
|
+
require 'mongo'
|
|
3
|
+
require 'synapse'
|
|
4
|
+
|
|
5
|
+
require 'synapse/mongo/version'
|
|
6
|
+
|
|
7
|
+
module Synapse
|
|
8
|
+
module Common
|
|
9
|
+
# Utility classes used by Mongo components
|
|
10
|
+
module Mongo
|
|
11
|
+
extend ActiveSupport::Autoload
|
|
12
|
+
|
|
13
|
+
autoload :BaseTemplate
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module EventStore
|
|
18
|
+
autoload :Mongo
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module Serialization
|
|
22
|
+
autoload :OrderedHashToHashConverter, 'synapse/serialization/converter/bson'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Common
|
|
3
|
+
module Mongo
|
|
4
|
+
# Represents a mechanism for accessing collections required by a component
|
|
5
|
+
# @abstract
|
|
6
|
+
class BaseTemplate
|
|
7
|
+
# @return [String] Name of the database to use
|
|
8
|
+
attr_accessor :database_name
|
|
9
|
+
|
|
10
|
+
# @return [String] Username to authenticate with (optional)
|
|
11
|
+
attr_accessor :username
|
|
12
|
+
|
|
13
|
+
# @return [String] Password to authenticate with (optional)
|
|
14
|
+
attr_accessor :password
|
|
15
|
+
|
|
16
|
+
# @param [Mongo::MongoClient] client
|
|
17
|
+
# @return [undefined]
|
|
18
|
+
def initialize(client)
|
|
19
|
+
@client = client
|
|
20
|
+
@database_name = 'synapse'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
protected
|
|
24
|
+
|
|
25
|
+
# @return [Mongo::DB]
|
|
26
|
+
def database
|
|
27
|
+
unless @database
|
|
28
|
+
@database = @client.db @database_name
|
|
29
|
+
|
|
30
|
+
if @username and @password
|
|
31
|
+
@database.authenticate @username, @password
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@database
|
|
36
|
+
end
|
|
37
|
+
end # BaseTemplate
|
|
38
|
+
end # Mongo
|
|
39
|
+
end # Common
|
|
40
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
require 'synapse/event_store/mongo/cursor_event_stream'
|
|
2
|
+
require 'synapse/event_store/mongo/event_store'
|
|
3
|
+
|
|
4
|
+
require 'synapse/event_store/mongo/storage_strategy'
|
|
5
|
+
require 'synapse/event_store/mongo/per_commit_strategy'
|
|
6
|
+
require 'synapse/event_store/mongo/per_event_strategy'
|
|
7
|
+
|
|
8
|
+
require 'synapse/event_store/mongo/template'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# TODO Document me
|
|
5
|
+
class CursorDomainEventStream < Domain::DomainEventStream
|
|
6
|
+
# @param [StorageStrategy] storage_strategy
|
|
7
|
+
# @param [Mongo::Cursor] cursor
|
|
8
|
+
# @param [Array] last_snapshot_commit
|
|
9
|
+
# @param [Object] aggregate_id
|
|
10
|
+
# @return [undefined]
|
|
11
|
+
def initialize(storage_strategy, cursor, last_snapshot_commit, aggregate_id)
|
|
12
|
+
@storage_strategy = storage_strategy
|
|
13
|
+
@cursor = cursor
|
|
14
|
+
@aggregate_id = aggregate_id
|
|
15
|
+
|
|
16
|
+
if last_snapshot_commit
|
|
17
|
+
# Current batch is an enumerator
|
|
18
|
+
@current_batch = last_snapshot_commit.each
|
|
19
|
+
else
|
|
20
|
+
@current_batch = [].each
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
initialize_next_event
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
def end?
|
|
28
|
+
@next.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [DomainEventMessage]
|
|
32
|
+
def next_event
|
|
33
|
+
@next.tap do
|
|
34
|
+
initialize_next_event
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [DomainEventMessage]
|
|
39
|
+
def peek
|
|
40
|
+
@next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# @return [undefined]
|
|
46
|
+
def initialize_next_event
|
|
47
|
+
begin
|
|
48
|
+
@next = @current_batch.next
|
|
49
|
+
rescue StopIteration
|
|
50
|
+
if @cursor.has_next?
|
|
51
|
+
document = @cursor.next
|
|
52
|
+
@current_batch = @storage_strategy.extract_events(document, @aggregate_id).each
|
|
53
|
+
|
|
54
|
+
retry
|
|
55
|
+
else
|
|
56
|
+
@next = nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end # CursorDomainEventStream
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Implementation of an event store backed by a Mongo database
|
|
5
|
+
class MongoEventStore < SnapshotEventStore
|
|
6
|
+
# @param [MongoTemplate] template
|
|
7
|
+
# @param [StorageStrategy] storage_strategy
|
|
8
|
+
# @return [undefined]
|
|
9
|
+
def initialize(template, storage_strategy)
|
|
10
|
+
@storage_strategy = storage_strategy
|
|
11
|
+
@template = template
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [undefined]
|
|
15
|
+
def ensure_indexes
|
|
16
|
+
@storage_strategy.ensure_indexes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @raise [EventStoreError] If an error occurs while reading the stream from the store
|
|
20
|
+
# @param [String] type_identifier Type descriptor of the aggregate to retrieve
|
|
21
|
+
# @param [Object] aggregate_id
|
|
22
|
+
# @return [DomainEventStream]
|
|
23
|
+
def read_events(type_identifier, aggregate_id)
|
|
24
|
+
first_sequence_number = -1
|
|
25
|
+
|
|
26
|
+
last_snapshot_commit = load_last_snapshot type_identifier, aggregate_id
|
|
27
|
+
if last_snapshot_commit and last_snapshot_commit.size > 0
|
|
28
|
+
first_sequence_number = last_snapshot_commit[0].sequence_number
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
first_sequence_number = first_sequence_number.next
|
|
32
|
+
|
|
33
|
+
cursor = @storage_strategy.fetch_events type_identifier, aggregate_id, first_sequence_number
|
|
34
|
+
|
|
35
|
+
unless last_snapshot_commit or cursor.has_next?
|
|
36
|
+
raise StreamNotFoundError.new type_identifier, aggregate_id
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
CursorDomainEventStream.new @storage_strategy, cursor, last_snapshot_commit, aggregate_id
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @raise [EventStoreError] If an error occurs while appending the stream to the store
|
|
43
|
+
# @param [String] type_identifier Type descriptor of the aggregate to append to
|
|
44
|
+
# @param [DomainEventStream] stream
|
|
45
|
+
# @return [undefined]
|
|
46
|
+
def append_events(type_identifier, stream)
|
|
47
|
+
events = stream.to_a
|
|
48
|
+
documents = @storage_strategy.create_documents type_identifier, events
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
@template.event_collection.insert documents
|
|
52
|
+
rescue ::Mongo::OperationFailure => exception
|
|
53
|
+
if exception.error_code == 11000
|
|
54
|
+
raise Repository::ConcurrencyError,
|
|
55
|
+
'Event for this aggregate and sequence number already present'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
raise ex
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @raise [EventStoreError] If an error occurs while appending the event to the store
|
|
63
|
+
# @param [String] type_identifier Type descriptor of the aggregate to append to
|
|
64
|
+
# @param [DomainEventMessage] snapshot_event
|
|
65
|
+
# @return [undefined]
|
|
66
|
+
def append_snapshot_event(type_identifier, snapshot_event)
|
|
67
|
+
documents = @storage_strategy.create_documents type_identifier, [snapshot_event]
|
|
68
|
+
@template.snapshot_collection.insert documents
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @param [String] type_identifier Type descriptor of the aggregate to retrieve
|
|
74
|
+
# @param [Object] aggregate_id
|
|
75
|
+
def load_last_snapshot(type_identifier, aggregate_id)
|
|
76
|
+
cursor = @storage_strategy.fetch_last_snapshot type_identifier, aggregate_id
|
|
77
|
+
|
|
78
|
+
unless cursor.has_next?
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
first = cursor.next_document
|
|
83
|
+
@storage_strategy.extract_events first, aggregate_id
|
|
84
|
+
end
|
|
85
|
+
end # MongoEventStore
|
|
86
|
+
end # Mongo
|
|
87
|
+
end # EventStore
|
|
88
|
+
end # Synapse
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Storage strategy that stores all events in a commit operation in a single document
|
|
5
|
+
#
|
|
6
|
+
# Since Mongo doesn't support transactions, this can be used as a substitute to guarantee
|
|
7
|
+
# atomic storage of events. The only downside is that it may be harder to query events
|
|
8
|
+
# from the event store.
|
|
9
|
+
#
|
|
10
|
+
# Performance also seems to be better using this strategy
|
|
11
|
+
class DocumentPerCommitStrategy < StorageStrategy
|
|
12
|
+
# @param [String] type_identifier Type identifier for the aggregate
|
|
13
|
+
# @param [Array] events Domain events to be committed
|
|
14
|
+
# @return [Array]
|
|
15
|
+
def create_documents(type_identifier, events)
|
|
16
|
+
document = CommitDocument.new
|
|
17
|
+
document.from_events(type_identifier, events, @serializer).to_hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param [Hash] hash
|
|
21
|
+
# @param [Object] aggregate_id
|
|
22
|
+
# @return [Array]
|
|
23
|
+
def extract_events(hash, aggregate_id)
|
|
24
|
+
document = CommitDocument.new
|
|
25
|
+
document.from_hash(hash).to_events(aggregate_id, @serializer, @upcaster_chain)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Mongo document that represents a commit containing one or more events
|
|
29
|
+
class CommitDocument
|
|
30
|
+
# @return [Object]
|
|
31
|
+
attr_reader :aggregate_id
|
|
32
|
+
|
|
33
|
+
# @param [String] type_identifier
|
|
34
|
+
# @param [Array] events
|
|
35
|
+
# @param [Serializer] serializer
|
|
36
|
+
# @return [CommitDocument]
|
|
37
|
+
def from_events(type_identifier, events, serializer)
|
|
38
|
+
first_event = events.first
|
|
39
|
+
last_event = events.last
|
|
40
|
+
|
|
41
|
+
@aggregate_type = type_identifier
|
|
42
|
+
@aggregate_id = first_event.aggregate_id.to_s
|
|
43
|
+
@first_sequence_number = first_event.sequence_number
|
|
44
|
+
@last_sequence_number = last_event.sequence_number
|
|
45
|
+
@first_timestamp = first_event.timestamp
|
|
46
|
+
@last_timestamp = last_event.timestamp
|
|
47
|
+
|
|
48
|
+
@events = Array.new
|
|
49
|
+
events.each do |event|
|
|
50
|
+
event_document = EventDocument.new
|
|
51
|
+
event_document.from_event event, serializer
|
|
52
|
+
|
|
53
|
+
@events.push event_document
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param [Hash] hash
|
|
60
|
+
# @return [CommitDocument]
|
|
61
|
+
def from_hash(hash)
|
|
62
|
+
hash.symbolize_keys!
|
|
63
|
+
|
|
64
|
+
@aggregate_id = hash.fetch :aggregate_id
|
|
65
|
+
@aggregate_type = hash.fetch :aggregate_type
|
|
66
|
+
@first_sequence_number = hash.fetch :first_sequence_number
|
|
67
|
+
@last_sequence_number = hash.fetch :last_sequence_number
|
|
68
|
+
@first_timestamp = hash.fetch :first_timestamp
|
|
69
|
+
@last_timestamp = hash.fetch :last_timestamp
|
|
70
|
+
|
|
71
|
+
@events = Array.new
|
|
72
|
+
|
|
73
|
+
event_hashes = hash.fetch :events
|
|
74
|
+
event_hashes.each do |event_hash|
|
|
75
|
+
event_document = EventDocument.new
|
|
76
|
+
event_document.from_hash event_hash
|
|
77
|
+
|
|
78
|
+
@events.push event_document
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [Hash]
|
|
85
|
+
def to_hash
|
|
86
|
+
events = Array.new
|
|
87
|
+
@events.each do |event|
|
|
88
|
+
events.push event.to_hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
{ aggregate_id: @aggregate_id,
|
|
92
|
+
aggregate_type: @aggregate_type,
|
|
93
|
+
# Allows us to use the same query to filter events as DocumentPerEvent
|
|
94
|
+
sequence_number: @first_sequence_number,
|
|
95
|
+
first_sequence_number: @first_sequence_number,
|
|
96
|
+
last_sequence_number: @last_sequence_number,
|
|
97
|
+
# Allows us to use the same query to filter events as DocumentPerEvent
|
|
98
|
+
timestamp: @first_timestamp,
|
|
99
|
+
first_timestamp: @first_timestamp,
|
|
100
|
+
last_timestamp: @last_timestamp,
|
|
101
|
+
events: events }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @param [Object] aggregate_id The actual aggregate identifier used to query the evnet store
|
|
105
|
+
# @param [Serializer] serializer
|
|
106
|
+
# @param [UpcasterChain] upcaster_chain
|
|
107
|
+
# @return [Array]
|
|
108
|
+
def to_events(aggregate_id, serializer, upcaster_chain)
|
|
109
|
+
events = Array.new
|
|
110
|
+
|
|
111
|
+
@events.each do |event_document|
|
|
112
|
+
event_data = DocumentDomainEventData.new aggregate_id, event_document
|
|
113
|
+
context = Upcasting::SerializedDomainEventUpcastingContext.new event_data, aggregate_id, serializer
|
|
114
|
+
|
|
115
|
+
upcast_objects = upcaster_chain.upcast event_document.payload, context
|
|
116
|
+
upcast_objects.each do |upcast_object|
|
|
117
|
+
upcast_data = Upcasting::UpcastSerializedDomainEventData.new event_data, aggregate_id, upcast_object
|
|
118
|
+
|
|
119
|
+
builder = Serialization::SerializedDomainEventMessageBuilder.new
|
|
120
|
+
|
|
121
|
+
# Prevent duplicate serialization of metadata if it was accessed during upcasting
|
|
122
|
+
metadata = context.serialized_metadata
|
|
123
|
+
if metadata.deserialized?
|
|
124
|
+
builder.metadata = Serialization::DeserializedObject.new metadata.deserialized
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
builder.from_data upcast_data, serializer
|
|
128
|
+
|
|
129
|
+
events.push builder.build
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
events
|
|
134
|
+
end
|
|
135
|
+
end # CommitDocument
|
|
136
|
+
|
|
137
|
+
# Mongo document that represents a single event as part of a commit document
|
|
138
|
+
class EventDocument
|
|
139
|
+
# @return [String]
|
|
140
|
+
attr_reader :id
|
|
141
|
+
|
|
142
|
+
# @return [Time]
|
|
143
|
+
attr_reader :timestamp
|
|
144
|
+
|
|
145
|
+
# @return [Integer]
|
|
146
|
+
attr_reader :sequence_number
|
|
147
|
+
|
|
148
|
+
# @return [SerializedObject]
|
|
149
|
+
def metadata
|
|
150
|
+
Serialization::SerializedMetadata.new @metadata, @metadata.class
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @return [SerializedObject]
|
|
154
|
+
def payload
|
|
155
|
+
Serialization::SerializedObject.new @payload, @payload.class,
|
|
156
|
+
Serialization::SerializedType.new(@payload_type, @payload_revision)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @param [EventMessage] event
|
|
160
|
+
# @param [Serializer] serializer
|
|
161
|
+
# @return [EventDocument]
|
|
162
|
+
def from_event(event, serializer)
|
|
163
|
+
serialization_target = String
|
|
164
|
+
if serializer.can_serialize_to? Hash
|
|
165
|
+
serialization_target = Hash
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
serialized_metadata = serializer.serialize_metadata event, serialization_target
|
|
169
|
+
serialized_payload = serializer.serialize_payload event, serialization_target
|
|
170
|
+
|
|
171
|
+
@id = event.id
|
|
172
|
+
@metadata = serialized_metadata.content
|
|
173
|
+
@payload = serialized_payload.content
|
|
174
|
+
@payload_type = serialized_payload.type.name
|
|
175
|
+
@payload_revision = serialized_payload.type.revision
|
|
176
|
+
@timestamp = event.timestamp
|
|
177
|
+
@sequence_number = event.sequence_number
|
|
178
|
+
|
|
179
|
+
self
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @param [Hash] hash
|
|
183
|
+
# @return [EventDocument]
|
|
184
|
+
def from_hash(hash)
|
|
185
|
+
hash.symbolize_keys!
|
|
186
|
+
|
|
187
|
+
@id = hash.fetch :id
|
|
188
|
+
@metadata = hash.fetch :metadata
|
|
189
|
+
@payload = hash.fetch :payload
|
|
190
|
+
@payload_type = hash.fetch :payload_type
|
|
191
|
+
@payload_revision = hash.fetch :payload_revision
|
|
192
|
+
@timestamp = hash.fetch :timestamp
|
|
193
|
+
@sequence_number = hash.fetch :sequence_number
|
|
194
|
+
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# @return [Hash]
|
|
199
|
+
def to_hash
|
|
200
|
+
{ id: @id,
|
|
201
|
+
metadata: @metadata,
|
|
202
|
+
payload: @payload,
|
|
203
|
+
payload_type: @payload_type,
|
|
204
|
+
payload_revision: @payload_revision,
|
|
205
|
+
timestamp: @timestamp,
|
|
206
|
+
sequence_number: @sequence_number }
|
|
207
|
+
end
|
|
208
|
+
end # EventDocument
|
|
209
|
+
|
|
210
|
+
# Serialized domain event data from an event document
|
|
211
|
+
class DocumentDomainEventData < Serialization::SerializedDomainEventData
|
|
212
|
+
# @param [Object] aggregate_id
|
|
213
|
+
# @param [EventDocument] event_document
|
|
214
|
+
# @return [undefined]
|
|
215
|
+
def initialize(aggregate_id, event_document)
|
|
216
|
+
@aggregate_id = aggregate_id
|
|
217
|
+
@event_document = event_document
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# @return [String]
|
|
221
|
+
def id
|
|
222
|
+
@event_document.id
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# @return [SerializedObject]
|
|
226
|
+
def metadata
|
|
227
|
+
@event_document.metadata
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# @return [SerializedObject]
|
|
231
|
+
def payload
|
|
232
|
+
@event_document.payload
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# @return [Time]
|
|
236
|
+
def timestamp
|
|
237
|
+
@event_document.timestamp
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# @return [Object]
|
|
241
|
+
def aggregate_id
|
|
242
|
+
@aggregate_id
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @return [Integer]
|
|
246
|
+
def sequence_number
|
|
247
|
+
@event_document.sequence_number
|
|
248
|
+
end
|
|
249
|
+
end # DocumentDomainEventData
|
|
250
|
+
end # DocumentPerCommitStrategy
|
|
251
|
+
end # Mongo
|
|
252
|
+
end # EventStore
|
|
253
|
+
end # Synapse
|
|
@@ -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,33 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module EventStore
|
|
3
|
+
module Mongo
|
|
4
|
+
# Template for accessing collections needed by the event store
|
|
5
|
+
class Template < Common::Mongo::BaseTemplate
|
|
6
|
+
# @return [String] Name of the collection containing domain events
|
|
7
|
+
attr_accessor :event_collection_name
|
|
8
|
+
|
|
9
|
+
# @return [String] Name of the collection containing snapshot events
|
|
10
|
+
attr_accessor :snapshot_collection_name
|
|
11
|
+
|
|
12
|
+
# @param [Mongo::MongoClient] client
|
|
13
|
+
# @return [undefined]
|
|
14
|
+
def initialize(client)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
@event_collection_name = 'domain_events'
|
|
18
|
+
@snapshot_collection_name = 'snapshot_events'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Mongo::Collection]
|
|
22
|
+
def event_collection
|
|
23
|
+
database.collection @event_collection_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Mongo::Collection]
|
|
27
|
+
def snapshot_collection
|
|
28
|
+
database.collection @snapshot_collection_name
|
|
29
|
+
end
|
|
30
|
+
end # MongoTemplate
|
|
31
|
+
end # Mongo
|
|
32
|
+
end # EventStore
|
|
33
|
+
end # Synapse
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Synapse
|
|
2
|
+
module Serialization
|
|
3
|
+
# Converter that converts an ordered hash from BSON into a regular Ruby hash
|
|
4
|
+
class OrderedHashToHashConverter
|
|
5
|
+
include Converter
|
|
6
|
+
|
|
7
|
+
converts BSON::OrderedHash, Hash
|
|
8
|
+
|
|
9
|
+
# @param [Object] original
|
|
10
|
+
# @return [Object]
|
|
11
|
+
def convert_content(original)
|
|
12
|
+
converted = Hash.new
|
|
13
|
+
|
|
14
|
+
original.each do |key, value|
|
|
15
|
+
if value.is_a? BSON::OrderedHash
|
|
16
|
+
value = convert_content value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
converted[key] = value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
converted
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Synapse
|
|
4
|
+
module Common
|
|
5
|
+
module Mongo
|
|
6
|
+
|
|
7
|
+
class BaseTemplateTest < Test::Unit::TestCase
|
|
8
|
+
def test_database
|
|
9
|
+
client = Object.new
|
|
10
|
+
database = Object.new
|
|
11
|
+
|
|
12
|
+
database_name = 'test_database'
|
|
13
|
+
username = 'test_username'
|
|
14
|
+
password = 'test_password'
|
|
15
|
+
|
|
16
|
+
mock(client).db(database_name) do
|
|
17
|
+
database
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
mock(database).authenticate(username, password)
|
|
21
|
+
|
|
22
|
+
template = BaseTemplate.new client
|
|
23
|
+
template.database_name = database_name
|
|
24
|
+
template.username = username
|
|
25
|
+
template.password = password
|
|
26
|
+
|
|
27
|
+
template.send :database
|
|
28
|
+
# Additional calls to database should return the same database
|
|
29
|
+
template.send :database
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Synapse
|
|
4
|
+
module EventStore
|
|
5
|
+
module Mongo
|
|
6
|
+
|
|
7
|
+
class MongoEventStoreTest < Test::Unit::TestCase
|
|
8
|
+
def test_integration
|
|
9
|
+
client = ::Mongo::MongoClient.new
|
|
10
|
+
template = Template.new client
|
|
11
|
+
|
|
12
|
+
serializer = Serialization::MarshalSerializer.new
|
|
13
|
+
upcaster_chain = Upcasting::UpcasterChain.new serializer.converter_factory
|
|
14
|
+
|
|
15
|
+
[DocumentPerCommitStrategy, DocumentPerEventStrategy].each do |type|
|
|
16
|
+
strategy = type.new template, serializer, upcaster_chain
|
|
17
|
+
test_integration_with template, strategy
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def test_integration_with(template, strategy)
|
|
25
|
+
store = MongoEventStore.new template, strategy
|
|
26
|
+
store.ensure_indexes
|
|
27
|
+
|
|
28
|
+
type_identifier = 'TestAggregate'
|
|
29
|
+
|
|
30
|
+
metadata = {
|
|
31
|
+
foo: 0,
|
|
32
|
+
bar: 1
|
|
33
|
+
}
|
|
34
|
+
payload = TestEvent.new 1, 2
|
|
35
|
+
aggregate_id = SecureRandom.uuid
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
x = 0
|
|
39
|
+
|
|
40
|
+
# Create two sets of 50 events each
|
|
41
|
+
# After this, we'll add an artificial snapshot at seq num 49
|
|
42
|
+
2.times do
|
|
43
|
+
events = Array.new
|
|
44
|
+
|
|
45
|
+
50.times do
|
|
46
|
+
events.push create_event metadata, payload, aggregate_id, x
|
|
47
|
+
x = x.next
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
append_stream = Domain::SimpleDomainEventStream.new events
|
|
51
|
+
store.append_events type_identifier, append_stream
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
read_stream = store.read_events type_identifier, aggregate_id
|
|
55
|
+
read_array = read_stream.to_a
|
|
56
|
+
|
|
57
|
+
assert_equal 100, read_array.count
|
|
58
|
+
|
|
59
|
+
snapshot = create_event metadata, payload, aggregate_id, 49
|
|
60
|
+
store.append_snapshot_event type_identifier, snapshot
|
|
61
|
+
|
|
62
|
+
read_stream = store.read_events type_identifier, aggregate_id
|
|
63
|
+
read_array = read_stream.to_a
|
|
64
|
+
|
|
65
|
+
assert_equal 51, read_array.count
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def create_event(metadata, payload, aggregate_id, sequence_number)
|
|
69
|
+
Domain::DomainEventMessage.build do |builder|
|
|
70
|
+
builder.metadata = metadata
|
|
71
|
+
builder.payload = payload
|
|
72
|
+
builder.aggregate_id = aggregate_id
|
|
73
|
+
builder.sequence_number = sequence_number
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class TestEvent
|
|
79
|
+
attr_accessor :foo
|
|
80
|
+
attr_accessor :bar
|
|
81
|
+
|
|
82
|
+
def initialize(foo, bar)
|
|
83
|
+
@foo, @bar = foo, bar
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Synapse
|
|
4
|
+
module Serialization
|
|
5
|
+
class OrderedHashToHashConverterTest < Test::Unit::TestCase
|
|
6
|
+
def test_convert
|
|
7
|
+
converter = OrderedHashToHashConverter.new
|
|
8
|
+
|
|
9
|
+
assert_equal BSON::OrderedHash, converter.source_type
|
|
10
|
+
assert_equal Hash, converter.target_type
|
|
11
|
+
|
|
12
|
+
source = BSON::OrderedHash.new
|
|
13
|
+
source[:foo] = 0
|
|
14
|
+
source[:bar] = BSON::OrderedHash.new
|
|
15
|
+
source[:bar][:baz] = 1
|
|
16
|
+
|
|
17
|
+
target = {
|
|
18
|
+
:foo => 0,
|
|
19
|
+
:bar => {
|
|
20
|
+
:baz => 1
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
assert_equal target, converter.convert_content(source)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: synapse-mongo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Ian Unruh
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-05-12 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: synapse-core
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.2.0
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ! '>='
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 0.2.0
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: mongo
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ! '>='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0'
|
|
46
|
+
description: MongoDB implementations for the Synapse CQRS framework
|
|
47
|
+
email: ianunruh@gmail.com
|
|
48
|
+
executables: []
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- lib/synapse-mongo.rb
|
|
53
|
+
- lib/synapse/mongo/version.rb
|
|
54
|
+
- lib/synapse/event_store/mongo/cursor_event_stream.rb
|
|
55
|
+
- lib/synapse/event_store/mongo/event_store.rb
|
|
56
|
+
- lib/synapse/event_store/mongo/storage_strategy.rb
|
|
57
|
+
- lib/synapse/event_store/mongo/template.rb
|
|
58
|
+
- lib/synapse/event_store/mongo/per_commit_strategy.rb
|
|
59
|
+
- lib/synapse/event_store/mongo/per_event_strategy.rb
|
|
60
|
+
- lib/synapse/event_store/mongo.rb
|
|
61
|
+
- lib/synapse/common/mongo/base_template.rb
|
|
62
|
+
- lib/synapse/serialization/converter/bson.rb
|
|
63
|
+
- test/test_helper.rb
|
|
64
|
+
- test/event_store/mongo/event_store_test.rb
|
|
65
|
+
- test/common/mongo/base_template_test.rb
|
|
66
|
+
- test/serialization/converter/bson_test.rb
|
|
67
|
+
homepage: https://github.com/iunruh/synapse-mongo
|
|
68
|
+
licenses: []
|
|
69
|
+
post_install_message:
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
none: false
|
|
75
|
+
requirements:
|
|
76
|
+
- - ! '>='
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
none: false
|
|
81
|
+
requirements:
|
|
82
|
+
- - ! '>='
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubyforge_project:
|
|
87
|
+
rubygems_version: 1.8.25
|
|
88
|
+
signing_key:
|
|
89
|
+
specification_version: 3
|
|
90
|
+
summary: MongoDB implementations for the Synapse CQRS framework
|
|
91
|
+
test_files: []
|
|
92
|
+
has_rdoc:
|