ruby_event_store 2.1.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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?, :==