ruby_event_store 2.0.0 → 2.3.0
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.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/lib/ruby_event_store/client.rb +37 -6
- data/lib/ruby_event_store/errors.rb +1 -0
- data/lib/ruby_event_store/event.rb +5 -2
- data/lib/ruby_event_store/in_memory_repository.rb +67 -15
- data/lib/ruby_event_store/instrumented_dispatcher.rb +12 -2
- data/lib/ruby_event_store/instrumented_repository.rb +17 -11
- data/lib/ruby_event_store/mappers/default.rb +1 -28
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +2 -0
- data/lib/ruby_event_store/mappers/forgotten_data.rb +1 -1
- data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +1 -1
- data/lib/ruby_event_store/mappers/instrumented_mapper.rb +0 -4
- data/lib/ruby_event_store/mappers/json_mapper.rb +7 -5
- data/lib/ruby_event_store/mappers/pipeline.rb +2 -26
- data/lib/ruby_event_store/mappers/pipeline_mapper.rb +0 -4
- data/lib/ruby_event_store/mappers/transformation/domain_event.rb +13 -3
- data/lib/ruby_event_store/mappers/transformation/upcast.rb +37 -0
- data/lib/ruby_event_store/metadata.rb +3 -3
- data/lib/ruby_event_store/projection.rb +2 -2
- data/lib/ruby_event_store/spec/broker_lint.rb +11 -11
- data/lib/ruby_event_store/spec/event_lint.rb +9 -9
- data/lib/ruby_event_store/spec/event_repository_lint.rb +338 -281
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +41 -33
- data/lib/ruby_event_store/subscriptions.rb +24 -9
- data/lib/ruby_event_store/transform_keys.rb +5 -5
- data/lib/ruby_event_store/version.rb +1 -1
- data/lib/ruby_event_store.rb +43 -44
- metadata +26 -17
- data/.mutant.yml +0 -1
- data/CHANGELOG.md +0 -93
- data/Gemfile +0 -9
- data/Gemfile.lock +0 -118
- data/Makefile +0 -32
- data/lib/ruby_event_store/mappers/deprecated_wrapper.rb +0 -33
- data/lib/ruby_event_store/mappers/transformation/serialization.rb +0 -36
- data/ruby_event_store.gemspec +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9170662f3f550d541b79d403aeacfdf734a41a2c48c90752a7a35218913c0ead
|
4
|
+
data.tar.gz: 3fa3e5b0e60d839391da9669a74228847aa921903c5d5a7db35363945af5b0f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f753b40d9b216d372dba8692c69b855ac8bb382664f853be628e094557aa391fe1076c98a934f437158622b65a2cf6b5353e6672facadb408ae43441576f49ce
|
7
|
+
data.tar.gz: 7b909ee630098c28449eae7ca5dc0b018158ee3a8e240074a25bf71d602bb0529873487dd5d6f6f0ff495cd06b9b1ddfa3b1538fe42f91556fcd1a7cba666232
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
1
|
# RubyEventStore
|
2
2
|
|
3
|
-
|
3
|
+
Ruby implementation of an event store. Ships with in-memory event repository, generic instrumentation and dispatches events synchronously.
|
4
|
+
|
5
|
+
Find out more at [https://railseventstore.org](https://railseventstore.org/)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "concurrent"
|
4
4
|
|
5
5
|
module RubyEventStore
|
6
6
|
class Client
|
@@ -13,7 +13,7 @@ module RubyEventStore
|
|
13
13
|
|
14
14
|
|
15
15
|
@repository = repository
|
16
|
-
@mapper =
|
16
|
+
@mapper = mapper
|
17
17
|
@subscriptions = subscriptions
|
18
18
|
@broker = Broker.new(subscriptions: subscriptions, dispatcher: dispatcher)
|
19
19
|
@clock = clock
|
@@ -64,7 +64,7 @@ module RubyEventStore
|
|
64
64
|
# @param expected_version (see #publish)
|
65
65
|
# @return [self]
|
66
66
|
def link(event_ids, stream_name:, expected_version: :any)
|
67
|
-
repository.link_to_stream(event_ids, Stream.new(stream_name), ExpectedVersion.new(expected_version))
|
67
|
+
repository.link_to_stream(Array(event_ids), Stream.new(stream_name), ExpectedVersion.new(expected_version))
|
68
68
|
self
|
69
69
|
end
|
70
70
|
|
@@ -94,6 +94,33 @@ module RubyEventStore
|
|
94
94
|
repository.streams_of(event_id)
|
95
95
|
end
|
96
96
|
|
97
|
+
# Gets position of the event in given stream
|
98
|
+
#
|
99
|
+
# The position is always nonnegative.
|
100
|
+
# Returns nil if the event has no specific position in stream.
|
101
|
+
# Raise error if event is not present in stream.
|
102
|
+
#
|
103
|
+
# @param event_id [String]
|
104
|
+
# @param stream_name [String]
|
105
|
+
# @return [Integer] nonnegative integer position of event in stream
|
106
|
+
# @raise [EventNotInStream]
|
107
|
+
def position_in_stream(event_id, stream_name)
|
108
|
+
repository.position_in_stream(event_id, Stream.new(stream_name))
|
109
|
+
end
|
110
|
+
|
111
|
+
# Gets position of the event in global stream
|
112
|
+
#
|
113
|
+
# The position is always nonnegative.
|
114
|
+
# Global position may have gaps, meaning, there may be event at
|
115
|
+
# position 40, but no event at position 39.
|
116
|
+
#
|
117
|
+
# @param event_id [String]
|
118
|
+
# @raise [EventNotFound]
|
119
|
+
# @return [Integer] nonnegno ative integer position of event in global stream
|
120
|
+
def global_position(event_id)
|
121
|
+
repository.global_position(event_id)
|
122
|
+
end
|
123
|
+
|
97
124
|
# Subscribes a handler (subscriber) that will be invoked for published events of provided type.
|
98
125
|
#
|
99
126
|
# @overload subscribe(subscriber, to:)
|
@@ -131,8 +158,8 @@ module RubyEventStore
|
|
131
158
|
#
|
132
159
|
# @param to [Class, String] type of events to get list of sybscribed handlers
|
133
160
|
# @return [Array<Object, Class>]
|
134
|
-
def subscribers_for(
|
135
|
-
subscriptions.all_for(
|
161
|
+
def subscribers_for(event_class)
|
162
|
+
subscriptions.all_for(event_type_resolver.call(event_class))
|
136
163
|
end
|
137
164
|
|
138
165
|
# Builder object for collecting temporary handlers (subscribers)
|
@@ -239,7 +266,7 @@ module RubyEventStore
|
|
239
266
|
# @return [Event] deserialized event
|
240
267
|
def deserialize(serializer:, event_type:, event_id:, data:, metadata:, timestamp: nil, valid_at: nil)
|
241
268
|
extract_timestamp = lambda do |m|
|
242
|
-
(m[:timestamp] || Time.parse(m.fetch(
|
269
|
+
(m[:timestamp] || Time.parse(m.fetch("timestamp"))).iso8601
|
243
270
|
end
|
244
271
|
|
245
272
|
mapper.record_to_event(
|
@@ -328,6 +355,10 @@ module RubyEventStore
|
|
328
355
|
|
329
356
|
protected
|
330
357
|
|
358
|
+
def event_type_resolver
|
359
|
+
subscriptions.event_type_resolver
|
360
|
+
end
|
361
|
+
|
331
362
|
def metadata=(value)
|
332
363
|
@metadata.value = value
|
333
364
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "securerandom"
|
4
4
|
|
5
5
|
module RubyEventStore
|
6
6
|
|
@@ -31,7 +31,7 @@ module RubyEventStore
|
|
31
31
|
# Type of event. Used when matching with subscribed handlers.
|
32
32
|
# @return [String]
|
33
33
|
def event_type
|
34
|
-
self.class.name
|
34
|
+
metadata[:event_type] || self.class.name
|
35
35
|
end
|
36
36
|
|
37
37
|
# Timestamp from metadata
|
@@ -50,6 +50,7 @@ module RubyEventStore
|
|
50
50
|
|
51
51
|
# Two events are equal if:
|
52
52
|
# * they are of the same class
|
53
|
+
# * have identical event type
|
53
54
|
# * have identical event id
|
54
55
|
# * have identical data (verified with eql? method)
|
55
56
|
#
|
@@ -59,6 +60,7 @@ module RubyEventStore
|
|
59
60
|
# @return [TrueClass, FalseClass]
|
60
61
|
def ==(other_event)
|
61
62
|
other_event.instance_of?(self.class) &&
|
63
|
+
other_event.event_type.eql?(event_type) &&
|
62
64
|
other_event.event_id.eql?(event_id) &&
|
63
65
|
other_event.data.eql?(data)
|
64
66
|
end
|
@@ -80,6 +82,7 @@ module RubyEventStore
|
|
80
82
|
# We don't use metadata because == does not use metadata
|
81
83
|
[
|
82
84
|
self.class,
|
85
|
+
event_type,
|
83
86
|
event_id,
|
84
87
|
data
|
85
88
|
].hash ^ BIG_VALUE
|
@@ -1,40 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "ostruct"
|
4
4
|
module RubyEventStore
|
5
5
|
class InMemoryRepository
|
6
|
+
class UnsupportedVersionAnyUsage < StandardError
|
7
|
+
def initialize
|
8
|
+
super <<~EOS
|
9
|
+
Mixing expected version :any and specific position (or :auto) is unsupported.
|
10
|
+
|
11
|
+
Read more about expected versions here:
|
12
|
+
https://railseventstore.org/docs/v2/expected_version/
|
13
|
+
EOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class EventInStream
|
18
|
+
def initialize(event_id, position)
|
19
|
+
@event_id = event_id
|
20
|
+
@position = position
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :event_id, :position
|
24
|
+
end
|
6
25
|
|
7
|
-
def initialize(serializer: NULL)
|
26
|
+
def initialize(serializer: NULL, ensure_supported_any_usage: false)
|
8
27
|
@serializer = serializer
|
9
28
|
@streams = Hash.new { |h, k| h[k] = Array.new }
|
10
29
|
@mutex = Mutex.new
|
11
30
|
@storage = Hash.new
|
31
|
+
@ensure_supported_any_usage = ensure_supported_any_usage
|
12
32
|
end
|
13
33
|
|
14
34
|
def append_to_stream(records, stream, expected_version)
|
15
|
-
serialized_records =
|
35
|
+
serialized_records = records.map { |record| record.serialize(serializer) }
|
16
36
|
|
17
37
|
with_synchronize(expected_version, stream) do |resolved_version|
|
18
|
-
|
38
|
+
ensure_supported_any_usage(resolved_version, stream)
|
39
|
+
raise WrongExpectedEventVersion unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
|
19
40
|
|
20
|
-
serialized_records.
|
41
|
+
serialized_records.each_with_index do |serialized_record, index|
|
21
42
|
raise EventDuplicatedInStream if has_event?(serialized_record.event_id)
|
22
43
|
storage[serialized_record.event_id] = serialized_record
|
23
|
-
|
44
|
+
add_to_stream(stream, serialized_record, resolved_version, index)
|
24
45
|
end
|
25
46
|
end
|
26
47
|
self
|
27
48
|
end
|
28
49
|
|
29
50
|
def link_to_stream(event_ids, stream, expected_version)
|
30
|
-
serialized_records =
|
51
|
+
serialized_records = event_ids.map { |id| read_event(id) }
|
31
52
|
|
32
53
|
with_synchronize(expected_version, stream) do |resolved_version|
|
33
|
-
|
54
|
+
ensure_supported_any_usage(resolved_version, stream)
|
55
|
+
raise WrongExpectedEventVersion unless resolved_version.nil? || last_stream_version(stream).equal?(resolved_version)
|
34
56
|
|
35
|
-
serialized_records.
|
57
|
+
serialized_records.each_with_index do |serialized_record, index|
|
36
58
|
raise EventDuplicatedInStream if has_event_in_stream?(serialized_record.event_id, stream.name)
|
37
|
-
|
59
|
+
add_to_stream(stream, serialized_record, resolved_version, index)
|
38
60
|
end
|
39
61
|
end
|
40
62
|
self
|
@@ -60,7 +82,7 @@ module RubyEventStore
|
|
60
82
|
serialized_records
|
61
83
|
.drop(offset)
|
62
84
|
.take(limit)
|
63
|
-
.map{|serialized_record| serialized_record.deserialize(serializer) }
|
85
|
+
.map { |serialized_record| serialized_record.deserialize(serializer) }
|
64
86
|
end
|
65
87
|
BatchEnumerator.new(spec.batch_size, serialized_records.size, batch_reader).each
|
66
88
|
elsif spec.first?
|
@@ -102,6 +124,16 @@ module RubyEventStore
|
|
102
124
|
.map { |name,| Stream.new(name) }
|
103
125
|
end
|
104
126
|
|
127
|
+
def position_in_stream(event_id, stream)
|
128
|
+
event_in_stream = streams[stream.name].find {|event_in_stream| event_in_stream.event_id.eql?(event_id) }
|
129
|
+
raise EventNotFoundInStream if event_in_stream.nil?
|
130
|
+
event_in_stream.position
|
131
|
+
end
|
132
|
+
|
133
|
+
def global_position(event_id)
|
134
|
+
storage.keys.index(event_id) or raise EventNotFound.new(event_id)
|
135
|
+
end
|
136
|
+
|
105
137
|
private
|
106
138
|
def read_scope(spec)
|
107
139
|
serialized_records = serialized_records_of_stream(spec.stream)
|
@@ -124,7 +156,7 @@ module RubyEventStore
|
|
124
156
|
end
|
125
157
|
|
126
158
|
def event_ids_of_stream(stream)
|
127
|
-
streams.fetch(stream.name, Array.new)
|
159
|
+
streams.fetch(stream.name, Array.new).map(&:event_id)
|
128
160
|
end
|
129
161
|
|
130
162
|
def serialized_records_of_stream(stream)
|
@@ -143,7 +175,7 @@ module RubyEventStore
|
|
143
175
|
end
|
144
176
|
|
145
177
|
def last_stream_version(stream)
|
146
|
-
|
178
|
+
streams.fetch(stream.name, Array.new).size - 1
|
147
179
|
end
|
148
180
|
|
149
181
|
def with_synchronize(expected_version, stream, &block)
|
@@ -158,19 +190,39 @@ module RubyEventStore
|
|
158
190
|
# not for the whole read+write algorithm.
|
159
191
|
Thread.pass
|
160
192
|
mutex.synchronize do
|
161
|
-
resolved_version = last_stream_version(stream) if expected_version.any?
|
162
193
|
block.call(resolved_version)
|
163
194
|
end
|
164
195
|
end
|
165
196
|
|
166
197
|
def has_event_in_stream?(event_id, stream_name)
|
167
|
-
streams.fetch(stream_name, Array.new).any? { |
|
198
|
+
streams.fetch(stream_name, Array.new).any? { |event_in_stream| event_in_stream.event_id.eql?(event_id) }
|
168
199
|
end
|
169
200
|
|
170
201
|
def index_of(source, event_id)
|
171
202
|
source.index {|item| item.event_id.eql?(event_id)}
|
172
203
|
end
|
173
204
|
|
205
|
+
def compute_position(resolved_version, index)
|
206
|
+
unless resolved_version.nil?
|
207
|
+
resolved_version + index + 1
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def add_to_stream(stream, serialized_record, resolved_version, index)
|
212
|
+
streams[stream.name] << EventInStream.new(serialized_record.event_id, compute_position(resolved_version, index))
|
213
|
+
end
|
214
|
+
|
215
|
+
def ensure_supported_any_usage(resolved_version, stream)
|
216
|
+
if @ensure_supported_any_usage
|
217
|
+
stream_positions = streams.fetch(stream.name, Array.new).map(&:position)
|
218
|
+
if resolved_version.nil?
|
219
|
+
raise UnsupportedVersionAnyUsage if !stream_positions.compact.empty?
|
220
|
+
else
|
221
|
+
raise UnsupportedVersionAnyUsage if stream_positions.include?(nil)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
174
226
|
attr_reader :streams, :mutex, :storage, :serializer
|
175
227
|
end
|
176
228
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ruby2_keywords"
|
4
|
+
|
3
5
|
module RubyEventStore
|
4
6
|
class InstrumentedDispatcher
|
5
7
|
def initialize(dispatcher, instrumentation)
|
@@ -13,8 +15,16 @@ module RubyEventStore
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
17
|
-
|
18
|
+
ruby2_keywords def method_missing(method_name, *arguments, &block)
|
19
|
+
if respond_to?(method_name)
|
20
|
+
dispatcher.public_send(method_name, *arguments, &block)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method_name, _include_private)
|
27
|
+
dispatcher.respond_to?(method_name)
|
18
28
|
end
|
19
29
|
|
20
30
|
private
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ruby2_keywords"
|
4
|
+
|
3
5
|
module RubyEventStore
|
4
6
|
class InstrumentedRepository
|
5
7
|
def initialize(repository, instrumentation)
|
@@ -7,9 +9,9 @@ module RubyEventStore
|
|
7
9
|
@instrumentation = instrumentation
|
8
10
|
end
|
9
11
|
|
10
|
-
def append_to_stream(
|
11
|
-
instrumentation.instrument("append_to_stream.repository.rails_event_store", events:
|
12
|
-
repository.append_to_stream(
|
12
|
+
def append_to_stream(records, stream, expected_version)
|
13
|
+
instrumentation.instrument("append_to_stream.repository.rails_event_store", events: records, stream: stream) do
|
14
|
+
repository.append_to_stream(records, stream, expected_version)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -25,14 +27,6 @@ module RubyEventStore
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
|
-
def has_event?(event_id)
|
29
|
-
repository.has_event?(event_id)
|
30
|
-
end
|
31
|
-
|
32
|
-
def last_stream_event(stream)
|
33
|
-
repository.last_stream_event(stream)
|
34
|
-
end
|
35
|
-
|
36
30
|
def read(specification)
|
37
31
|
instrumentation.instrument("read.repository.rails_event_store", specification: specification) do
|
38
32
|
repository.read(specification)
|
@@ -57,6 +51,18 @@ module RubyEventStore
|
|
57
51
|
end
|
58
52
|
end
|
59
53
|
|
54
|
+
ruby2_keywords def method_missing(method_name, *arguments, &block)
|
55
|
+
if respond_to?(method_name)
|
56
|
+
repository.public_send(method_name, *arguments, &block)
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to_missing?(method_name, _include_private)
|
63
|
+
repository.respond_to?(method_name)
|
64
|
+
end
|
65
|
+
|
60
66
|
private
|
61
67
|
attr_reader :repository, :instrumentation
|
62
68
|
end
|
@@ -1,36 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'yaml'
|
4
|
-
|
5
3
|
module RubyEventStore
|
6
4
|
module Mappers
|
7
5
|
class Default < PipelineMapper
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :serializer
|
11
|
-
|
12
|
-
def initialize(serializer: UNSET, events_class_remapping: {})
|
13
|
-
case serializer
|
14
|
-
when UNSET
|
15
|
-
@serializer = YAML
|
16
|
-
else
|
17
|
-
warn <<~EOW
|
18
|
-
Passing serializer: to #{self.class} has been deprecated.
|
19
|
-
|
20
|
-
Pass it directly to the repository and the scheduler. For example:
|
21
|
-
|
22
|
-
Rails.configuration.event_store = RailsEventStore::Client.new(
|
23
|
-
mapper: RubyEventStore::Mappers::Default.new,
|
24
|
-
repository: RailsEventStoreActiveRecord::EventRepository.new(serializer: #{serializer}),
|
25
|
-
dispatcher: RubyEventStore::ComposedDispatcher.new(
|
26
|
-
RailsEventStore::AfterCommitAsyncDispatcher.new(scheduler: RailsEventStore::ActiveJobScheduler.new(serializer: #{serializer}),
|
27
|
-
RubyEventStore::Dispatcher.new
|
28
|
-
)
|
29
|
-
)
|
30
|
-
EOW
|
31
|
-
@serializer = serializer
|
32
|
-
end
|
33
|
-
|
6
|
+
def initialize(events_class_remapping: {})
|
34
7
|
super(Pipeline.new(
|
35
8
|
Transformation::EventClassRemapper.new(events_class_remapping),
|
36
9
|
Transformation::SymbolizeMetadataKeys.new,
|