ruby_event_store 2.2.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/lib/ruby_event_store/client.rb +29 -2
- 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 +64 -12
- data/lib/ruby_event_store/instrumented_dispatcher.rb +12 -2
- data/lib/ruby_event_store/instrumented_repository.rb +14 -8
- data/lib/ruby_event_store/mappers/encryption_mapper.rb +1 -1
- 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/transformation/domain_event.rb +11 -1
- 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 +273 -235
- data/lib/ruby_event_store/spec/subscriptions_lint.rb +35 -35
- data/lib/ruby_event_store/subscriptions.rb +1 -1
- data/lib/ruby_event_store/version.rb +1 -1
- data/lib/ruby_event_store.rb +43 -43
- metadata +16 -2
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
|
@@ -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
|
@@ -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:)
|
@@ -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(
|
@@ -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,26 +1,47 @@
|
|
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
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
|
@@ -30,11 +51,12 @@ module RubyEventStore
|
|
30
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
|
@@ -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)
|
@@ -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
|
@@ -21,11 +21,21 @@ module RubyEventStore
|
|
21
21
|
def load(record)
|
22
22
|
Object.const_get(record.event_type).new(
|
23
23
|
event_id: record.event_id,
|
24
|
+
data: record.data,
|
24
25
|
metadata: record.metadata.merge(
|
25
26
|
timestamp: record.timestamp,
|
26
|
-
valid_at:
|
27
|
+
valid_at: record.valid_at,
|
27
28
|
),
|
29
|
+
)
|
30
|
+
rescue NameError
|
31
|
+
Event.new(
|
32
|
+
event_id: record.event_id,
|
28
33
|
data: record.data,
|
34
|
+
metadata: record.metadata.merge(
|
35
|
+
timestamp: record.timestamp,
|
36
|
+
valid_at: record.valid_at,
|
37
|
+
event_type: record.event_type,
|
38
|
+
),
|
29
39
|
)
|
30
40
|
end
|
31
41
|
end
|
@@ -72,14 +72,14 @@ module RubyEventStore
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def reduce_from_streams(event_store, start, count)
|
75
|
-
raise ArgumentError.new(
|
75
|
+
raise ArgumentError.new("Start must be an array with event ids") unless valid_starting_point?(start)
|
76
76
|
streams.zip(start_events(start)).reduce(initial_state) do |state, (stream_name, start_event_id)|
|
77
77
|
read_scope(event_store, stream_name, count, start_event_id).reduce(state, &method(:transition))
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
81
|
def reduce_from_all_streams(event_store, start, count)
|
82
|
-
raise ArgumentError.new(
|
82
|
+
raise ArgumentError.new("Start must be valid event id") unless valid_starting_point?(start)
|
83
83
|
read_scope(event_store, nil, count, start).reduce(initial_state, &method(:transition))
|
84
84
|
end
|
85
85
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
RSpec.shared_examples :broker do |broker_klass|
|
2
|
-
let(:event) { instance_double(::RubyEventStore::Event, event_type:
|
2
|
+
let(:event) { instance_double(::RubyEventStore::Event, event_type: "EventType") }
|
3
3
|
let(:record) { instance_double(::RubyEventStore::Record) }
|
4
4
|
let(:handler) { HandlerClass.new }
|
5
5
|
let(:subscriptions) { ::RubyEventStore::Subscriptions.new }
|
@@ -7,38 +7,38 @@ RSpec.shared_examples :broker do |broker_klass|
|
|
7
7
|
let(:broker) { broker_klass.new(subscriptions: subscriptions, dispatcher: dispatcher) }
|
8
8
|
|
9
9
|
specify "no dispatch when no subscriptions" do
|
10
|
-
expect(subscriptions).to receive(:all_for).with(
|
10
|
+
expect(subscriptions).to receive(:all_for).with("EventType").and_return([])
|
11
11
|
expect(dispatcher).not_to receive(:call)
|
12
12
|
broker.call(event, record)
|
13
13
|
end
|
14
14
|
|
15
15
|
specify "calls subscription" do
|
16
|
-
expect(subscriptions).to receive(:all_for).with(
|
16
|
+
expect(subscriptions).to receive(:all_for).with("EventType").and_return([handler])
|
17
17
|
expect(dispatcher).to receive(:call).with(handler, event, record)
|
18
18
|
broker.call(event, record)
|
19
19
|
end
|
20
20
|
|
21
21
|
specify "calls subscribed class" do
|
22
|
-
expect(subscriptions).to receive(:all_for).with(
|
22
|
+
expect(subscriptions).to receive(:all_for).with("EventType").and_return([HandlerClass])
|
23
23
|
expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
|
24
24
|
broker.call(event, record)
|
25
25
|
end
|
26
26
|
|
27
27
|
specify "calls all subscriptions" do
|
28
|
-
expect(subscriptions).to receive(:all_for).with(
|
28
|
+
expect(subscriptions).to receive(:all_for).with("EventType").and_return([handler, HandlerClass])
|
29
29
|
expect(dispatcher).to receive(:call).with(handler, event, record)
|
30
30
|
expect(dispatcher).to receive(:call).with(HandlerClass, event, record)
|
31
31
|
broker.call(event, record)
|
32
32
|
end
|
33
33
|
|
34
|
-
specify
|
34
|
+
specify "raise error when no subscriber" do
|
35
35
|
expect { broker.add_subscription(nil, [])}.to raise_error(RubyEventStore::SubscriberNotExist, "subscriber must be first argument or block")
|
36
36
|
expect { broker.add_global_subscription(nil)}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
|
37
37
|
expect { broker.add_thread_subscription(nil, []).call}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
|
38
38
|
expect { broker.add_thread_global_subscription(nil).call}.to raise_error(RubyEventStore::SubscriberNotExist), "subscriber must be first argument or block"
|
39
39
|
end
|
40
40
|
|
41
|
-
specify
|
41
|
+
specify "raise error when wrong subscriber" do
|
42
42
|
allow(dispatcher).to receive(:verify).and_return(false)
|
43
43
|
expect do
|
44
44
|
broker.add_subscription(HandlerClass, [])
|
@@ -56,8 +56,8 @@ RSpec.shared_examples :broker do |broker_klass|
|
|
56
56
|
|
57
57
|
specify "verify and add - local subscriptions" do
|
58
58
|
expect(dispatcher).to receive(:verify).with(handler).and_return(true)
|
59
|
-
expect(subscriptions).to receive(:add_subscription).with(handler, [
|
60
|
-
broker.add_subscription(handler, [
|
59
|
+
expect(subscriptions).to receive(:add_subscription).with(handler, ["EventType"])
|
60
|
+
broker.add_subscription(handler, ["EventType"])
|
61
61
|
end
|
62
62
|
|
63
63
|
specify "verify and add - global subscriptions" do
|
@@ -68,8 +68,8 @@ RSpec.shared_examples :broker do |broker_klass|
|
|
68
68
|
|
69
69
|
specify "verify and add - thread local subscriptions" do
|
70
70
|
expect(dispatcher).to receive(:verify).with(handler).and_return(true)
|
71
|
-
expect(subscriptions).to receive(:add_thread_subscription).with(handler, [
|
72
|
-
broker.add_thread_subscription(handler, [
|
71
|
+
expect(subscriptions).to receive(:add_thread_subscription).with(handler, ["EventType"])
|
72
|
+
broker.add_thread_subscription(handler, ["EventType"])
|
73
73
|
end
|
74
74
|
|
75
75
|
specify "verify and add - thread global subscriptions" do
|