ruby_cqrs 0.2.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 +7 -0
- data/License.txt +21 -0
- data/README.md +33 -0
- data/lib/ruby_cqrs/data/event_store.rb +13 -0
- data/lib/ruby_cqrs/data/in_memory_event_store.rb +70 -0
- data/lib/ruby_cqrs/data/serialization.rb +30 -0
- data/lib/ruby_cqrs/domain/aggregate.rb +107 -0
- data/lib/ruby_cqrs/domain/aggregate_repository.rb +122 -0
- data/lib/ruby_cqrs/domain/event.rb +10 -0
- data/lib/ruby_cqrs/domain/snapshot.rb +9 -0
- data/lib/ruby_cqrs/domain/snapshotable.rb +48 -0
- data/lib/ruby_cqrs/error.rb +3 -0
- data/lib/ruby_cqrs/guid.rb +13 -0
- data/lib/ruby_cqrs/version.rb +3 -0
- data/lib/ruby_cqrs.rb +12 -0
- data/spec/feature/basic_usage_spec.rb +177 -0
- data/spec/feature/snapshot_spec.rb +216 -0
- data/spec/fixture/typical_domain.rb +182 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/matchers.rb +8 -0
- data/spec/unit/aggregate_repository_spec.rb +332 -0
- data/spec/unit/aggregate_spec.rb +123 -0
- data/spec/unit/in_memory_event_store_spec.rb +404 -0
- data/spec/unit/serialization_spec.rb +52 -0
- metadata +120 -0
@@ -0,0 +1,404 @@
|
|
1
|
+
require_relative('../spec_helper')
|
2
|
+
|
3
|
+
describe RubyCqrs::Data::InMemoryEventStore do
|
4
|
+
let(:command_context) {}
|
5
|
+
let(:event_store) { RubyCqrs::Data::InMemoryEventStore.new }
|
6
|
+
let(:repository) { RubyCqrs::Domain::AggregateRepository.new\
|
7
|
+
event_store, command_context }
|
8
|
+
let(:aggregate_type) { SomeDomain::AggregateRoot }
|
9
|
+
let(:aggregate_type_str) { SomeDomain::AggregateRoot.to_s }
|
10
|
+
# an aggregate has a SNAPSHOT_THRESHOLD of 45
|
11
|
+
let(:aggregate_s_45_type) { SomeDomain::AggregateRoot45Snapshot }
|
12
|
+
let(:aggregate_s_45_type_str) { SomeDomain::AggregateRoot45Snapshot.to_s }
|
13
|
+
|
14
|
+
describe '#load & #save' do
|
15
|
+
it 'saves a new aggregate then is able to load the correct data back' do
|
16
|
+
new_aggregate = aggregate_type.new
|
17
|
+
new_aggregate.test_fire
|
18
|
+
new_aggregate.test_fire_ag
|
19
|
+
repository.save new_aggregate
|
20
|
+
|
21
|
+
state = event_store.load_by new_aggregate.aggregate_id, command_context
|
22
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
23
|
+
expect(state[:events].size).to eq(2)
|
24
|
+
expect(state[:events][0][:version]).to eq(1)
|
25
|
+
expect(state[:events][0][:aggregate_id]).to eq(new_aggregate.aggregate_id)
|
26
|
+
expect(state[:events][1][:version]).to eq(2)
|
27
|
+
expect(state[:events][1][:aggregate_id]).to eq(new_aggregate.aggregate_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'saves an existing aggregate then is able to load the correct data back' do
|
31
|
+
new_aggregate = aggregate_type.new
|
32
|
+
new_aggregate.test_fire
|
33
|
+
new_aggregate.test_fire_ag
|
34
|
+
repository.save new_aggregate
|
35
|
+
old_aggregate = repository.find_by new_aggregate.aggregate_id
|
36
|
+
old_aggregate.test_fire
|
37
|
+
old_aggregate.test_fire_ag
|
38
|
+
repository.save old_aggregate
|
39
|
+
state = event_store.load_by old_aggregate.aggregate_id, command_context
|
40
|
+
|
41
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
42
|
+
expect(state[:events].size).to eq(4)
|
43
|
+
expect(state[:events][0][:version]).to eq(1)
|
44
|
+
expect(state[:events][0][:aggregate_id]).to eq(old_aggregate.aggregate_id)
|
45
|
+
expect(state[:events][1][:version]).to eq(2)
|
46
|
+
expect(state[:events][1][:aggregate_id]).to eq(old_aggregate.aggregate_id)
|
47
|
+
expect(state[:events][2][:version]).to eq(3)
|
48
|
+
expect(state[:events][2][:aggregate_id]).to eq(old_aggregate.aggregate_id)
|
49
|
+
expect(state[:events][3][:version]).to eq(4)
|
50
|
+
expect(state[:events][3][:aggregate_id]).to eq(old_aggregate.aggregate_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "receives correct input on #save with 1 snapshot taken when enough events get fired" do
|
54
|
+
aggregate = aggregate_type.new
|
55
|
+
expect(event_store).to receive(:save)\
|
56
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
57
|
+
(1..30).each { |x| aggregate.test_fire }
|
58
|
+
repository.save aggregate
|
59
|
+
|
60
|
+
aggregate = aggregate_s_45_type.new
|
61
|
+
expect(event_store).to receive(:save)\
|
62
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
63
|
+
(1..45).each { |x| aggregate.test_fire }
|
64
|
+
repository.save aggregate
|
65
|
+
end
|
66
|
+
|
67
|
+
it "receives correct input on #save with 1 snapshot taken when enough events get fired, also test result" do
|
68
|
+
aggregate = aggregate_type.new
|
69
|
+
(1..30).each { |x| aggregate.test_fire }
|
70
|
+
repository.save aggregate
|
71
|
+
|
72
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
73
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
74
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
75
|
+
expect(state[:events].size).to eq(0)
|
76
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
77
|
+
expect(state[:snapshot][:version]).to be(30)
|
78
|
+
|
79
|
+
aggregate = aggregate_s_45_type.new
|
80
|
+
(1..45).each { |x| aggregate.test_fire }
|
81
|
+
repository.save aggregate
|
82
|
+
|
83
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
84
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
85
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
86
|
+
expect(state[:events].size).to eq(0)
|
87
|
+
expect(state[:snapshot]).to_not be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it "receives correct input on #save with no snapshot taken when not enough events get fired" do
|
91
|
+
aggregate = aggregate_type.new
|
92
|
+
expect(event_store).to receive(:save)\
|
93
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
94
|
+
(1..29).each { |x| aggregate.test_fire }
|
95
|
+
repository.save aggregate
|
96
|
+
|
97
|
+
aggregate = aggregate_s_45_type.new
|
98
|
+
expect(event_store).to receive(:save)\
|
99
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
100
|
+
(1..44).each { |x| aggregate.test_fire }
|
101
|
+
repository.save aggregate
|
102
|
+
end
|
103
|
+
|
104
|
+
it "receives correct input on #save with no snapshot taken when not enough events get fired, also test result" do
|
105
|
+
aggregate = aggregate_type.new
|
106
|
+
(1..29).each { |x| aggregate.test_fire }
|
107
|
+
repository.save aggregate
|
108
|
+
|
109
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
110
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
111
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
112
|
+
expect(state[:events].size).to eq(29)
|
113
|
+
expect(state[:snapshot]).to be_nil
|
114
|
+
|
115
|
+
aggregate = aggregate_s_45_type.new
|
116
|
+
(1..44).each { |x| aggregate.test_fire }
|
117
|
+
repository.save aggregate
|
118
|
+
|
119
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
120
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
121
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
122
|
+
expect(state[:events].size).to eq(44)
|
123
|
+
expect(state[:snapshot]).to be_nil
|
124
|
+
end
|
125
|
+
|
126
|
+
it "receives correct input on #save incrementally and eventually triggers a snapshot" do
|
127
|
+
aggregate = aggregate_type.new
|
128
|
+
|
129
|
+
expect(event_store).to receive(:save)\
|
130
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
131
|
+
(1..15).each { |x| aggregate.test_fire }
|
132
|
+
repository.save aggregate
|
133
|
+
|
134
|
+
expect(event_store).to receive(:save)\
|
135
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
136
|
+
(1..15).each { |x| aggregate.test_fire }
|
137
|
+
repository.save aggregate
|
138
|
+
|
139
|
+
aggregate = aggregate_s_45_type.new
|
140
|
+
|
141
|
+
expect(event_store).to receive(:save)\
|
142
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
143
|
+
(1..30).each { |x| aggregate.test_fire }
|
144
|
+
repository.save aggregate
|
145
|
+
|
146
|
+
expect(event_store).to receive(:save)\
|
147
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
148
|
+
(1..15).each { |x| aggregate.test_fire }
|
149
|
+
repository.save aggregate
|
150
|
+
end
|
151
|
+
|
152
|
+
it "receives correct input on #save incrementally and eventually triggers a snapshot, also test result" do
|
153
|
+
aggregate = aggregate_type.new
|
154
|
+
(1..15).each { |x| aggregate.test_fire }
|
155
|
+
repository.save aggregate
|
156
|
+
(1..15).each { |x| aggregate.test_fire }
|
157
|
+
repository.save aggregate
|
158
|
+
|
159
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
160
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
161
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
162
|
+
expect(state[:events].size).to eq(0)
|
163
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
164
|
+
expect(state[:snapshot][:version]).to be(30)
|
165
|
+
|
166
|
+
aggregate = aggregate_s_45_type.new
|
167
|
+
(1..30).each { |x| aggregate.test_fire }
|
168
|
+
repository.save aggregate
|
169
|
+
(1..15).each { |x| aggregate.test_fire }
|
170
|
+
repository.save aggregate
|
171
|
+
|
172
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
173
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
174
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
175
|
+
expect(state[:events].size).to eq(0)
|
176
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
177
|
+
expect(state[:snapshot][:version]).to be(45)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "receives correct input on #save with 1 snapshot taken when more than enough events get fired" do
|
181
|
+
aggregate = aggregate_type.new
|
182
|
+
|
183
|
+
expect(event_store).to receive(:save)\
|
184
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
185
|
+
(1..45).each { |x| aggregate.test_fire }
|
186
|
+
repository.save aggregate
|
187
|
+
|
188
|
+
aggregate = aggregate_s_45_type.new
|
189
|
+
|
190
|
+
expect(event_store).to receive(:save)\
|
191
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
192
|
+
(1..60).each { |x| aggregate.test_fire }
|
193
|
+
repository.save aggregate
|
194
|
+
end
|
195
|
+
|
196
|
+
it "receives correct input on #save with 1 snapshot taken when more than enough events get fired, also test result" do
|
197
|
+
aggregate = aggregate_type.new
|
198
|
+
(1..45).each { |x| aggregate.test_fire }
|
199
|
+
repository.save aggregate
|
200
|
+
|
201
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
202
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
203
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
204
|
+
expect(state[:events].size).to eq(0)
|
205
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
206
|
+
expect(state[:snapshot][:version]).to be(45)
|
207
|
+
|
208
|
+
aggregate = aggregate_s_45_type.new
|
209
|
+
(1..60).each { |x| aggregate.test_fire }
|
210
|
+
repository.save aggregate
|
211
|
+
|
212
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
213
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
214
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
215
|
+
expect(state[:events].size).to eq(0)
|
216
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
217
|
+
expect(state[:snapshot][:version]).to be(60)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "receives correct input on #save with 1 snapshot taken when enough events get fired, twice" do
|
221
|
+
aggregate = aggregate_type.new
|
222
|
+
|
223
|
+
expect(event_store).to receive(:save)\
|
224
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
225
|
+
(1..30).each { |x| aggregate.test_fire }
|
226
|
+
repository.save aggregate
|
227
|
+
|
228
|
+
expect(event_store).to receive(:save)\
|
229
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
230
|
+
(1..30).each { |x| aggregate.test_fire }
|
231
|
+
repository.save aggregate
|
232
|
+
|
233
|
+
aggregate = aggregate_s_45_type.new
|
234
|
+
|
235
|
+
expect(event_store).to receive(:save)\
|
236
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
237
|
+
(1..45).each { |x| aggregate.test_fire }
|
238
|
+
repository.save aggregate
|
239
|
+
|
240
|
+
expect(event_store).to receive(:save)\
|
241
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
242
|
+
(1..45).each { |x| aggregate.test_fire }
|
243
|
+
repository.save aggregate
|
244
|
+
end
|
245
|
+
|
246
|
+
it "receives correct input on #save with 1 snapshot taken when enough events get fired, twice, also test result" do
|
247
|
+
aggregate = aggregate_type.new
|
248
|
+
(1..30).each { |x| aggregate.test_fire }
|
249
|
+
repository.save aggregate
|
250
|
+
(1..30).each { |x| aggregate.test_fire }
|
251
|
+
repository.save aggregate
|
252
|
+
|
253
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
254
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
255
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
256
|
+
expect(state[:events].size).to eq(0)
|
257
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
258
|
+
expect(state[:snapshot][:version]).to be(60)
|
259
|
+
|
260
|
+
aggregate = aggregate_s_45_type.new
|
261
|
+
(1..45).each { |x| aggregate.test_fire }
|
262
|
+
repository.save aggregate
|
263
|
+
(1..45).each { |x| aggregate.test_fire }
|
264
|
+
repository.save aggregate
|
265
|
+
|
266
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
267
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
268
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
269
|
+
expect(state[:events].size).to eq(0)
|
270
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
271
|
+
expect(state[:snapshot][:version]).to be(90)
|
272
|
+
end
|
273
|
+
|
274
|
+
it "receives correct input on #save incrementally and eventually triggers two snapshots" do
|
275
|
+
aggregate = aggregate_type.new
|
276
|
+
|
277
|
+
expect(event_store).to receive(:save)\
|
278
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
279
|
+
(1..30).each { |x| aggregate.test_fire }
|
280
|
+
repository.save aggregate
|
281
|
+
|
282
|
+
expect(event_store).to receive(:save)\
|
283
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
284
|
+
(1..15).each { |x| aggregate.test_fire }
|
285
|
+
repository.save aggregate
|
286
|
+
|
287
|
+
expect(event_store).to receive(:save)\
|
288
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
289
|
+
(1..15).each { |x| aggregate.test_fire }
|
290
|
+
repository.save aggregate
|
291
|
+
|
292
|
+
expect(event_store).to receive(:save)\
|
293
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
294
|
+
(1..15).each { |x| aggregate.test_fire }
|
295
|
+
repository.save aggregate
|
296
|
+
|
297
|
+
aggregate = aggregate_s_45_type.new
|
298
|
+
|
299
|
+
expect(event_store).to receive(:save)\
|
300
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
301
|
+
(1..30).each { |x| aggregate.test_fire }
|
302
|
+
repository.save aggregate
|
303
|
+
|
304
|
+
expect(event_store).to receive(:save)\
|
305
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
306
|
+
(1..30).each { |x| aggregate.test_fire }
|
307
|
+
repository.save aggregate
|
308
|
+
|
309
|
+
expect(event_store).to receive(:save)\
|
310
|
+
{ |changes, context| expect(changes[0][:snapshot]).to be_nil }
|
311
|
+
(1..30).each { |x| aggregate.test_fire }
|
312
|
+
repository.save aggregate
|
313
|
+
|
314
|
+
expect(event_store).to receive(:save)\
|
315
|
+
{ |changes, context| expect(changes[0][:snapshot]).to_not be_nil }
|
316
|
+
(1..15).each { |x| aggregate.test_fire }
|
317
|
+
repository.save aggregate
|
318
|
+
end
|
319
|
+
|
320
|
+
it "receives correct input on #save incrementally and eventually triggers two snapshots, also test result" do
|
321
|
+
aggregate = aggregate_type.new
|
322
|
+
(1..30).each { |x| aggregate.test_fire }
|
323
|
+
repository.save aggregate
|
324
|
+
(1..15).each { |x| aggregate.test_fire }
|
325
|
+
repository.save aggregate
|
326
|
+
(1..15).each { |x| aggregate.test_fire }
|
327
|
+
repository.save aggregate
|
328
|
+
(1..15).each { |x| aggregate.test_fire }
|
329
|
+
repository.save aggregate
|
330
|
+
|
331
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
332
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
333
|
+
expect(state[:aggregate_type]).to eq(aggregate_type_str)
|
334
|
+
expect(state[:events].size).to eq(15)
|
335
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
336
|
+
expect(state[:snapshot][:version]).to be(60)
|
337
|
+
|
338
|
+
aggregate = aggregate_s_45_type.new
|
339
|
+
(1..30).each { |x| aggregate.test_fire }
|
340
|
+
repository.save aggregate
|
341
|
+
(1..30).each { |x| aggregate.test_fire }
|
342
|
+
repository.save aggregate
|
343
|
+
(1..30).each { |x| aggregate.test_fire }
|
344
|
+
repository.save aggregate
|
345
|
+
(1..15).each { |x| aggregate.test_fire }
|
346
|
+
repository.save aggregate
|
347
|
+
|
348
|
+
state = event_store.load_by aggregate.aggregate_id, command_context
|
349
|
+
expect(state[:aggregate_id]).to eq(aggregate.aggregate_id)
|
350
|
+
expect(state[:aggregate_type]).to eq(aggregate_s_45_type_str)
|
351
|
+
expect(state[:events].size).to eq(0)
|
352
|
+
expect(state[:snapshot][:data]).to_not be_nil
|
353
|
+
expect(state[:snapshot][:version]).to be(105)
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'raises concurrency error when two instances of the same aggregate try to compete with each other' do
|
357
|
+
# obtain two instances of the same aggregate
|
358
|
+
new_aggregate = aggregate_type.new
|
359
|
+
new_aggregate.test_fire
|
360
|
+
new_aggregate.test_fire_ag
|
361
|
+
repository.save new_aggregate
|
362
|
+
instance_1 = new_aggregate
|
363
|
+
instance_2 = repository.find_by new_aggregate.aggregate_id
|
364
|
+
|
365
|
+
# now both instances try to compete with each other
|
366
|
+
instance_1.test_fire
|
367
|
+
instance_2.test_fire
|
368
|
+
|
369
|
+
# only one instance of the two could be saved successfully
|
370
|
+
repository.save instance_1
|
371
|
+
expect(instance_1.version).to eq(instance_1.instance_variable_get(:@source_version))
|
372
|
+
expect{ repository.save instance_2 }.to raise_error(RubyCqrs::AggregateConcurrencyError)
|
373
|
+
expect(instance_2.version).to_not eq(instance_2.instance_variable_get(:@source_version))
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'makes sure no state could be persisted if any error occured during the save process' do
|
377
|
+
# obtain two instances of the same aggregate
|
378
|
+
instance_1 = aggregate_type.new
|
379
|
+
instance_1.test_fire
|
380
|
+
instance_1.test_fire_ag
|
381
|
+
repository.save instance_1
|
382
|
+
instance_2 = repository.find_by instance_1.aggregate_id
|
383
|
+
|
384
|
+
# now both instances try to compete with each other
|
385
|
+
instance_1.test_fire
|
386
|
+
instance_2.test_fire
|
387
|
+
|
388
|
+
# instance 1 wins the save
|
389
|
+
repository.save instance_1
|
390
|
+
|
391
|
+
# instance 3 has done its work and ready to be persisted
|
392
|
+
instance_3 = aggregate_type.new
|
393
|
+
instance_3.test_fire
|
394
|
+
|
395
|
+
# when trying to save instance 2 and 3 together, instance 2 would trigger an error
|
396
|
+
# so both instance 2 and 3 could not be persisted
|
397
|
+
expect{ repository.save [ instance_3, instance_2 ] }.to\
|
398
|
+
raise_error(RubyCqrs::AggregateConcurrencyError)
|
399
|
+
expect(instance_1.version).to eq(instance_1.instance_variable_get(:@source_version))
|
400
|
+
expect(instance_2.version).to_not eq(instance_2.instance_variable_get(:@source_version))
|
401
|
+
expect(instance_3.version).to_not eq(instance_3.instance_variable_get(:@source_version))
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative('../spec_helper')
|
2
|
+
describe 'decoder & encoder' do
|
3
|
+
let(:unsupported_obj) { SomeDomain::FifthEvent.new }
|
4
|
+
let(:unsupported_obj_type) { SomeDomain::FifthEvent.name }
|
5
|
+
let(:supported_obj) { SomeDomain::THIRD_EVENT_INSTANCE }
|
6
|
+
let(:supported_obj_type) { SomeDomain::THIRD_EVENT_INSTANCE.class.name }
|
7
|
+
|
8
|
+
describe RubyCqrs::Data::Encodable do
|
9
|
+
describe '#try_encode' do
|
10
|
+
it 'encodes a supported object into string' do
|
11
|
+
encoded_message = supported_obj.try_encode
|
12
|
+
expect(encoded_message.class.name).to eq('String')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'raises an ObjectNotEncodableError when trying to encode an unsupported object' do
|
16
|
+
expect{ unsupported_obj.try_encode }.to raise_error(RubyCqrs::ObjectNotEncodableError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe RubyCqrs::Data::Decodable do
|
22
|
+
let(:decoder_type) { Class.new { include RubyCqrs::Data::Decodable } }
|
23
|
+
let(:decoder) { decoder_type.new }
|
24
|
+
|
25
|
+
describe '#try_decode' do
|
26
|
+
let(:unsupported_record) {
|
27
|
+
{ :object_type => unsupported_obj_type,
|
28
|
+
:data => unsupported_obj } }
|
29
|
+
let(:supported_record) {
|
30
|
+
{ :object_type => supported_obj_type,
|
31
|
+
:data => supported_obj.try_encode } }
|
32
|
+
|
33
|
+
it 'decodes a supported object type from string' do
|
34
|
+
decoded_object = decoder.try_decode(supported_record[:object_type],\
|
35
|
+
supported_record[:data])
|
36
|
+
expect(decoded_object.class.name).to eq(supported_obj_type)
|
37
|
+
# specific test value
|
38
|
+
expect(decoded_object.id).to eq(1)
|
39
|
+
expect(decoded_object.name).to eq('some dude')
|
40
|
+
expect(decoded_object.phone).to eq('13322244444')
|
41
|
+
expect(decoded_object.note).to eq('good luck')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises an ObjectNotDecodableError when trying to decode an unsupported object' do
|
45
|
+
expect {
|
46
|
+
decoder.try_decode unsupported_record[:object_type],\
|
47
|
+
unsupported_record[:data]
|
48
|
+
}.to raise_error(RubyCqrs::ObjectNotDecodableError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_cqrs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Raven Chen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uuidtools
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.1.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.1.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: beefcake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
description: a ruby implementation of cqrs, using event sourcing
|
56
|
+
email:
|
57
|
+
- ravenchen.cn@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.md
|
62
|
+
files:
|
63
|
+
- License.txt
|
64
|
+
- README.md
|
65
|
+
- lib/ruby_cqrs.rb
|
66
|
+
- lib/ruby_cqrs/data/event_store.rb
|
67
|
+
- lib/ruby_cqrs/data/in_memory_event_store.rb
|
68
|
+
- lib/ruby_cqrs/data/serialization.rb
|
69
|
+
- lib/ruby_cqrs/domain/aggregate.rb
|
70
|
+
- lib/ruby_cqrs/domain/aggregate_repository.rb
|
71
|
+
- lib/ruby_cqrs/domain/event.rb
|
72
|
+
- lib/ruby_cqrs/domain/snapshot.rb
|
73
|
+
- lib/ruby_cqrs/domain/snapshotable.rb
|
74
|
+
- lib/ruby_cqrs/error.rb
|
75
|
+
- lib/ruby_cqrs/guid.rb
|
76
|
+
- lib/ruby_cqrs/version.rb
|
77
|
+
- spec/feature/basic_usage_spec.rb
|
78
|
+
- spec/feature/snapshot_spec.rb
|
79
|
+
- spec/fixture/typical_domain.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/support/matchers.rb
|
82
|
+
- spec/unit/aggregate_repository_spec.rb
|
83
|
+
- spec/unit/aggregate_spec.rb
|
84
|
+
- spec/unit/in_memory_event_store_spec.rb
|
85
|
+
- spec/unit/serialization_spec.rb
|
86
|
+
homepage: https://github.com/iravench/ruby_cqrs
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options:
|
92
|
+
- "--charset=UTF-8"
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.4.6
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: ruby_cqrs-0.2.0
|
111
|
+
test_files:
|
112
|
+
- spec/feature/basic_usage_spec.rb
|
113
|
+
- spec/feature/snapshot_spec.rb
|
114
|
+
- spec/fixture/typical_domain.rb
|
115
|
+
- spec/spec_helper.rb
|
116
|
+
- spec/support/matchers.rb
|
117
|
+
- spec/unit/aggregate_repository_spec.rb
|
118
|
+
- spec/unit/aggregate_spec.rb
|
119
|
+
- spec/unit/in_memory_event_store_spec.rb
|
120
|
+
- spec/unit/serialization_spec.rb
|