ruby_event_store 0.21.0 → 0.22.0

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