ruby_event_store 0.21.0 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cfb0772fe901e15fc75b778a04a4dd73192b0407
4
- data.tar.gz: '07929227f6b1964589cef0962edce96e47cb4198'
3
+ metadata.gz: 99a317191ac0b7798f70cb2f044b8dd5093e26be
4
+ data.tar.gz: 2459057d0fe80e5a2c740ec24e27513994807af6
5
5
  SHA512:
6
- metadata.gz: b7207f55c941d2e1bcd48cf656ef001750c9a17617507f38438e671ab8e2b288a6b045a5aec2731ff7da28462e872e76628094dbe80a6f8696238e67fa35ee72
7
- data.tar.gz: ee9af3042920d3a5fb9fe407eb0b0e47a6f46cb0fdf160f1a26f245ee5e057a433b3a1f35eda6afa72c77ef09a7a2423d3e8b7c3b573aeb835c912be2250ce2d
6
+ metadata.gz: 58a68c35017820ec1375810a16aadf00c744b69a2e61c95523abde8b54637996b5555eff582fd2e5d9fb628623462cb08e43e9168803e873f7c041572d5eb5cf
7
+ data.tar.gz: 58b2abff609bca93bf3b77f429c7980ddba3a1abdeaa583af33915398152e05faf52738c11a13b7b1edaf8184905c5e464e11d2513c52ee78868ac488b6365dd
@@ -31,6 +31,11 @@ module RubyEventStore
31
31
  :ok
32
32
  end
33
33
 
34
+ def link_to_stream(event_ids, stream_name:, expected_version: :any)
35
+ repository.link_to_stream(event_ids, stream_name, expected_version)
36
+ self
37
+ end
38
+
34
39
  def delete_stream(stream_name)
35
40
  raise IncorrectStreamData if stream_name.nil? || stream_name.empty?
36
41
  repository.delete_stream(stream_name)
@@ -119,7 +124,7 @@ module RubyEventStore
119
124
  else
120
125
  start = start.to_s
121
126
  raise InvalidPageStart if start.empty?
122
- raise EventNotFound unless repository.has_event?(start)
127
+ raise EventNotFound.new(start) unless repository.has_event?(start)
123
128
  end
124
129
  raise InvalidPageSize unless count > 0
125
130
  @start = start
@@ -2,11 +2,19 @@ module RubyEventStore
2
2
  WrongExpectedEventVersion = Class.new(StandardError)
3
3
  InvalidExpectedVersion = Class.new(StandardError)
4
4
  IncorrectStreamData = Class.new(StandardError)
5
- EventNotFound = Class.new(StandardError)
6
5
  SubscriberNotExist = Class.new(StandardError)
7
6
  InvalidPageStart = Class.new(ArgumentError)
8
7
  InvalidPageSize = Class.new(ArgumentError)
9
8
  EventDuplicatedInStream = Class.new(StandardError)
9
+ NotSupported = Class.new(StandardError)
10
+
11
+ class EventNotFound < StandardError
12
+ attr_reader :event_id
13
+ def initialize(event_id)
14
+ super("Event not found: #{event_id}")
15
+ @event_id = event_id
16
+ end
17
+ end
10
18
 
11
19
  class InvalidHandler < StandardError
12
20
  def initialize(object)
@@ -10,21 +10,12 @@ module RubyEventStore
10
10
  end
11
11
 
12
12
  def append_to_stream(events, stream_name, expected_version)
13
- raise InvalidExpectedVersion if !expected_version.equal?(:any) && stream_name.eql?(GLOBAL_STREAM)
14
- events = normalize_to_array(events)
15
- stream = read_stream_events_forward(stream_name)
16
- expected_version = case expected_version
17
- when :none
18
- -1
19
- when :auto, :any
20
- stream.size - 1
21
- when Integer
22
- expected_version
23
- else
24
- raise InvalidExpectedVersion
25
- end
26
- append_with_synchronize(events, expected_version, stream, stream_name)
27
- self
13
+ add_to_stream(events, expected_version, stream_name, true)
14
+ end
15
+
16
+ def link_to_stream(event_ids, stream_name, expected_version)
17
+ events = normalize_to_array(event_ids).map{|eid| read_event(eid) }
18
+ add_to_stream(events, expected_version, stream_name, nil)
28
19
  end
29
20
 
30
21
  def delete_stream(stream_name)
@@ -66,7 +57,7 @@ module RubyEventStore
66
57
  end
67
58
 
68
59
  def read_event(event_id)
69
- all.find { |e| event_id.equal?(e.event_id) } or raise EventNotFound
60
+ all.find { |e| event_id.eql?(e.event_id) } or raise EventNotFound.new(event_id)
70
61
  end
71
62
 
72
63
  def get_all_streams
@@ -80,7 +71,24 @@ module RubyEventStore
80
71
  return *events
81
72
  end
82
73
 
83
- def append_with_synchronize(events, expected_version, stream, stream_name)
74
+ def add_to_stream(events, expected_version, stream_name, include_global)
75
+ raise InvalidExpectedVersion if !expected_version.equal?(:any) && stream_name.eql?(GLOBAL_STREAM)
76
+ events = normalize_to_array(events)
77
+ stream = read_stream_events_forward(stream_name)
78
+ expected_version = case expected_version
79
+ when :none
80
+ -1
81
+ when :auto, :any
82
+ stream.size - 1
83
+ when Integer
84
+ expected_version
85
+ else
86
+ raise InvalidExpectedVersion
87
+ end
88
+ append_with_synchronize(events, expected_version, stream, stream_name, include_global)
89
+ end
90
+
91
+ def append_with_synchronize(events, expected_version, stream, stream_name, include_global)
84
92
  # expected_version :auto assumes external lock is used
85
93
  # which makes reading stream before writing safe.
86
94
  #
@@ -90,18 +98,22 @@ module RubyEventStore
90
98
  # not for the whole read+write algorithm.
91
99
  Thread.pass
92
100
  @mutex.synchronize do
93
- append(events, expected_version, stream, stream_name)
101
+ append(events, expected_version, stream, stream_name, include_global)
94
102
  end
95
103
  end
96
104
 
97
- def append(events, expected_version, stream, stream_name)
105
+ def append(events, expected_version, stream, stream_name, include_global)
98
106
  raise WrongExpectedEventVersion unless (stream.size - 1).equal?(expected_version)
99
107
  events.each do |event|
100
- all.push(event)
101
108
  raise EventDuplicatedInStream if stream.any?{|ev| ev.event_id.eql?(event.event_id) }
109
+ if include_global
110
+ raise EventDuplicatedInStream if all.any?{|ev| ev.event_id.eql?(event.event_id) }
111
+ all.push(event)
112
+ end
102
113
  stream.push(event)
103
114
  end
104
115
  streams[stream_name] = stream
116
+ self
105
117
  end
106
118
 
107
119
  def read_batch(source, start_event_id, count)
@@ -6,25 +6,55 @@ RSpec.shared_examples :event_repository do |repository_class|
6
6
  expect(repository.read_all_streams_forward(:head, 1)).to be_empty
7
7
  end
8
8
 
9
- specify 'publish fail if expected version is nil' do
9
+ specify 'append_to_stream fail if expected version is nil' do
10
10
  expect do
11
11
  repository.append_to_stream(event = TestDomainEvent.new, 'stream', nil)
12
12
  end.to raise_error(RubyEventStore::InvalidExpectedVersion)
13
13
  end
14
14
 
15
+ specify 'link_to_stream fail if expected version is nil' do
16
+ skip unless test_link_events_to_stream
17
+ repository.append_to_stream(event = TestDomainEvent.new, 'stream', :any)
18
+ expect do
19
+ repository.link_to_stream(event.event_id, 'stream', nil)
20
+ end.to raise_error(RubyEventStore::InvalidExpectedVersion)
21
+ end
22
+
15
23
  specify 'append_to_stream returns self' do
16
24
  repository.
17
25
  append_to_stream(event = TestDomainEvent.new, 'stream', -1).
18
26
  append_to_stream(event = TestDomainEvent.new, 'stream', 0)
19
27
  end
20
28
 
21
- specify 'adds initial event to a new stream' do
29
+ specify 'link_to_stream returns self' do
30
+ skip unless test_link_events_to_stream
31
+ event0 = TestDomainEvent.new
32
+ event1 = TestDomainEvent.new
33
+ repository.
34
+ append_to_stream([event0, event1], 'stream0', -1).
35
+ link_to_stream(event0.event_id, 'flow', -1).
36
+ link_to_stream(event1.event_id, 'flow', 0)
37
+ end
38
+
39
+ specify 'adds an initial event to a new stream' do
22
40
  repository.append_to_stream(event = TestDomainEvent.new, 'stream', :none)
23
41
  expect(repository.read_all_streams_forward(:head, 1).first).to eq(event)
24
42
  expect(repository.read_stream_events_forward('stream').first).to eq(event)
25
43
  expect(repository.read_stream_events_forward('other_stream')).to be_empty
26
44
  end
27
45
 
46
+ specify 'links an initial event to a new stream' do
47
+ skip unless test_link_events_to_stream
48
+ repository.
49
+ append_to_stream(event = TestDomainEvent.new, 'stream', :none).
50
+ link_to_stream(event.event_id, 'flow', :none)
51
+
52
+ expect(repository.read_all_streams_forward(:head, 1).first).to eq(event)
53
+ expect(repository.read_stream_events_forward('stream').first).to eq(event)
54
+ expect(repository.read_stream_events_forward('flow')).to eq([event])
55
+ expect(repository.read_stream_events_forward('other')).to be_empty
56
+ end
57
+
28
58
  specify 'adds multiple initial events to a new stream' do
29
59
  repository.append_to_stream([
30
60
  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -34,6 +64,19 @@ RSpec.shared_examples :event_repository do |repository_class|
34
64
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1])
35
65
  end
36
66
 
67
+ specify 'links multiple initial events to a new stream' do
68
+ skip unless test_link_events_to_stream
69
+ repository.append_to_stream([
70
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
71
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
72
+ ], 'stream', :none).link_to_stream([
73
+ event0.event_id,
74
+ event1.event_id,
75
+ ], 'flow', :none)
76
+ expect(repository.read_all_streams_forward(:head, 2)).to eq([event0, event1])
77
+ expect(repository.read_stream_events_forward('flow')).to eq([event0, event1])
78
+ end
79
+
37
80
  specify 'correct expected version on second write' do
38
81
  repository.append_to_stream([
39
82
  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -47,6 +90,22 @@ RSpec.shared_examples :event_repository do |repository_class|
47
90
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
48
91
  end
49
92
 
93
+ specify 'correct expected version on second link' do
94
+ skip unless test_link_events_to_stream
95
+ repository.append_to_stream([
96
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
97
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
98
+ ], 'stream', :none).append_to_stream([
99
+ event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
100
+ event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
101
+ ], 'flow', :none).link_to_stream([
102
+ event0.event_id,
103
+ event1.event_id,
104
+ ], 'flow', 1)
105
+ expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1, event2, event3])
106
+ expect(repository.read_stream_events_forward('flow')).to eq([event2, event3, event0, event1])
107
+ end
108
+
50
109
  specify 'incorrect expected version on second write' do
51
110
  repository.append_to_stream([
52
111
  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -63,6 +122,27 @@ RSpec.shared_examples :event_repository do |repository_class|
63
122
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1])
64
123
  end
65
124
 
125
+ specify 'incorrect expected version on second link' do
126
+ skip unless test_link_events_to_stream
127
+ repository.append_to_stream([
128
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
129
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
130
+ ], 'stream', :none)
131
+ repository.append_to_stream([
132
+ event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
133
+ event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
134
+ ], 'other', -1)
135
+ expect do
136
+ repository.link_to_stream([
137
+ event2.event_id,
138
+ event3.event_id,
139
+ ], 'stream', 0)
140
+ end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
141
+
142
+ expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1, event2, event3])
143
+ expect(repository.read_stream_events_forward('stream')).to eq([event0, event1])
144
+ end
145
+
66
146
  specify ':none on first and subsequent write' do
67
147
  repository.append_to_stream([
68
148
  eventA = TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -76,6 +156,22 @@ RSpec.shared_examples :event_repository do |repository_class|
76
156
  expect(repository.read_stream_events_forward('stream')).to eq([eventA])
77
157
  end
78
158
 
159
+ specify ':none on first and subsequent link' do
160
+ skip unless test_link_events_to_stream
161
+ repository.append_to_stream([
162
+ eventA = TestDomainEvent.new(event_id: SecureRandom.uuid),
163
+ eventB = TestDomainEvent.new(event_id: SecureRandom.uuid),
164
+ ], 'stream', :none)
165
+
166
+ repository.link_to_stream([eventA.event_id], 'flow', :none)
167
+ expect do
168
+ repository.link_to_stream([eventB.event_id], 'flow', :none)
169
+ end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
170
+
171
+ expect(repository.read_all_streams_forward(:head, 1)).to eq([eventA])
172
+ expect(repository.read_stream_events_forward('flow')).to eq([eventA])
173
+ end
174
+
79
175
  specify ':any allows stream with best-effort order and no guarantee' do
80
176
  repository.append_to_stream([
81
177
  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -89,6 +185,26 @@ RSpec.shared_examples :event_repository do |repository_class|
89
185
  expect(repository.read_stream_events_forward('stream').to_set).to eq(Set.new([event0, event1, event2, event3]))
90
186
  end
91
187
 
188
+ specify ':any allows linking in stream with best-effort order and no guarantee' do
189
+ skip unless test_link_events_to_stream
190
+ repository.append_to_stream([
191
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
192
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
193
+ event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
194
+ event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
195
+ ], 'stream', :any)
196
+
197
+ repository.link_to_stream([
198
+ event0.event_id, event1.event_id,
199
+ ], 'flow', :any)
200
+ repository.link_to_stream([
201
+ event2.event_id, event3.event_id,
202
+ ], 'flow', :any)
203
+
204
+ expect(repository.read_all_streams_forward(:head, 4).to_set).to eq(Set.new([event0, event1, event2, event3]))
205
+ expect(repository.read_stream_events_forward('flow').to_set).to eq(Set.new([event0, event1, event2, event3]))
206
+ end
207
+
92
208
  specify ':auto queries for last position in given stream' do
93
209
  skip unless test_expected_version_auto
94
210
  repository.append_to_stream([
@@ -106,6 +222,25 @@ RSpec.shared_examples :event_repository do |repository_class|
106
222
  ], 'stream', 1)
107
223
  end
108
224
 
225
+ specify ':auto queries for last position in given stream when linking' do
226
+ skip unless test_expected_version_auto
227
+ skip unless test_link_events_to_stream
228
+ repository.append_to_stream([
229
+ eventA = TestDomainEvent.new(event_id: SecureRandom.uuid),
230
+ eventB = TestDomainEvent.new(event_id: SecureRandom.uuid),
231
+ eventC = TestDomainEvent.new(event_id: SecureRandom.uuid),
232
+ ], 'another', :auto)
233
+ repository.append_to_stream([
234
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
235
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
236
+ ], 'stream', :auto)
237
+ repository.link_to_stream([
238
+ eventA.event_id,
239
+ eventB.event_id,
240
+ eventC.event_id,
241
+ ], 'stream', 1)
242
+ end
243
+
109
244
  specify ':auto starts from 0' do
110
245
  skip unless test_expected_version_auto
111
246
  repository.append_to_stream([
@@ -118,6 +253,22 @@ RSpec.shared_examples :event_repository do |repository_class|
118
253
  end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
119
254
  end
120
255
 
256
+ specify ':auto linking starts from 0' do
257
+ skip unless test_expected_version_auto
258
+ skip unless test_link_events_to_stream
259
+ repository.append_to_stream([
260
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
261
+ ], 'whatever', :auto)
262
+ repository.link_to_stream([
263
+ event0.event_id,
264
+ ], 'stream', :auto)
265
+ expect do
266
+ repository.append_to_stream([
267
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
268
+ ], 'stream', -1)
269
+ end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
270
+ end
271
+
121
272
  specify ':auto queries for last position and follows in incremental way' do
122
273
  skip unless test_expected_version_auto
123
274
  # It is expected that there is higher level lock
@@ -137,6 +288,28 @@ RSpec.shared_examples :event_repository do |repository_class|
137
288
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
138
289
  end
139
290
 
291
+ specify ':auto queries for last position and follows in incremental way when linking' do
292
+ skip unless test_expected_version_auto
293
+ skip unless test_link_events_to_stream
294
+ repository.append_to_stream([
295
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
296
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
297
+ event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
298
+ event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
299
+ ], 'stream', :auto)
300
+ repository.link_to_stream([
301
+ event0.event_id, event1.event_id,
302
+ ], 'flow', :auto)
303
+ repository.link_to_stream([
304
+ event2.event_id, event3.event_id,
305
+ ], 'flow', :auto)
306
+ expect(repository.read_all_streams_forward(:head, 4)).to eq([
307
+ event0, event1,
308
+ event2, event3
309
+ ])
310
+ expect(repository.read_stream_events_forward('flow')).to eq([event0, event1, event2, event3])
311
+ end
312
+
140
313
  specify ':auto is compatible with manual expectation' do
141
314
  skip unless test_expected_version_auto
142
315
  repository.append_to_stream([
@@ -151,6 +324,23 @@ RSpec.shared_examples :event_repository do |repository_class|
151
324
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
152
325
  end
153
326
 
327
+ specify ':auto is compatible with manual expectation when linking' do
328
+ skip unless test_expected_version_auto
329
+ skip unless test_link_events_to_stream
330
+ repository.append_to_stream([
331
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
332
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
333
+ ], 'stream', :auto)
334
+ repository.link_to_stream([
335
+ event0.event_id,
336
+ ], 'flow', :auto)
337
+ repository.link_to_stream([
338
+ event1.event_id,
339
+ ], 'flow', 0)
340
+ expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1,])
341
+ expect(repository.read_stream_events_forward('flow')).to eq([event0, event1,])
342
+ end
343
+
154
344
  specify 'manual is compatible with auto expectation' do
155
345
  skip unless test_expected_version_auto
156
346
  repository.append_to_stream([
@@ -165,6 +355,23 @@ RSpec.shared_examples :event_repository do |repository_class|
165
355
  expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
166
356
  end
167
357
 
358
+ specify 'manual is compatible with auto expectation when linking' do
359
+ skip unless test_expected_version_auto
360
+ skip unless test_link_events_to_stream
361
+ repository.append_to_stream([
362
+ event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
363
+ event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
364
+ ], 'stream', :auto)
365
+ repository.link_to_stream([
366
+ event0.event_id,
367
+ ], 'flow', :none)
368
+ repository.link_to_stream([
369
+ event1.event_id,
370
+ ], 'flow', :auto)
371
+ expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1])
372
+ expect(repository.read_stream_events_forward('flow')).to eq([event0, event1])
373
+ end
374
+
168
375
  specify 'unlimited concurrency for :any - everything should succeed' do
169
376
  skip unless test_race_conditions_any
170
377
  verify_conncurency_assumptions
@@ -204,6 +411,53 @@ RSpec.shared_examples :event_repository do |repository_class|
204
411
  end
205
412
  end
206
413
 
414
+ specify 'unlimited concurrency for :any - everything should succeed when linking' do
415
+ skip unless test_race_conditions_any
416
+ skip unless test_link_events_to_stream
417
+ verify_conncurency_assumptions
418
+ begin
419
+ concurrency_level = 4
420
+
421
+ fail_occurred = false
422
+ wait_for_it = true
423
+
424
+ concurrency_level.times.map do |i|
425
+ 100.times do |j|
426
+ eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
427
+ repository.append_to_stream([
428
+ TestDomainEvent.new(event_id: eid),
429
+ ], 'stream', :any)
430
+ end
431
+ end
432
+
433
+ threads = concurrency_level.times.map do |i|
434
+ Thread.new do
435
+ true while wait_for_it
436
+ begin
437
+ 100.times do |j|
438
+ eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
439
+ repository.link_to_stream(eid, 'flow', :any)
440
+ end
441
+ rescue RubyEventStore::WrongExpectedEventVersion
442
+ fail_occurred = true
443
+ end
444
+ end
445
+ end
446
+ wait_for_it = false
447
+ threads.each(&:join)
448
+ expect(fail_occurred).to eq(false)
449
+ expect(repository.read_stream_events_forward('flow').size).to eq(400)
450
+ events_in_stream = repository.read_stream_events_forward('flow')
451
+ expect(events_in_stream.size).to eq(400)
452
+ events0 = events_in_stream.select do |ev|
453
+ ev.event_id.start_with?("0-")
454
+ end
455
+ expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
456
+ ensure
457
+ cleanup_concurrency_test
458
+ end
459
+ end
460
+
207
461
  specify 'limited concurrency for :auto - some operations will fail without outside lock, stream is ordered' do
208
462
  skip unless test_expected_version_auto
209
463
  skip unless test_race_conditions_auto
@@ -246,6 +500,57 @@ RSpec.shared_examples :event_repository do |repository_class|
246
500
  end
247
501
  end
248
502
 
503
+ specify 'limited concurrency for :auto - some operations will fail without outside lock, stream is ordered' do
504
+ skip unless test_expected_version_auto
505
+ skip unless test_race_conditions_auto
506
+ skip unless test_link_events_to_stream
507
+
508
+ verify_conncurency_assumptions
509
+ begin
510
+ concurrency_level = 4
511
+
512
+ concurrency_level.times.map do |i|
513
+ 100.times do |j|
514
+ eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
515
+ repository.append_to_stream([
516
+ TestDomainEvent.new(event_id: eid),
517
+ ], 'whatever', :any)
518
+ end
519
+ end
520
+
521
+ fail_occurred = 0
522
+ wait_for_it = true
523
+
524
+ threads = concurrency_level.times.map do |i|
525
+ Thread.new do
526
+ true while wait_for_it
527
+ 100.times do |j|
528
+ begin
529
+ eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
530
+ repository.link_to_stream(eid, 'stream', :auto)
531
+ sleep(rand(concurrency_level) / 1000.0)
532
+ rescue RubyEventStore::WrongExpectedEventVersion
533
+ fail_occurred +=1
534
+ end
535
+ end
536
+ end
537
+ end
538
+ wait_for_it = false
539
+ threads.each(&:join)
540
+ expect(fail_occurred).to be > 0
541
+ events_in_stream = repository.read_stream_events_forward('stream')
542
+ expect(events_in_stream.size).to be < 400
543
+ expect(events_in_stream.size).to be >= 100
544
+ events0 = events_in_stream.select do |ev|
545
+ ev.event_id.start_with?("0-")
546
+ end
547
+ expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
548
+ additional_limited_concurrency_for_auto_check
549
+ ensure
550
+ cleanup_concurrency_test
551
+ end
552
+ end
553
+
249
554
  it 'appended event is stored in given stream' do
250
555
  expected_event = TestDomainEvent.new(data: {})
251
556
  repository.append_to_stream(expected_event, 'stream', :any)
@@ -268,6 +573,21 @@ RSpec.shared_examples :event_repository do |repository_class|
268
573
  expect(retrieved_event.metadata[:request_id]).to eq(3)
269
574
  end
270
575
 
576
+ it 'data and metadata attributes are retrieved when linking' do
577
+ skip unless test_link_events_to_stream
578
+ event = TestDomainEvent.new(
579
+ data: { order_id: 3 },
580
+ metadata: { request_id: 4 }
581
+ )
582
+ repository.
583
+ append_to_stream(event, 'stream', :any).
584
+ link_to_stream(event.event_id, 'flow', :any)
585
+ retrieved_event = repository.read_stream_events_forward('flow').first
586
+ expect(retrieved_event.metadata[:request_id]).to eq(4)
587
+ expect(retrieved_event.data[:order_id]).to eq(3)
588
+ expect(event).to eq(retrieved_event)
589
+ end
590
+
271
591
  it 'does not have deleted streams' do
272
592
  repository.append_to_stream(e1 = TestDomainEvent.new, 'stream', -1)
273
593
  repository.append_to_stream(e2 = TestDomainEvent.new, 'other_stream', -1)
@@ -278,6 +598,17 @@ RSpec.shared_examples :event_repository do |repository_class|
278
598
  expect(repository.read_all_streams_forward(:head, 10)).to eq([e1,e2])
279
599
  end
280
600
 
601
+ it 'does not have deleted streams with linked events' do
602
+ skip unless test_link_events_to_stream
603
+ repository.
604
+ append_to_stream(e1 = TestDomainEvent.new, 'stream', -1).
605
+ link_to_stream(e1.event_id, 'flow', -1)
606
+
607
+ repository.delete_stream('flow')
608
+ expect(repository.read_stream_events_forward('flow')).to be_empty
609
+ expect(repository.read_all_streams_forward(:head, 10)).to eq([e1])
610
+ end
611
+
281
612
  it 'has or has not domain event' do
282
613
  just_an_id = 'd5c134c2-db65-4e87-b6ea-d196f8f1a292'
283
614
  repository.append_to_stream(TestDomainEvent.new(event_id: just_an_id), 'stream', -1)
@@ -285,6 +616,10 @@ RSpec.shared_examples :event_repository do |repository_class|
285
616
  expect(repository.has_event?(just_an_id)).to be_truthy
286
617
  expect(repository.has_event?(just_an_id.clone)).to be_truthy
287
618
  expect(repository.has_event?('any other id')).to be_falsey
619
+
620
+ repository.delete_stream('stream')
621
+ expect(repository.has_event?(just_an_id)).to be_truthy
622
+ expect(repository.has_event?(just_an_id.clone)).to be_truthy
288
623
  end
289
624
 
290
625
  it 'knows last event in stream' do
@@ -295,6 +630,18 @@ RSpec.shared_examples :event_repository do |repository_class|
295
630
  expect(repository.last_stream_event('other_stream')).to be_nil
296
631
  end
297
632
 
633
+ it 'knows last event in stream when linked' do
634
+ skip unless test_link_events_to_stream
635
+ repository.append_to_stream([
636
+ e0 = TestDomainEvent.new(event_id: '00000000-0000-0000-0000-000000000001'),
637
+ e1 = TestDomainEvent.new(event_id: '00000000-0000-0000-0000-000000000002'),
638
+ ],
639
+ 'stream',
640
+ -1
641
+ ).link_to_stream([e1.event_id, e0.event_id], 'flow', -1)
642
+ expect(repository.last_stream_event('flow')).to eq(e0)
643
+ end
644
+
298
645
  it 'reads batch of events from stream forward & backward' do
299
646
  event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
300
647
  events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
@@ -315,20 +662,62 @@ RSpec.shared_examples :event_repository do |repository_class|
315
662
  expect(repository.read_events_backward('stream', events[4].event_id, 100)).to eq(events.first(4).reverse)
316
663
  end
317
664
 
665
+ it 'reads batch of linked events from stream forward & backward' do
666
+ skip unless test_link_events_to_stream
667
+ event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
668
+ events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
669
+ repository.append_to_stream(TestDomainEvent.new, 'other_stream', -1)
670
+ events.each.with_index do |event, index|
671
+ repository.
672
+ append_to_stream(event, 'stream', index - 1).
673
+ link_to_stream(event.event_id, 'flow', index - 1)
674
+ end
675
+ repository.append_to_stream(TestDomainEvent.new, 'other_stream', 0)
676
+
677
+ expect(repository.read_events_forward('flow', :head, 3)).to eq(events.first(3))
678
+ expect(repository.read_events_forward('flow', :head, 100)).to eq(events)
679
+ expect(repository.read_events_forward('flow', events[4].event_id, 4)).to eq(events[5..8])
680
+ expect(repository.read_events_forward('flow', events[4].event_id, 100)).to eq(events[5..9])
681
+
682
+ expect(repository.read_events_backward('flow', :head, 3)).to eq(events.last(3).reverse)
683
+ expect(repository.read_events_backward('flow', :head, 100)).to eq(events.reverse)
684
+ expect(repository.read_events_backward('flow', events[4].event_id, 4)).to eq(events.first(4).reverse)
685
+ expect(repository.read_events_backward('flow', events[4].event_id, 100)).to eq(events.first(4).reverse)
686
+ end
318
687
 
319
688
  it 'reads all stream events forward & backward' do
320
689
  s1 = 'stream'
321
690
  s2 = 'other_stream'
322
- repository.append_to_stream(a = TestDomainEvent.new(event_id: '7010d298-ab69-4bb1-9251-f3466b5d1282'), s1, -1)
323
- repository.append_to_stream(b = TestDomainEvent.new(event_id: '34f88aca-aaba-4ca0-9256-8017b47528c5'), s2, -1)
324
- repository.append_to_stream(c = TestDomainEvent.new(event_id: '8e61c864-ceae-4684-8726-97c34eb8fc4f'), s1, 0)
325
- repository.append_to_stream(d = TestDomainEvent.new(event_id: '30963ed9-6349-450b-ac9b-8ea50115b3bd'), s2, 0)
326
- repository.append_to_stream(e = TestDomainEvent.new(event_id: '5bdc58b7-e8a7-4621-afd6-ccb828d72457'), s2, 1)
691
+ repository.
692
+ append_to_stream(a = TestDomainEvent.new(event_id: '7010d298-ab69-4bb1-9251-f3466b5d1282'), s1, -1).
693
+ append_to_stream(b = TestDomainEvent.new(event_id: '34f88aca-aaba-4ca0-9256-8017b47528c5'), s2, -1).
694
+ append_to_stream(c = TestDomainEvent.new(event_id: '8e61c864-ceae-4684-8726-97c34eb8fc4f'), s1, 0).
695
+ append_to_stream(d = TestDomainEvent.new(event_id: '30963ed9-6349-450b-ac9b-8ea50115b3bd'), s2, 0).
696
+ append_to_stream(e = TestDomainEvent.new(event_id: '5bdc58b7-e8a7-4621-afd6-ccb828d72457'), s2, 1)
327
697
 
328
698
  expect(repository.read_stream_events_forward(s1)).to eq [a,c]
329
699
  expect(repository.read_stream_events_backward(s1)).to eq [c,a]
330
700
  end
331
701
 
702
+ it 'reads all stream linked events forward & backward' do
703
+ skip unless test_link_events_to_stream
704
+ s1, fs1, fs2 = 'stream', 'flow', 'other_flow'
705
+ repository.
706
+ append_to_stream(a = TestDomainEvent.new(event_id: '7010d298-ab69-4bb1-9251-f3466b5d1282'), s1, -1).
707
+ append_to_stream(b = TestDomainEvent.new(event_id: '34f88aca-aaba-4ca0-9256-8017b47528c5'), s1, 0).
708
+ append_to_stream(c = TestDomainEvent.new(event_id: '8e61c864-ceae-4684-8726-97c34eb8fc4f'), s1, 1).
709
+ append_to_stream(d = TestDomainEvent.new(event_id: '30963ed9-6349-450b-ac9b-8ea50115b3bd'), s1, 2).
710
+ append_to_stream(e = TestDomainEvent.new(event_id: '5bdc58b7-e8a7-4621-afd6-ccb828d72457'), s1, 3).
711
+ link_to_stream('7010d298-ab69-4bb1-9251-f3466b5d1282', fs1, -1).
712
+ link_to_stream('34f88aca-aaba-4ca0-9256-8017b47528c5', fs2, -1).
713
+ link_to_stream('8e61c864-ceae-4684-8726-97c34eb8fc4f', fs1, 0).
714
+ link_to_stream('30963ed9-6349-450b-ac9b-8ea50115b3bd', fs2, 0).
715
+ link_to_stream('5bdc58b7-e8a7-4621-afd6-ccb828d72457', fs2, 1)
716
+
717
+ expect(repository.read_stream_events_forward(fs1)).to eq [a,c]
718
+ expect(repository.read_stream_events_backward(fs1)).to eq [c,a]
719
+ end
720
+
332
721
  it 'reads batch of events from all streams forward & backward' do
333
722
  event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
334
723
  events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
@@ -347,6 +736,27 @@ RSpec.shared_examples :event_repository do |repository_class|
347
736
  expect(repository.read_all_streams_backward(events[4].event_id, 100)).to eq(events.first(4).reverse)
348
737
  end
349
738
 
739
+ it 'linked events do not affect reading from all streams - no duplicates' do
740
+ skip unless test_link_events_to_stream
741
+ event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
742
+ events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
743
+ events.each do |ev|
744
+ repository.
745
+ append_to_stream(ev, SecureRandom.uuid, -1).
746
+ link_to_stream(ev.event_id, SecureRandom.uuid, -1)
747
+ end
748
+
749
+ expect(repository.read_all_streams_forward(:head, 3)).to eq(events.first(3))
750
+ expect(repository.read_all_streams_forward(:head, 100)).to eq(events)
751
+ expect(repository.read_all_streams_forward(events[4].event_id, 4)).to eq(events[5..8])
752
+ expect(repository.read_all_streams_forward(events[4].event_id, 100)).to eq(events[5..9])
753
+
754
+ expect(repository.read_all_streams_backward(:head, 3)).to eq(events.last(3).reverse)
755
+ expect(repository.read_all_streams_backward(:head, 100)).to eq(events.reverse)
756
+ expect(repository.read_all_streams_backward(events[4].event_id, 4)).to eq(events.first(4).reverse)
757
+ expect(repository.read_all_streams_backward(events[4].event_id, 100)).to eq(events.first(4).reverse)
758
+ end
759
+
350
760
  it 'reads events different uuid object but same content' do
351
761
  event_ids = [
352
762
  "96c920b1-cdd0-40f4-907c-861b9fff7d02",
@@ -378,6 +788,33 @@ RSpec.shared_examples :event_repository do |repository_class|
378
788
  end.to raise_error(RubyEventStore::EventDuplicatedInStream)
379
789
  end
380
790
 
791
+ it 'does not allow same event twice' do
792
+ repository.append_to_stream(
793
+ TestDomainEvent.new(event_id: "a1b49edb-7636-416f-874a-88f94b859bef"),
794
+ 'stream',
795
+ -1
796
+ )
797
+ expect do
798
+ repository.append_to_stream(
799
+ TestDomainEvent.new(event_id: "a1b49edb-7636-416f-874a-88f94b859bef"),
800
+ 'another',
801
+ -1
802
+ )
803
+ end.to raise_error(RubyEventStore::EventDuplicatedInStream)
804
+ end
805
+
806
+ it 'does not allow linking same event twice in a stream' do
807
+ skip unless test_link_events_to_stream
808
+ repository.append_to_stream([
809
+ TestDomainEvent.new(event_id: "a1b49edb-7636-416f-874a-88f94b859bef"),
810
+ ], 'stream',
811
+ -1
812
+ ).link_to_stream("a1b49edb-7636-416f-874a-88f94b859bef", 'flow', -1)
813
+ expect do
814
+ repository.link_to_stream("a1b49edb-7636-416f-874a-88f94b859bef", 'flow', 0)
815
+ end.to raise_error(RubyEventStore::EventDuplicatedInStream)
816
+ end
817
+
381
818
  it 'allows appending to GLOBAL_STREAM explicitly' do
382
819
  event = TestDomainEvent.new(event_id: "df8b2ba3-4e2c-4888-8d14-4364855fa80e")
383
820
  repository.append_to_stream(event, "all", :any)
@@ -418,6 +855,20 @@ RSpec.shared_examples :event_repository do |repository_class|
418
855
  end
419
856
  end
420
857
 
858
+ specify "only :none, :any, :auto and Integer allowed as expected_version when linking" do
859
+ skip unless test_link_events_to_stream
860
+ [Object.new, SecureRandom.uuid, :foo].each do |invalid_expected_version|
861
+ repository.append_to_stream(
862
+ TestDomainEvent.new(event_id: evid = SecureRandom.uuid),
863
+ SecureRandom.uuid,
864
+ -1
865
+ )
866
+ expect {
867
+ repository.link_to_stream(evid, SecureRandom.uuid, invalid_expected_version)
868
+ }.to raise_error(RubyEventStore::InvalidExpectedVersion)
869
+ end
870
+ end
871
+
421
872
  specify "events not persisted if append failed" do
422
873
  repository.append_to_stream([
423
874
  TestDomainEvent.new(event_id: SecureRandom.uuid),
@@ -444,14 +895,33 @@ RSpec.shared_examples :event_repository do |repository_class|
444
895
  end
445
896
 
446
897
  specify 'reading particular event' do
447
- test_event = TestDomainEvent.new
448
- repository.append_to_stream(TestDomainEvent.new, "test", -1)
449
- repository.append_to_stream(test_event, "test", 0)
898
+ test_event = TestDomainEvent.new(event_id: "941cd8f5-b3f9-47af-b4e4-07f8cea37467")
899
+ repository.
900
+ append_to_stream(TestDomainEvent.new, "test", -1).
901
+ append_to_stream(test_event, "test", 0)
450
902
 
451
- expect(repository.read_event(test_event.event_id)).to eq(test_event)
903
+ expect(repository.read_event("941cd8f5-b3f9-47af-b4e4-07f8cea37467")).to eq(test_event)
452
904
  end
453
905
 
454
906
  specify 'reading non-existent event' do
455
- expect{repository.read_event('72922e65-1b32-4e97-8023-03ae81dd3a27')}.to raise_error(RubyEventStore::EventNotFound)
907
+ expect do
908
+ repository.read_event('72922e65-1b32-4e97-8023-03ae81dd3a27')
909
+ end.to raise_error do |err|
910
+ expect(err).to be_a(RubyEventStore::EventNotFound)
911
+ expect(err.event_id).to eq('72922e65-1b32-4e97-8023-03ae81dd3a27')
912
+ expect(err.message).to eq('Event not found: 72922e65-1b32-4e97-8023-03ae81dd3a27')
913
+ end
914
+ end
915
+
916
+ specify 'linking non-existent event' do
917
+ skip unless test_link_events_to_stream
918
+ expect do
919
+ repository.link_to_stream('72922e65-1b32-4e97-8023-03ae81dd3a27', "flow", -1)
920
+ end.to raise_error do |err|
921
+ expect(err).to be_a(RubyEventStore::EventNotFound)
922
+ expect(err.event_id).to eq('72922e65-1b32-4e97-8023-03ae81dd3a27')
923
+ expect(err.message).to eq('Event not found: 72922e65-1b32-4e97-8023-03ae81dd3a27')
924
+ end
456
925
  end
926
+
457
927
  end
@@ -1,3 +1,3 @@
1
1
  module RubyEventStore
2
- VERSION = "0.21.0"
2
+ VERSION = "0.22.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_event_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkency
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-22 00:00:00.000000000 Z
11
+ date: 2018-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  version: '0'
130
130
  requirements: []
131
131
  rubyforge_project:
132
- rubygems_version: 2.6.11
132
+ rubygems_version: 2.6.13
133
133
  signing_key:
134
134
  specification_version: 4
135
135
  summary: Event Store in Ruby