ruby_event_store 2.1.0 → 2.4.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_event_store/batch_enumerator.rb +3 -3
  3. data/lib/ruby_event_store/broker.rb +5 -4
  4. data/lib/ruby_event_store/client.rb +81 -48
  5. data/lib/ruby_event_store/composed_dispatcher.rb +1 -3
  6. data/lib/ruby_event_store/correlated_commands.rb +4 -15
  7. data/lib/ruby_event_store/errors.rb +11 -10
  8. data/lib/ruby_event_store/event.rb +9 -14
  9. data/lib/ruby_event_store/expected_version.rb +3 -7
  10. data/lib/ruby_event_store/in_memory_repository.rb +100 -37
  11. data/lib/ruby_event_store/instrumented_dispatcher.rb +11 -2
  12. data/lib/ruby_event_store/instrumented_repository.rb +13 -8
  13. data/lib/ruby_event_store/link_by_metadata.rb +4 -21
  14. data/lib/ruby_event_store/mappers/default.rb +6 -4
  15. data/lib/ruby_event_store/mappers/encryption_key.rb +7 -16
  16. data/lib/ruby_event_store/mappers/encryption_mapper.rb +6 -6
  17. data/lib/ruby_event_store/mappers/forgotten_data.rb +1 -1
  18. data/lib/ruby_event_store/mappers/in_memory_encryption_key_repository.rb +1 -1
  19. data/lib/ruby_event_store/mappers/null_mapper.rb +0 -1
  20. data/lib/ruby_event_store/mappers/pipeline.rb +3 -10
  21. data/lib/ruby_event_store/mappers/pipeline_mapper.rb +1 -0
  22. data/lib/ruby_event_store/mappers/transformation/domain_event.rb +22 -12
  23. data/lib/ruby_event_store/mappers/transformation/encryption.rb +21 -25
  24. data/lib/ruby_event_store/mappers/transformation/event_class_remapper.rb +6 -5
  25. data/lib/ruby_event_store/mappers/transformation/stringify_metadata_keys.rb +6 -5
  26. data/lib/ruby_event_store/mappers/transformation/symbolize_metadata_keys.rb +6 -5
  27. data/lib/ruby_event_store/mappers/transformation/upcast.rb +2 -6
  28. data/lib/ruby_event_store/metadata.rb +46 -17
  29. data/lib/ruby_event_store/projection.rb +12 -20
  30. data/lib/ruby_event_store/record.rb +14 -26
  31. data/lib/ruby_event_store/serialized_record.rb +14 -26
  32. data/lib/ruby_event_store/serializers/yaml.rb +17 -0
  33. data/lib/ruby_event_store/spec/broker_lint.rb +38 -28
  34. data/lib/ruby_event_store/spec/event_lint.rb +10 -10
  35. data/lib/ruby_event_store/spec/event_repository_lint.rb +746 -730
  36. data/lib/ruby_event_store/spec/mapper_lint.rb +2 -2
  37. data/lib/ruby_event_store/spec/subscriptions_lint.rb +58 -68
  38. data/lib/ruby_event_store/specification.rb +20 -16
  39. data/lib/ruby_event_store/specification_reader.rb +2 -3
  40. data/lib/ruby_event_store/specification_result.rb +52 -46
  41. data/lib/ruby_event_store/stream.rb +3 -7
  42. data/lib/ruby_event_store/subscriptions.rb +17 -17
  43. data/lib/ruby_event_store/transform_keys.rb +1 -1
  44. data/lib/ruby_event_store/version.rb +1 -1
  45. data/lib/ruby_event_store.rb +44 -43
  46. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88203a9b943002241df25fa83c6f067e7944810e66d713eeb11e8cebf13271e2
4
- data.tar.gz: 1b032e898517d787a2e6d0cb6b15775792591abc094e68d2483590f916e69c9e
3
+ metadata.gz: 21f9e0f89d5ab86baa4f840b70c2093d518e8077ee8380cce54144c0f123aaf0
4
+ data.tar.gz: 9927020649c8f492843ce48af29b020e7831efae54a949cb2e8078288254bed9
5
5
  SHA512:
6
- metadata.gz: 780c9e0f6064311d45be29c0553c47e65e679a3967b91e6caca48a55ef9c34a06070e421b1413c1a35634c6e45c1452204ac29fae2aa706fe7a0e46d1dd99f40
7
- data.tar.gz: 66b7a11ce2d3b58a5f9c2690a7f7f998fc9130c302343923cdd5a57a63d6a398ea258cced2d1b3161e07d9fb081d04bd7c4f305d6576cae59bd6e50f50ba6a92
6
+ metadata.gz: 4763bae7c34f07594ff9757f6482de78c84550bec861210e89930624438025ad0882bb7476eba0f1b511b02b2ff17c63c82a39b9ab065586dfc2704e6b21b6ba
7
+ data.tar.gz: 7ed29eadf34c3b9c3e22169250dfc5c9da81f2fa6f470c57c39313f79125c14ea57f2fd0a2c30715a915417ae84eb1b69bd8ec06a6b01de94e5bc000bea2f8ba
@@ -3,9 +3,9 @@
3
3
  module RubyEventStore
4
4
  class BatchEnumerator
5
5
  def initialize(batch_size, total_limit, reader)
6
- @batch_size = batch_size
6
+ @batch_size = batch_size
7
7
  @total_limit = total_limit
8
- @reader = reader
8
+ @reader = reader
9
9
  end
10
10
 
11
11
  def each
@@ -13,7 +13,7 @@ module RubyEventStore
13
13
 
14
14
  0.step(total_limit - 1, batch_size) do |batch_offset|
15
15
  batch_limit = [batch_size, total_limit - batch_offset].min
16
- result = reader.call(batch_offset, batch_limit)
16
+ result = reader.call(batch_offset, batch_limit)
17
17
 
18
18
  break if result.empty?
19
19
  yield result
@@ -9,9 +9,7 @@ module RubyEventStore
9
9
 
10
10
  def call(event, record)
11
11
  subscribers = subscriptions.all_for(event.event_type)
12
- subscribers.each do |subscriber|
13
- dispatcher.call(subscriber, event, record)
14
- end
12
+ subscribers.each { |subscriber| dispatcher.call(subscriber, event, record) }
15
13
  end
16
14
 
17
15
  def add_subscription(subscriber, event_types)
@@ -35,11 +33,14 @@ module RubyEventStore
35
33
  end
36
34
 
37
35
  private
36
+
38
37
  attr_reader :dispatcher, :subscriptions
39
38
 
40
39
  def verify_subscription(subscriber)
41
40
  raise SubscriberNotExist, "subscriber must be first argument or block" unless subscriber
42
- raise InvalidHandler.new("Handler #{subscriber} is invalid for dispatcher #{dispatcher}") unless dispatcher.verify(subscriber)
41
+ unless dispatcher.verify(subscriber)
42
+ raise InvalidHandler.new("Handler #{subscriber} is invalid for dispatcher #{dispatcher}")
43
+ end
43
44
  end
44
45
  end
45
46
  end
@@ -1,27 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent'
3
+ require "concurrent"
4
4
 
5
5
  module RubyEventStore
6
6
  class Client
7
- def initialize(repository:,
8
- mapper: Mappers::Default.new,
9
- subscriptions: Subscriptions.new,
10
- dispatcher: Dispatcher.new,
11
- clock: default_clock,
12
- correlation_id_generator: default_correlation_id_generator)
13
-
14
-
15
- @repository = repository
16
- @mapper = mapper
17
- @subscriptions = subscriptions
18
- @broker = Broker.new(subscriptions: subscriptions, dispatcher: dispatcher)
19
- @clock = clock
20
- @metadata = Concurrent::ThreadLocalVar.new
7
+ def initialize(
8
+ repository:,
9
+ mapper: Mappers::Default.new,
10
+ subscriptions: Subscriptions.new,
11
+ dispatcher: Dispatcher.new,
12
+ clock: default_clock,
13
+ correlation_id_generator: default_correlation_id_generator
14
+ )
15
+ @repository = repository
16
+ @mapper = mapper
17
+ @subscriptions = subscriptions
18
+ @broker = Broker.new(subscriptions: subscriptions, dispatcher: dispatcher)
19
+ @clock = clock
20
+ @metadata = Concurrent::ThreadLocalVar.new
21
21
  @correlation_id_generator = correlation_id_generator
22
22
  end
23
23
 
24
-
25
24
  # Persists events and notifies subscribed handlers about them
26
25
  #
27
26
  # @param events [Array<Event>, Event] event(s)
@@ -30,13 +29,10 @@ module RubyEventStore
30
29
  # @return [self]
31
30
  def publish(events, stream_name: GLOBAL_STREAM, expected_version: :any)
32
31
  enriched_events = enrich_events_metadata(events)
33
- records = transform(enriched_events)
32
+ records = transform(enriched_events)
34
33
  append_records_to_stream(records, stream_name: stream_name, expected_version: expected_version)
35
34
  enriched_events.zip(records) do |event, record|
36
- with_metadata(
37
- correlation_id: event.metadata.fetch(:correlation_id),
38
- causation_id: event.event_id,
39
- ) do
35
+ with_metadata(correlation_id: event.metadata.fetch(:correlation_id), causation_id: event.event_id) do
40
36
  broker.(event, record)
41
37
  end
42
38
  end
@@ -94,6 +90,43 @@ module RubyEventStore
94
90
  repository.streams_of(event_id)
95
91
  end
96
92
 
93
+ # Gets position of the event in given stream
94
+ #
95
+ # The position is always nonnegative.
96
+ # Returns nil if the event has no specific position in stream.
97
+ # Raise error if event is not present in stream.
98
+ #
99
+ # @param event_id [String]
100
+ # @param stream_name [String]
101
+ # @return [Integer] nonnegative integer position of event in stream
102
+ # @raise [EventNotInStream]
103
+ def position_in_stream(event_id, stream_name)
104
+ repository.position_in_stream(event_id, Stream.new(stream_name))
105
+ end
106
+
107
+ # Gets position of the event in global stream
108
+ #
109
+ # The position is always nonnegative.
110
+ # Global position may have gaps, meaning, there may be event at
111
+ # position 40, but no event at position 39.
112
+ #
113
+ # @param event_id [String]
114
+ # @raise [EventNotFound]
115
+ # @return [Integer] nonnegno ative integer position of event in global stream
116
+ def global_position(event_id)
117
+ repository.global_position(event_id)
118
+ end
119
+
120
+ # Checks whether event is linked in given stream
121
+ #
122
+ # @param event_id [String]
123
+ # @param stream_name [String]
124
+ # @return [Boolean] true if event is linked to given stream, false otherwise
125
+ def event_in_stream?(event_id, stream_name)
126
+ stream = Stream.new(stream_name)
127
+ stream.global? ? repository.has_event?(event_id) : repository.event_in_stream?(event_id, stream)
128
+ end
129
+
97
130
  # Subscribes a handler (subscriber) that will be invoked for published events of provided type.
98
131
  #
99
132
  # @overload subscribe(subscriber, to:)
@@ -131,8 +164,8 @@ module RubyEventStore
131
164
  #
132
165
  # @param to [Class, String] type of events to get list of sybscribed handlers
133
166
  # @return [Array<Object, Class>]
134
- def subscribers_for(event_type)
135
- subscriptions.all_for(event_type)
167
+ def subscribers_for(event_class)
168
+ subscriptions.all_for(event_type_resolver.call(event_class))
136
169
  end
137
170
 
138
171
  # Builder object for collecting temporary handlers (subscribers)
@@ -143,7 +176,7 @@ module RubyEventStore
143
176
  @block = block
144
177
  @broker = broker
145
178
  @global_subscribers = []
146
- @subscribers = Hash.new {[]}
179
+ @subscribers = Hash.new { [] }
147
180
  end
148
181
 
149
182
  # Subscribes temporary handlers that
@@ -175,7 +208,7 @@ module RubyEventStore
175
208
  # @param to [Array<Class>] types of events to subscribe
176
209
  # @param handler [Proc] handler passed as proc
177
210
  # @return [self]
178
- def subscribe(handler=nil, to:, &handler2)
211
+ def subscribe(handler = nil, to:, &handler2)
179
212
  raise ArgumentError if handler && handler2
180
213
  @subscribers[handler || handler2] += Array(to)
181
214
  self
@@ -187,7 +220,7 @@ module RubyEventStore
187
220
  #
188
221
  # @return [Object] value returned by the invoked block of code
189
222
  def call
190
- unsubs = add_thread_global_subscribers
223
+ unsubs = add_thread_global_subscribers
191
224
  unsubs += add_thread_subscribers
192
225
  @block.call
193
226
  ensure
@@ -197,15 +230,11 @@ module RubyEventStore
197
230
  private
198
231
 
199
232
  def add_thread_subscribers
200
- @subscribers.map do |subscriber, types|
201
- @broker.add_thread_subscription(subscriber, types)
202
- end
233
+ @subscribers.map { |subscriber, types| @broker.add_thread_subscription(subscriber, types) }
203
234
  end
204
235
 
205
236
  def add_thread_global_subscribers
206
- @global_subscribers.map do |subscriber|
207
- @broker.add_thread_global_subscription(subscriber)
208
- end
237
+ @global_subscribers.map { |subscriber| @broker.add_thread_global_subscription(subscriber) }
209
238
  end
210
239
  end
211
240
 
@@ -238,19 +267,19 @@ module RubyEventStore
238
267
  #
239
268
  # @return [Event] deserialized event
240
269
  def deserialize(serializer:, event_type:, event_id:, data:, metadata:, timestamp: nil, valid_at: nil)
241
- extract_timestamp = lambda do |m|
242
- (m[:timestamp] || Time.parse(m.fetch('timestamp'))).iso8601
243
- end
270
+ extract_timestamp = lambda { |m| (m[:timestamp] || Time.parse(m.fetch("timestamp"))).iso8601 }
244
271
 
245
272
  mapper.record_to_event(
246
- SerializedRecord.new(
247
- event_type: event_type,
248
- event_id: event_id,
249
- data: data,
250
- metadata: metadata,
251
- timestamp: timestamp || timestamp_ = extract_timestamp[serializer.load(metadata)],
252
- valid_at: valid_at || timestamp_,
253
- ).deserialize(serializer)
273
+ SerializedRecord
274
+ .new(
275
+ event_type: event_type,
276
+ event_id: event_id,
277
+ data: data,
278
+ metadata: metadata,
279
+ timestamp: timestamp || timestamp_ = extract_timestamp[serializer.load(metadata)],
280
+ valid_at: valid_at || timestamp_
281
+ )
282
+ .deserialize(serializer)
254
283
  )
255
284
  end
256
285
 
@@ -311,14 +340,14 @@ module RubyEventStore
311
340
 
312
341
  def enrich_events_metadata(events)
313
342
  events = Array(events)
314
- events.each{|event| enrich_event_metadata(event) }
343
+ events.each { |event| enrich_event_metadata(event) }
315
344
  events
316
345
  end
317
346
 
318
347
  def enrich_event_metadata(event)
319
348
  metadata.each { |key, value| event.metadata[key] ||= value }
320
- event.metadata[:timestamp] ||= clock.call
321
- event.metadata[:valid_at] ||= event.metadata.fetch(:timestamp)
349
+ event.metadata[:timestamp] ||= clock.call
350
+ event.metadata[:valid_at] ||= event.metadata.fetch(:timestamp)
322
351
  event.metadata[:correlation_id] ||= correlation_id_generator.call
323
352
  end
324
353
 
@@ -328,16 +357,20 @@ module RubyEventStore
328
357
 
329
358
  protected
330
359
 
360
+ def event_type_resolver
361
+ subscriptions.event_type_resolver
362
+ end
363
+
331
364
  def metadata=(value)
332
365
  @metadata.value = value
333
366
  end
334
367
 
335
368
  def default_clock
336
- ->{ Time.now.utc.round(TIMESTAMP_PRECISION) }
369
+ -> { Time.now.utc.round(TIMESTAMP_PRECISION) }
337
370
  end
338
371
 
339
372
  def default_correlation_id_generator
340
- ->{ SecureRandom.uuid }
373
+ -> { SecureRandom.uuid }
341
374
  end
342
375
 
343
376
  attr_reader :repository, :mapper, :subscriptions, :broker, :clock, :correlation_id_generator
@@ -16,9 +16,7 @@ module RubyEventStore
16
16
  end
17
17
 
18
18
  def verify(subscriber)
19
- @dispatchers.any? do |dispatcher|
20
- dispatcher.verify(subscriber)
21
- end
19
+ @dispatchers.any? { |dispatcher| dispatcher.verify(subscriber) }
22
20
  end
23
21
  end
24
22
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  class CorrelatedCommands
5
-
6
5
  def initialize(event_store, command_bus)
7
6
  @event_store = event_store
8
7
  @command_bus = command_bus
@@ -13,23 +12,13 @@ module RubyEventStore
13
12
 
14
13
  def call(command)
15
14
  correlation_id = event_store.metadata[:correlation_id]
16
- causation_id = event_store.metadata[:causation_id]
15
+ causation_id = event_store.metadata[:causation_id]
17
16
 
18
17
  if correlation_id && causation_id
19
- command.correlate_with(MiniEvent.new(
20
- correlation_id,
21
- causation_id,
22
- )) if command.respond_to?(:correlate_with)
23
- event_store.with_metadata(
24
- causation_id: command.message_id,
25
- ) do
26
- command_bus.call(command)
27
- end
18
+ command.correlate_with(MiniEvent.new(correlation_id, causation_id)) if command.respond_to?(:correlate_with)
19
+ event_store.with_metadata(causation_id: command.message_id) { command_bus.call(command) }
28
20
  else
29
- event_store.with_metadata(
30
- correlation_id: command.message_id,
31
- causation_id: command.message_id,
32
- ) do
21
+ event_store.with_metadata(correlation_id: command.message_id, causation_id: command.message_id) do
33
22
  command_bus.call(command)
34
23
  end
35
24
  end
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyEventStore
4
- Error = Class.new(StandardError)
4
+ Error = Class.new(StandardError)
5
5
  WrongExpectedEventVersion = Class.new(Error)
6
- InvalidExpectedVersion = Class.new(Error)
7
- IncorrectStreamData = Class.new(Error)
8
- SubscriberNotExist = Class.new(Error)
9
- InvalidPageStart = Class.new(Error)
10
- InvalidPageStop = Class.new(Error)
11
- InvalidPageSize = Class.new(Error)
12
- EventDuplicatedInStream = Class.new(Error)
13
- ReservedInternalName = Class.new(Error)
14
- InvalidHandler = Class.new(Error)
6
+ InvalidExpectedVersion = Class.new(Error)
7
+ IncorrectStreamData = Class.new(Error)
8
+ SubscriberNotExist = Class.new(Error)
9
+ InvalidPageStart = Class.new(Error)
10
+ InvalidPageStop = Class.new(Error)
11
+ InvalidPageSize = Class.new(Error)
12
+ EventDuplicatedInStream = Class.new(Error)
13
+ ReservedInternalName = Class.new(Error)
14
+ InvalidHandler = Class.new(Error)
15
+ EventNotFoundInStream = Class.new(Error)
15
16
 
16
17
  class EventNotFound < Error
17
18
  attr_reader :event_id
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module RubyEventStore
6
-
7
6
  # Data structure representing an event
8
7
  class Event
9
8
  # Instantiates a new event
@@ -17,7 +16,7 @@ module RubyEventStore
17
16
  def initialize(event_id: SecureRandom.uuid, metadata: nil, data: {})
18
17
  @event_id = event_id.to_s
19
18
  @metadata = Metadata.new(metadata.to_h)
20
- @data = data
19
+ @data = data
21
20
  end
22
21
 
23
22
  attr_reader :event_id, :metadata, :data
@@ -31,7 +30,7 @@ module RubyEventStore
31
30
  # Type of event. Used when matching with subscribed handlers.
32
31
  # @return [String]
33
32
  def event_type
34
- self.class.name
33
+ metadata[:event_type] || self.class.name
35
34
  end
36
35
 
37
36
  # Timestamp from metadata
@@ -50,6 +49,7 @@ module RubyEventStore
50
49
 
51
50
  # Two events are equal if:
52
51
  # * they are of the same class
52
+ # * have identical event type
53
53
  # * have identical event id
54
54
  # * have identical data (verified with eql? method)
55
55
  #
@@ -58,9 +58,8 @@ module RubyEventStore
58
58
  # Event equality ignores metadata!
59
59
  # @return [TrueClass, FalseClass]
60
60
  def ==(other_event)
61
- other_event.instance_of?(self.class) &&
62
- other_event.event_id.eql?(event_id) &&
63
- other_event.data.eql?(data)
61
+ other_event.instance_of?(self.class) && other_event.event_type.eql?(event_type) &&
62
+ other_event.event_id.eql?(event_id) && other_event.data.eql?(data)
64
63
  end
65
64
 
66
65
  # @private
@@ -78,11 +77,7 @@ module RubyEventStore
78
77
  # * data
79
78
  def hash
80
79
  # We don't use metadata because == does not use metadata
81
- [
82
- self.class,
83
- event_id,
84
- data
85
- ].hash ^ BIG_VALUE
80
+ [self.class, event_type, event_id, data].hash ^ BIG_VALUE
86
81
  end
87
82
 
88
83
  # Reads correlation_id from metadata.
@@ -116,7 +111,7 @@ module RubyEventStore
116
111
  # @param val [String]
117
112
  # @return [String]
118
113
  def causation_id=(val)
119
- metadata[:causation_id]= val
114
+ metadata[:causation_id] = val
120
115
  end
121
116
 
122
117
  # Sets correlation_id and causation_id in metadata based
@@ -127,7 +122,7 @@ module RubyEventStore
127
122
  # @return [String] set causation_id
128
123
  def correlate_with(other_message)
129
124
  self.correlation_id = other_message.correlation_id || other_message.message_id
130
- self.causation_id = other_message.message_id
125
+ self.causation_id = other_message.message_id
131
126
  self
132
127
  end
133
128
 
@@ -21,7 +21,7 @@ module RubyEventStore
21
21
 
22
22
  def initialize(version)
23
23
  @version = version
24
- invalid_version! unless [Integer, :any, :none, :auto].any? {|i| i === version}
24
+ invalid_version! unless [Integer, :any, :none, :auto].any? { |i| i === version }
25
25
  end
26
26
 
27
27
  def any?
@@ -53,15 +53,11 @@ module RubyEventStore
53
53
  private_constant :BIG_VALUE
54
54
 
55
55
  def hash
56
- [
57
- self.class,
58
- version
59
- ].hash ^ BIG_VALUE
56
+ [self.class, version].hash ^ BIG_VALUE
60
57
  end
61
58
 
62
59
  def ==(other_expected_version)
63
- other_expected_version.instance_of?(self.class) &&
64
- other_expected_version.version.equal?(version)
60
+ other_expected_version.instance_of?(self.class) && other_expected_version.version.equal?(version)
65
61
  end
66
62
 
67
63
  alias_method :eql?, :==