euston-eventstore 1.0.2-java
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/Rakefile +126 -0
- data/euston-eventstore.gemspec +68 -0
- data/lib/euston-eventstore/commit.rb +77 -0
- data/lib/euston-eventstore/constants.rb +5 -0
- data/lib/euston-eventstore/dispatcher/asynchronous_dispatcher.rb +37 -0
- data/lib/euston-eventstore/dispatcher/null_dispatcher.rb +11 -0
- data/lib/euston-eventstore/dispatcher/synchronous_dispatcher.rb +21 -0
- data/lib/euston-eventstore/errors.rb +21 -0
- data/lib/euston-eventstore/event_message.rb +26 -0
- data/lib/euston-eventstore/optimistic_event_store.rb +68 -0
- data/lib/euston-eventstore/optimistic_event_stream.rb +106 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_commit.rb +82 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_commit_id.rb +16 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_config.rb +28 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_event_message.rb +31 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_engine.rb +167 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_persistence_factory.rb +31 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_snapshot.rb +32 -0
- data/lib/euston-eventstore/persistence/mongodb/mongo_stream_head.rb +29 -0
- data/lib/euston-eventstore/persistence/stream_head.rb +23 -0
- data/lib/euston-eventstore/snapshot.rb +21 -0
- data/lib/euston-eventstore/version.rb +5 -0
- data/lib/euston-eventstore.rb +7 -0
- data/spec/event_store/dispatcher/asynchronous_dispatcher_spec.rb +75 -0
- data/spec/event_store/dispatcher/synchronous_dispatcher_spec.rb +39 -0
- data/spec/event_store/optimistic_event_store_spec.rb +292 -0
- data/spec/event_store/optimistic_event_stream_spec.rb +318 -0
- data/spec/event_store/persistence/mongodb_spec.rb +301 -0
- data/spec/event_store/serialization/simple_message.rb +12 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/array_enumeration_counter.rb +20 -0
- metadata +178 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Euston
|
4
|
+
module EventStore
|
5
|
+
module Persistence
|
6
|
+
module Mongodb
|
7
|
+
class Config
|
8
|
+
include ::Singleton
|
9
|
+
|
10
|
+
def host
|
11
|
+
@host ||= 'localhost'
|
12
|
+
end
|
13
|
+
|
14
|
+
def port
|
15
|
+
@port ||= 27017
|
16
|
+
end
|
17
|
+
|
18
|
+
def options
|
19
|
+
@options ||= { :safe => { :fsync => true }}
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_writer :host, :port, :options
|
23
|
+
attr_accessor :database, :logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
module Persistence
|
4
|
+
module Mongodb
|
5
|
+
module MongoEventMessage
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_hash(hash)
|
10
|
+
{}.recursive_symbolize_keys!
|
11
|
+
message = EventMessage.new hash['body'].recursive_symbolize_keys!
|
12
|
+
message.instance_variable_set :@headers, hash['headers'].recursive_symbolize_keys!
|
13
|
+
message
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
{
|
19
|
+
:headers => headers,
|
20
|
+
:body => body.to_hash.recursive_stringify_symbol_values!
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class EventMessage
|
28
|
+
include Persistence::Mongodb::MongoEventMessage
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
module Persistence
|
4
|
+
module Mongodb
|
5
|
+
class MongoPersistenceEngine
|
6
|
+
def initialize(store)
|
7
|
+
@store = store
|
8
|
+
|
9
|
+
collection_names = store.collection_names
|
10
|
+
store.create_collection 'commits' unless collection_names.include? 'commits' # :safe = true
|
11
|
+
store.create_collection 'snapshot' unless collection_names.include? 'snapshot' # :safe = false
|
12
|
+
store.create_collection 'streams' unless collection_names.include? 'streams' # :safe = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_snapshot(snapshot)
|
16
|
+
return false if snapshot.nil?
|
17
|
+
|
18
|
+
begin
|
19
|
+
mongo_snapshot = snapshot.is_a?(Hash) ? snapshot : snapshot.to_hash
|
20
|
+
id = { '_id' => mongo_snapshot[:_id] }
|
21
|
+
|
22
|
+
persisted_snapshots.update(id, { 'payload' => mongo_snapshot[:payload] }.merge(id), { :upsert => true })
|
23
|
+
|
24
|
+
# jmongo's find_one is broken
|
25
|
+
if defined?(JMongo)
|
26
|
+
stream_head = MongoStreamHead.from_hash persisted_stream_heads.find({ '_id' => snapshot.stream_id }).to_a.first
|
27
|
+
else
|
28
|
+
stream_head = MongoStreamHead.from_hash persisted_stream_heads.find_one({ '_id' => snapshot.stream_id })
|
29
|
+
end
|
30
|
+
|
31
|
+
unsnapshotted = stream_head.head_revision - snapshot.stream_revision
|
32
|
+
persisted_stream_heads.update({ '_id' => snapshot.stream_id },
|
33
|
+
{ '$set' => { 'snapshot_revision' => snapshot.stream_revision, 'unsnapshotted' => unsnapshotted } })
|
34
|
+
return true
|
35
|
+
rescue Mongo::OperationFailure
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def commit(attempt)
|
41
|
+
try_mongo do
|
42
|
+
commit = attempt.to_mongo_commit
|
43
|
+
|
44
|
+
begin
|
45
|
+
# for concurrency / duplicate commit detection safe mode is required
|
46
|
+
persisted_commits.insert commit, :safe => true
|
47
|
+
update_stream_head_async attempt.stream_id, attempt.stream_revision, attempt.events.length
|
48
|
+
rescue Mongo::OperationFailure, NativeException => e
|
49
|
+
raise(Euston::EventStore::StorageError, e.message, e.backtrace) unless e.message.include? CONCURRENCY_EXCEPTION
|
50
|
+
|
51
|
+
# jmongo's find_one is broken
|
52
|
+
if defined?(JMongo)
|
53
|
+
committed = persisted_commits.find(attempt.to_id_query).to_a.first
|
54
|
+
else
|
55
|
+
committed = persisted_commits.find_one(attempt.to_id_query)
|
56
|
+
end
|
57
|
+
|
58
|
+
raise Euston::EventStore::DuplicateCommitError if !committed.nil? && committed['commit_id'] == attempt.commit_id
|
59
|
+
raise Euston::EventStore::ConcurrencyError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_from(options)
|
65
|
+
try_mongo do
|
66
|
+
if options.has_key? :timestamp
|
67
|
+
query = { 'commit_timestamp' => { '$gte' => options[:timestamp].to_f } }
|
68
|
+
order = [ 'commit_timestamp', Mongo::ASCENDING ]
|
69
|
+
else
|
70
|
+
query = { '_id.stream_id' => options[:stream_id],
|
71
|
+
'events.stream_revision' => { '$gte' => options[:min_revision], '$lte' => options[:max_revision] } }
|
72
|
+
|
73
|
+
order = [ 'events.stream_revision', Mongo::ASCENDING ]
|
74
|
+
end
|
75
|
+
|
76
|
+
persisted_commits.find(query).sort(order).to_a.map { |hash| MongoCommit.from_hash hash }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_snapshot(stream_id, max_revision)
|
81
|
+
try_mongo do
|
82
|
+
query = { '_id' => { '$gt' => { 'stream_id' => stream_id, 'stream_revision' => nil },
|
83
|
+
'$lte' => { 'stream_id' => stream_id, 'stream_revision' => max_revision } } }
|
84
|
+
order = [ '_id', Mongo::DESCENDING ]
|
85
|
+
|
86
|
+
persisted_snapshots.find(query).sort(order).limit(1).to_a.map { |hash| MongoSnapshot::from_hash hash }.first
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_streams_to_snapshot(max_threshold)
|
91
|
+
try_mongo do
|
92
|
+
query = { 'unsnapshotted' => { '$gte' => max_threshold } }
|
93
|
+
order = [ 'unsnapshotted', Mongo::DESCENDING ]
|
94
|
+
|
95
|
+
persisted_stream_heads.find(query).sort(order).to_a.map { |hash| MongoStreamHead.from_hash hash }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_undispatched_commits
|
100
|
+
try_mongo do
|
101
|
+
query = { 'dispatched' => false }
|
102
|
+
order = [ 'commit_timestamp', Mongo::ASCENDING ]
|
103
|
+
|
104
|
+
persisted_commits.find(query).sort(order).to_a.map { |hash| MongoCommit.from_hash hash }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def init
|
109
|
+
try_mongo do
|
110
|
+
persisted_commits.ensure_index [ ['dispatched', Mongo::ASCENDING],
|
111
|
+
['commit_timestamp', Mongo::ASCENDING] ], :unique => false, :name => 'dispatched_index'
|
112
|
+
|
113
|
+
persisted_commits.ensure_index [ ['_id.stream_id', Mongo::ASCENDING],
|
114
|
+
['events.stream_revision', Mongo::ASCENDING] ], :unique => true, :name => 'get_from_index'
|
115
|
+
|
116
|
+
persisted_commits.ensure_index [ ['commit_timestamp', Mongo::ASCENDING] ], :unique => false, :name => 'commit_timestamp_index'
|
117
|
+
|
118
|
+
persisted_stream_heads.ensure_index [ ['unsnapshotted', Mongo::ASCENDING] ], :unique => false, :name => 'unsnapshotted_index'
|
119
|
+
end
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def mark_commit_as_dispatched(commit)
|
124
|
+
try_mongo do
|
125
|
+
persisted_commits.update commit.to_id_query, { '$set' => { 'dispatched' => true }}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def persisted_commits
|
132
|
+
@store.collection 'commits'
|
133
|
+
end
|
134
|
+
|
135
|
+
def persisted_snapshots
|
136
|
+
@store.collection 'snapshots'
|
137
|
+
end
|
138
|
+
|
139
|
+
def persisted_stream_heads
|
140
|
+
@store.collection 'streams'
|
141
|
+
end
|
142
|
+
|
143
|
+
def try_mongo(&block)
|
144
|
+
begin
|
145
|
+
yield block
|
146
|
+
rescue Mongo::ConnectionError => e
|
147
|
+
raise Euston::EventStore::StorageUnavailableError, e.to_s, e.backtrace
|
148
|
+
rescue Mongo::MongoDBError => e
|
149
|
+
raise Euston::EventStore::StorageError, e.to_s, e.backtrace
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def update_stream_head_async(stream_id, stream_revision, events_count)
|
154
|
+
Thread.fork do
|
155
|
+
persisted_stream_heads.update(
|
156
|
+
{ '_id' => stream_id },
|
157
|
+
{ '$set' => { 'head_revision' => stream_revision }, '$inc' => { 'snapshot_revision' => 0, 'unsnapshotted' => events_count } },
|
158
|
+
{ :upsert => true })
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
CONCURRENCY_EXCEPTION = "E1100"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
if RUBY_PLATFORM.to_s == 'java'
|
2
|
+
module JMongo
|
3
|
+
module BasicDBObjectExtentions
|
4
|
+
include HashKeys
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'jmongo'
|
9
|
+
else
|
10
|
+
require 'mongo'
|
11
|
+
end
|
12
|
+
|
13
|
+
module Euston
|
14
|
+
module EventStore
|
15
|
+
module Persistence
|
16
|
+
module Mongodb
|
17
|
+
class MongoPersistenceFactory
|
18
|
+
def self.build
|
19
|
+
config = Config.instance
|
20
|
+
connection = ::Mongo::Connection.new(config.host, config.port, config.options)
|
21
|
+
|
22
|
+
MongoPersistenceEngine.new connection.db(config.database)
|
23
|
+
end
|
24
|
+
def self.build_with_proxy()
|
25
|
+
ZmqPersistenceEngineProxy.new(build.init)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
module Persistence
|
4
|
+
module Mongodb
|
5
|
+
module MongoSnapshot
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_hash(hash)
|
10
|
+
return nil if hash.nil?
|
11
|
+
|
12
|
+
id = hash['_id']
|
13
|
+
|
14
|
+
Euston::EventStore::Snapshot.new id['stream_id'], id['stream_revision'], hash['payload']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
{
|
20
|
+
:_id => { :stream_id => stream_id, :stream_revision => stream_revision },
|
21
|
+
:payload => payload.recursive_stringify_symbol_values!
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Snapshot
|
29
|
+
include Persistence::Mongodb::MongoSnapshot
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
module Persistence
|
4
|
+
module Mongodb
|
5
|
+
module MongoStreamHead
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_hash(hash)
|
10
|
+
Euston::EventStore::Persistence::StreamHead.new hash['_id'], hash['head_revision'], hash['snapshot_revision']
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
{
|
16
|
+
:stream_id => @stream_id,
|
17
|
+
:head_revision => @head_revision,
|
18
|
+
:snapshot_revision => @snapshot_revision
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class StreamHead
|
25
|
+
include Mongodb::MongoStreamHead
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
module Persistence
|
4
|
+
# Indicates the most recent information representing the head of a given stream.
|
5
|
+
class StreamHead
|
6
|
+
def initialize(stream_id, head_revision, snapshot_revision)
|
7
|
+
@stream_id = stream_id
|
8
|
+
@head_revision = head_revision
|
9
|
+
@snapshot_revision = snapshot_revision
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold.
|
13
|
+
attr_reader :stream_id
|
14
|
+
|
15
|
+
# Gets the value which indicates the revision, length, or number of events committed to the stream.
|
16
|
+
attr_reader :head_revision
|
17
|
+
|
18
|
+
# Gets the value which indicates the revision at which the last snapshot was taken.
|
19
|
+
attr_reader :snapshot_revision
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Euston
|
2
|
+
module EventStore
|
3
|
+
# Represents a materialized view of a stream at specific revision.
|
4
|
+
class Snapshot
|
5
|
+
def initialize(stream_id, stream_revision, payload)
|
6
|
+
@stream_id = stream_id
|
7
|
+
@stream_revision = stream_revision
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
# Gets the value which uniquely identifies the stream to which the snapshot applies.
|
12
|
+
attr_reader :stream_id
|
13
|
+
|
14
|
+
# Gets the position at which the snapshot applies.
|
15
|
+
attr_reader :stream_revision
|
16
|
+
|
17
|
+
# Gets the snapshot or materialized view of the stream at the revision indicated.
|
18
|
+
attr_reader :payload
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Euston::EventStore do
|
4
|
+
let(:uuid) { Uuid }
|
5
|
+
|
6
|
+
describe 'asynchronous dispatcher' do
|
7
|
+
context 'when instantiating the asynchronous dispatcher' do
|
8
|
+
let(:stream_id) { uuid.generate }
|
9
|
+
let(:commits) { [ new_commit(:stream_id => stream_id), new_commit(:stream_id => stream_id) ] }
|
10
|
+
let(:bus) { stub('bus').as_null_object }
|
11
|
+
let(:persistence) { stub('persistence').as_null_object }
|
12
|
+
|
13
|
+
before do
|
14
|
+
persistence.should_receive(:init).once
|
15
|
+
persistence.should_receive(:get_undispatched_commits).once { commits }
|
16
|
+
bus.should_receive(:publish).with(commits.first).once
|
17
|
+
bus.should_receive(:publish).with(commits.last).once
|
18
|
+
|
19
|
+
Euston::EventStore::Dispatcher::AsynchronousDispatcher.new bus, persistence
|
20
|
+
sleep 0.25
|
21
|
+
end
|
22
|
+
|
23
|
+
it('initializes the persistence engine') { persistence.rspec_verify }
|
24
|
+
it('gets the set of undispatched commits') { persistence.rspec_verify }
|
25
|
+
it('provides the commits to the published') { bus.rspec_verify }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when asynchronously dispatching a commit' do
|
29
|
+
let(:commit) { new_commit }
|
30
|
+
let(:bus) { stub('bus').as_null_object }
|
31
|
+
let(:persistence) { stub('persistence').as_null_object }
|
32
|
+
|
33
|
+
before do
|
34
|
+
bus.should_receive(:publish).with(commit).once
|
35
|
+
persistence.should_receive(:mark_commit_as_dispatched).with(commit).once
|
36
|
+
|
37
|
+
@dispatcher = Euston::EventStore::Dispatcher::AsynchronousDispatcher.new bus, persistence
|
38
|
+
@dispatcher.dispatch commit
|
39
|
+
sleep 0.25
|
40
|
+
end
|
41
|
+
|
42
|
+
it('provides the commit to the message bus') { bus.rspec_verify }
|
43
|
+
it('marks the commit as dispatched') { persistence.rspec_verify }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when an asynchronously dispatched commit throws an exception' do
|
47
|
+
let(:commit) { new_commit }
|
48
|
+
let(:persistence) { stub('persistence').as_null_object }
|
49
|
+
|
50
|
+
before do
|
51
|
+
persistence.stub(:get_undispatched_commits) { [] }
|
52
|
+
|
53
|
+
@dispatcher = Euston::EventStore::Dispatcher::AsynchronousDispatcher.new nil, persistence do |commit, exception|
|
54
|
+
@caught_commit = commit
|
55
|
+
@caught_exception = exception
|
56
|
+
end
|
57
|
+
|
58
|
+
@dispatcher.dispatch commit
|
59
|
+
sleep 0.25
|
60
|
+
end
|
61
|
+
|
62
|
+
it('provides the commit that caused the error') { @caught_commit.should be_an(Euston::EventStore::Commit) }
|
63
|
+
it('provides the exception') { @caught_exception.should be_an(Exception) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def new_commit(options = {})
|
67
|
+
defaults = { :stream_id => uuid.generate,
|
68
|
+
:stream_revision => 0,
|
69
|
+
:commit_id => uuid.generate,
|
70
|
+
:commit_sequence => 0 }
|
71
|
+
|
72
|
+
Euston::EventStore::Commit.new(defaults.merge options)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Euston::EventStore do
|
4
|
+
let(:uuid) { Uuid }
|
5
|
+
|
6
|
+
describe 'synchronous dispatcher' do
|
7
|
+
let(:bus) { stub('bus').as_null_object }
|
8
|
+
let(:persistence) { stub('persistence').as_null_object }
|
9
|
+
|
10
|
+
context 'when synchronously dispatching a commit' do
|
11
|
+
let(:commit) { new_commit }
|
12
|
+
|
13
|
+
before do
|
14
|
+
persistence.stub(:get_undispatched_commits) { [] }
|
15
|
+
persistence.should_receive(:mark_commit_as_dispatched).with(commit).once
|
16
|
+
|
17
|
+
@dispatched_commits = []
|
18
|
+
|
19
|
+
@dispatcher = Euston::EventStore::Dispatcher::SynchronousDispatcher.new(persistence) do |c|
|
20
|
+
@dispatched_commits << c
|
21
|
+
end
|
22
|
+
|
23
|
+
@dispatcher.dispatch commit
|
24
|
+
end
|
25
|
+
|
26
|
+
it('provides the commit to the message bus') { @dispatched_commits.should have(1).item }
|
27
|
+
it('marks the commit as dispatched') { persistence.rspec_verify }
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_commit(options = {})
|
31
|
+
defaults = { :stream_id => uuid.generate,
|
32
|
+
:stream_revision => 0,
|
33
|
+
:commit_id => uuid.generate,
|
34
|
+
:commit_sequence => 0 }
|
35
|
+
|
36
|
+
Euston::EventStore::Commit.new(defaults.merge options)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|