event_store 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/Rakefile +15 -0
- data/db/database.yml +41 -0
- data/db/event_store_db_designer_common_queries.sql +4 -0
- data/db/event_store_db_designer_event_data.sql +3585 -0
- data/db/event_store_sample_data_generator.rb +30 -0
- data/db/migrations/001_create_event_store_events.rb +60 -0
- data/db/pg_migrations/001_create_event_store_events.rb +17 -0
- data/event_store.gemspec +34 -0
- data/lib/event_store.rb +113 -0
- data/lib/event_store/aggregate.rb +72 -0
- data/lib/event_store/client.rb +87 -0
- data/lib/event_store/errors.rb +4 -0
- data/lib/event_store/event_appender.rb +83 -0
- data/lib/event_store/time_hacker.rb +16 -0
- data/lib/event_store/version.rb +3 -0
- data/spec/benchmark/bench.rb +34 -0
- data/spec/benchmark/memory_profile.rb +48 -0
- data/spec/benchmark/seed_db.rb +45 -0
- data/spec/event_store/client_spec.rb +287 -0
- data/spec/event_store/snapshot_spec.rb +45 -0
- data/spec/event_store/vertica guy notes.txt +23 -0
- data/spec/spec_helper.rb +28 -0
- metadata +276 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module EventStore
|
2
|
+
class TimeHacker
|
3
|
+
class << self
|
4
|
+
#Hack around various DB adapters that hydrate dates from the db into the local ruby timezone
|
5
|
+
def translate_occurred_at_from_local_to_gmt(occurred_at)
|
6
|
+
if occurred_at.class == Time
|
7
|
+
#expecting "2001-02-03 01:26:40 -0700"
|
8
|
+
Time.parse(occurred_at.to_s.gsub(/\s[+-]\d+$/, ' UTC'))
|
9
|
+
elsif occurred_at.class == DateTime
|
10
|
+
#expecting "2001-02-03T01:26:40+00:00"
|
11
|
+
Time.parse(occurred_at.iso8601.gsub('T', ' ').gsub(/[+-]\d{2}\:\d{2}/, ' UTC'))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'event_store'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
# db_config = Hash[
|
5
|
+
# :username => 'nexia',
|
6
|
+
# :password => 'Password1',
|
7
|
+
# host: 'ec2-54-221-80-232.compute-1.amazonaws.com',
|
8
|
+
# encoding: 'utf8',
|
9
|
+
# pool: 1000,
|
10
|
+
# adapter: :postgres,
|
11
|
+
# database: 'event_store_performance'
|
12
|
+
# ]
|
13
|
+
# EventStore.connect ( db_config )
|
14
|
+
EventStore.connect :adapter => :postgres, :database => 'event_store_performance', host: 'localhost'
|
15
|
+
EventStore.redis_connect host: 'localhost'
|
16
|
+
|
17
|
+
ITERATIONS = 1000
|
18
|
+
|
19
|
+
Benchmark.bmbm do |x|
|
20
|
+
x.report "Time to read #{ITERATIONS} Event Snapshots" do
|
21
|
+
ITERATIONS.times do
|
22
|
+
EventStore::Client.new(rand(200) + 1, :device).snapshot
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Benchmark.bmbm do |x|
|
28
|
+
x.report "Time to read #{ITERATIONS} Event Streams" do
|
29
|
+
ITERATIONS.times do
|
30
|
+
EventStore::Client.new(rand(200) + 1, :device).event_stream
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
lib = File.expand_path('../../../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'event_store'
|
5
|
+
|
6
|
+
EventStore.connect :adapter => :postgres, :database => 'event_store_performance', :host => 'localhost'
|
7
|
+
|
8
|
+
iterations = 100
|
9
|
+
loads_per_iteration = 100
|
10
|
+
|
11
|
+
# GC.stat output explanation
|
12
|
+
# COUNT: the number of times a GC ran (both full GC and lazy sweep are
|
13
|
+
# included)
|
14
|
+
# HEAP_USED: the number of heaps that have more than 0 slots used in them. The
|
15
|
+
# larger this number, the slower your GC will be.
|
16
|
+
# HEAP_LENGTH: the total number of heaps allocated in memory. For example 1648
|
17
|
+
# means - about 25.75MB is allocated to Ruby heaps. (1648 * (2 << 13)).to_f /
|
18
|
+
# (2 << 19)
|
19
|
+
# HEAP_INCREMENT: Is the number of extra heaps to be allocated, next time Ruby
|
20
|
+
# grows the number of heaps (as it does after it runs a GC and discovers it
|
21
|
+
# does not have enough free space), this number is updated each GC run to be
|
22
|
+
# 1.8 * heap_used. In later versions of Ruby this multiplier is configurable.
|
23
|
+
# HEAP_LIVE_NUM: This is the running number objects in Ruby heaps, it will
|
24
|
+
# change every time you call GC.stat
|
25
|
+
# HEAP_FREE_NUM: This is a slightly confusing number, it changes after a GC
|
26
|
+
# runs, it will let you know how many objects were left in the heaps after the
|
27
|
+
# GC finished running. So, in this example we had 102447 slots empty after the
|
28
|
+
# last GC. (it also increased when objects are recycled internally - which can
|
29
|
+
# happen between GCs)
|
30
|
+
# HEAP_FINAL_NUM: Is the count of objects that were not finalized during the
|
31
|
+
# last GC
|
32
|
+
# TOTAL_ALLOCATED_OBJECT: The running total of allocated objects from the
|
33
|
+
# beginning of the process. This number will change every time you allocate
|
34
|
+
# objects. Note: in a corner case this value may overflow.
|
35
|
+
# TOTAL_FREED_OBJECT: The number of objects that were freed by the GC from the
|
36
|
+
# beginning of the process.
|
37
|
+
|
38
|
+
def output_gc_data
|
39
|
+
puts GC.stat
|
40
|
+
end
|
41
|
+
|
42
|
+
iterations.times do
|
43
|
+
loads_per_iteration.times do
|
44
|
+
client = EventStore::Client.new(rand(300)+1, :device)
|
45
|
+
client.snapshot
|
46
|
+
end
|
47
|
+
output_gc_data
|
48
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'event_store'
|
2
|
+
|
3
|
+
# db_config = Hash[
|
4
|
+
# :username => 'postgres',
|
5
|
+
# :password => 'Password1',
|
6
|
+
# host: 'ec2-54-221-80-232.compute-1.amazonaws.com',
|
7
|
+
# encoding: 'utf8',
|
8
|
+
# pool: 1000,
|
9
|
+
# adapter: :postgres,
|
10
|
+
# database: 'event_store_performance'
|
11
|
+
# ]
|
12
|
+
# EventStore.connect ( db_config )
|
13
|
+
# EventStore.connect :adapter => :postgres, :database => 'event_store_performance', host: 'localhost'
|
14
|
+
EventStore.connect :adapter => :vertica, :database => 'nexia_history', host: '192.168.180.65', username: 'dbadmin', password: 'password'
|
15
|
+
EventStore.redis_connect host: 'localhost'
|
16
|
+
|
17
|
+
|
18
|
+
DEVICES = 1000
|
19
|
+
EVENTS_PER_DEVICE = 5_000
|
20
|
+
EVENT_TYPES = 1000
|
21
|
+
|
22
|
+
event_types = Array.new(EVENT_TYPES) { |i| "event_type_#{i}" }
|
23
|
+
|
24
|
+
EventStore.redis.flushall
|
25
|
+
|
26
|
+
puts "Creating #{DEVICES} Aggregates with #{EVENTS_PER_DEVICE} events each. There are #{EVENT_TYPES} types of events."
|
27
|
+
|
28
|
+
(1..DEVICES).each do |device_id|
|
29
|
+
client = EventStore::Client.new(device_id, :device)
|
30
|
+
records = []
|
31
|
+
|
32
|
+
EVENTS_PER_DEVICE.times do |version|
|
33
|
+
records << EventStore::Event.new(device_id.to_s, Time.now, event_types.sample, rand(9999999999999).to_s(2), version)
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Appending #{EVENTS_PER_DEVICE} events for #{device_id} of #{DEVICES} Aggregates."
|
37
|
+
start_time = Time.now
|
38
|
+
client.append(records)
|
39
|
+
end_time = Time.now
|
40
|
+
total_time = end_time - start_time
|
41
|
+
puts "Success! (Total Time: #{total_time} = #{(EVENTS_PER_DEVICE) / total_time} inserts per second)"
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EventStore::Client do
|
4
|
+
let(:es_client) { EventStore::Client }
|
5
|
+
|
6
|
+
before do
|
7
|
+
client_1 = es_client.new('1', :device)
|
8
|
+
client_2 = es_client.new('2', :device)
|
9
|
+
|
10
|
+
events_by_aggregate_id = {'1' => [], '2' => []}
|
11
|
+
@event_time = Time.parse("2001-01-01 00:00:00 UTC")
|
12
|
+
([1]*10 + [2]*10).shuffle.each_with_index do |aggregate_id, version|
|
13
|
+
events_by_aggregate_id[aggregate_id.to_s] << EventStore::Event.new(aggregate_id.to_s, @event_time, 'event_name', "#{234532.to_s(2)}_foo}", version)
|
14
|
+
end
|
15
|
+
client_1.append events_by_aggregate_id['1']
|
16
|
+
client_2.append events_by_aggregate_id['2']
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#raw_event_stream' do
|
20
|
+
it "should be an array of hashes that represent database records, not EventStore::SerializedEvent objects" do
|
21
|
+
raw_stream = es_client.new(1, :device).raw_event_stream
|
22
|
+
raw_stream.class.should == Array
|
23
|
+
raw_event = raw_stream.first
|
24
|
+
raw_event.class.should == Hash
|
25
|
+
raw_event.keys.should == [:id, :version, :aggregate_id, :fully_qualified_name, :occurred_at, :serialized_event]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be empty for aggregates without events' do
|
29
|
+
stream = es_client.new(100, :device).raw_event_stream
|
30
|
+
expect(stream.empty?).to be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should only have events for a single aggregate' do
|
34
|
+
stream = es_client.new(1, :device).raw_event_stream
|
35
|
+
stream.each { |event| event[:aggregate_id].should == '1' }
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should have all events for that aggregate' do
|
39
|
+
stream = es_client.new(1, :device).raw_event_stream
|
40
|
+
expect(stream.count).to eq(10)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#event_stream' do
|
45
|
+
it "should be an array of EventStore::SerializedEvent objects" do
|
46
|
+
stream = es_client.new(1, :device).event_stream
|
47
|
+
stream.class.should == Array
|
48
|
+
event = stream.first
|
49
|
+
event.class.should == EventStore::SerializedEvent
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be empty for aggregates without events' do
|
53
|
+
stream = es_client.new(100, :device).raw_event_stream
|
54
|
+
expect(stream.empty?).to be_true
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should only have events for a single aggregate' do
|
58
|
+
raw_stream = es_client.new(1, :device).raw_event_stream
|
59
|
+
stream = es_client.new(1, :device).event_stream
|
60
|
+
stream.map(&:fully_qualified_name).should == raw_stream.inject([]){|m, event| m << event[:fully_qualified_name]; m}
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should have all events for that aggregate' do
|
64
|
+
stream = es_client.new(1, :device).event_stream
|
65
|
+
expect(stream.count).to eq(10)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
describe '#raw_event_streams_from_version' do
|
71
|
+
subject { es_client.new(1, :device) }
|
72
|
+
|
73
|
+
it 'should return all the raw events in the stream starting from a certain version' do
|
74
|
+
minimum_event_version = 2
|
75
|
+
raw_stream = subject.raw_event_stream_from(minimum_event_version)
|
76
|
+
event_versions = raw_stream.inject([]){|m, event| m << event[:version]; m}
|
77
|
+
event_versions.min.should >= minimum_event_version
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return no more than the maximum number of events specified above the ' do
|
81
|
+
max_number_of_events = 5
|
82
|
+
minimum_event_version = 2
|
83
|
+
raw_stream = subject.raw_event_stream_from(minimum_event_version, max_number_of_events)
|
84
|
+
expect(raw_stream.count).to eq(max_number_of_events)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should be empty for version above the current highest version number' do
|
88
|
+
raw_stream = subject.raw_event_stream_from(subject.version + 1)
|
89
|
+
expect(raw_stream).to be_empty
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'event_stream_from_version' do
|
94
|
+
subject { es_client.new(1, :device) }
|
95
|
+
|
96
|
+
it 'should return all the raw events in the stream starting from a certain version' do
|
97
|
+
minimum_event_version = 2
|
98
|
+
raw_stream = subject.raw_event_stream_from(minimum_event_version)
|
99
|
+
event_versions = raw_stream.inject([]){|m, event| m << event[:version]; m}
|
100
|
+
event_versions.min.should >= minimum_event_version
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should return no more than the maximum number of events specified above the ' do
|
104
|
+
max_number_of_events = 5
|
105
|
+
minimum_event_version = 2
|
106
|
+
raw_stream = subject.raw_event_stream_from(minimum_event_version, max_number_of_events)
|
107
|
+
expect(raw_stream.count).to eq(max_number_of_events)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should be empty for version above the current highest version number' do
|
111
|
+
raw_stream = subject.raw_event_stream_from(subject.version + 1)
|
112
|
+
raw_stream.should == []
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#peek' do
|
117
|
+
let(:client) {es_client.new(1, :device)}
|
118
|
+
subject { client.peek }
|
119
|
+
|
120
|
+
it 'should return the last event in the event stream' do
|
121
|
+
last_event = EventStore.db.from(client.event_table).where(aggregate_id: '1').order(:version).last
|
122
|
+
subject.should == EventStore::SerializedEvent.new(last_event[:fully_qualified_name], last_event[:serialized_event].to_s, last_event[:version], @event_time)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe '#append' do
|
127
|
+
before do
|
128
|
+
@client = EventStore::Client.new('1', :device)
|
129
|
+
@event = @client.peek
|
130
|
+
version = @client.version
|
131
|
+
@old_event = EventStore::Event.new('1', (@event_time - 2000).utc, "old", "#{1000.to_s(2)}_foo", version += 1)
|
132
|
+
@new_event = EventStore::Event.new('1', (@event_time - 1000).utc, "new", "#{1001.to_s(2)}_foo", version += 1)
|
133
|
+
@really_new_event = EventStore::Event.new('1', (@event_time + 100).utc, "really_new", "#{1002.to_s(2)}_foo", version += 1)
|
134
|
+
@duplicate_event = EventStore::Event.new('1', (@event_time).utc, 'duplicate', "#{12.to_s(2)}_foo", version += 1)
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "when expected version number is greater than the last version" do
|
138
|
+
describe 'and there are no prior events of type' do
|
139
|
+
before do
|
140
|
+
@client.append([@old_event])
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should append a single event of a new type without raising an error' do
|
144
|
+
initial_count = @client.count
|
145
|
+
events = [@new_event]
|
146
|
+
@client.append(events)
|
147
|
+
@client.count.should == initial_count + events.length
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should append multiple events of a new type without raising and error' do
|
151
|
+
initial_count = @client.count
|
152
|
+
events = [@new_event, @new_event]
|
153
|
+
@client.append(events)
|
154
|
+
@client.count.should == initial_count + events.length
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should increment the version number by the number of events added" do
|
158
|
+
events = [@new_event, @really_new_event]
|
159
|
+
initial_version = @client.version
|
160
|
+
@client.append(events)
|
161
|
+
@client.version.should == (initial_version + events.length)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should set the snapshot version number to match that of the last event in the aggregate's event stream" do
|
165
|
+
events = [@new_event, @really_new_event]
|
166
|
+
initial_stream_version = @client.raw_event_stream.last[:version]
|
167
|
+
@client.snapshot.last.version.should == initial_stream_version
|
168
|
+
@client.append(events)
|
169
|
+
updated_stream_version = @client.raw_event_stream.last[:version]
|
170
|
+
@client.snapshot.last.version.should == updated_stream_version
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should write-through-cache the event in a snapshot without duplicating events" do
|
174
|
+
@client.destroy!
|
175
|
+
@client.append([@old_event, @new_event, @really_new_event])
|
176
|
+
@client.snapshot.should == @client.event_stream
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe 'with prior events of same type' do
|
181
|
+
it 'should raise a ConcurrencyError if the the event version is less than current version' do
|
182
|
+
@client.append([@duplicate_event])
|
183
|
+
reset_current_version_for(@client)
|
184
|
+
expect { @client.append([@duplicate_event]) }.to raise_error(EventStore::ConcurrencyError)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should not raise an error when two events of the same type are appended' do
|
188
|
+
@client.append([@duplicate_event])
|
189
|
+
@duplicate_event[:version] += 1
|
190
|
+
@client.append([@duplicate_event]) #will fail automatically if it throws an error, no need for assertions (which now print warning for some reason)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should write-through-cache the event in a snapshot without duplicating events" do
|
194
|
+
@client.destroy!
|
195
|
+
@client.append([@old_event, @new_event, @new_event])
|
196
|
+
expected = []
|
197
|
+
expected << @client.event_stream.first
|
198
|
+
expected << @client.event_stream.last
|
199
|
+
@client.snapshot.should == expected
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should increment the version number by the number of unique events added" do
|
203
|
+
events = [@old_event, @old_event, @old_event]
|
204
|
+
initial_version = @client.version
|
205
|
+
@client.append(events)
|
206
|
+
@client.version.should == (initial_version + events.uniq.length)
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should set the snapshot version number to match that of the last event in the aggregate's event stream" do
|
210
|
+
events = [@old_event, @old_event]
|
211
|
+
initial_stream_version = @client.raw_event_stream.last[:version]
|
212
|
+
@client.snapshot.last.version.should == initial_stream_version
|
213
|
+
@client.append(events)
|
214
|
+
updated_stream_version = @client.raw_event_stream.last[:version]
|
215
|
+
@client.snapshot.last.version.should == updated_stream_version
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe 'transactional' do
|
221
|
+
before do
|
222
|
+
@bad_event = @new_event.dup
|
223
|
+
@bad_event.fully_qualified_name = nil
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should revert all append events if one fails' do
|
227
|
+
starting_count = @client.count
|
228
|
+
expect { @client.append([@new_event, @bad_event]) }.to raise_error(EventStore::AttributeMissingError)
|
229
|
+
expect(@client.count).to eq(starting_count)
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'does not yield to the block if it fails' do
|
233
|
+
x = 0
|
234
|
+
expect { @client.append([@bad_event]) { x += 1 } }.to raise_error(EventStore::AttributeMissingError)
|
235
|
+
expect(x).to eq(0)
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'yield to the block after event creation' do
|
239
|
+
x = 0
|
240
|
+
@client.append([]) { x += 1 }
|
241
|
+
expect(x).to eq(1)
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should pass the raw event_data to the block' do
|
245
|
+
@client.append([@new_event]) do |raw_event_data|
|
246
|
+
expect(raw_event_data).to eq([@new_event])
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe 'snapshot' do
|
252
|
+
before do
|
253
|
+
@client = es_client.new('10', :device)
|
254
|
+
@client.snapshot.length.should == 0
|
255
|
+
version = @client.version
|
256
|
+
@client.append %w{ e1 e2 e3 e1 e2 e4 e5 e2 e5 e4}.map {|fqn|EventStore::Event.new('10', Time.now.utc, fqn, 234532.to_s(2), version += 1)}
|
257
|
+
end
|
258
|
+
|
259
|
+
it "finds the most recent records for each type" do
|
260
|
+
version = @client.version
|
261
|
+
expected_snapshot = %w{ e1 e2 e3 e4 e5 }.map {|fqn| EventStore::SerializedEvent.new(fqn, 234532.to_s(2), version +=1 ) }
|
262
|
+
@client.event_stream.length.should == 10
|
263
|
+
actual_snapshot = @client.snapshot
|
264
|
+
actual_snapshot.length.should == 5
|
265
|
+
actual_snapshot.map(&:fully_qualified_name).should == ["e3", "e1", "e2", "e5", "e4"] #sorted by version no
|
266
|
+
actual_snapshot.map(&:serialized_event).should == expected_snapshot.map(&:serialized_event)
|
267
|
+
most_recent_events_of_each_type = {}
|
268
|
+
@client.event_stream.each do |e|
|
269
|
+
if most_recent_events_of_each_type[e.fully_qualified_name].nil? || most_recent_events_of_each_type[e.fully_qualified_name].version < e.version
|
270
|
+
most_recent_events_of_each_type[e.fully_qualified_name] = e
|
271
|
+
end
|
272
|
+
end
|
273
|
+
actual_snapshot.map(&:version).should == most_recent_events_of_each_type.values.map(&:version).sort
|
274
|
+
end
|
275
|
+
|
276
|
+
it "increments the version number of the snapshot when an event is appended" do
|
277
|
+
@client.snapshot.last.version.should == @client.raw_event_stream.last[:version]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
def reset_current_version_for(client)
|
283
|
+
aggregate = client.instance_variable_get("@aggregate")
|
284
|
+
EventStore.redis.hset(aggregate.snapshot_version_table, :current_version, 1000)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module EventStore
|
3
|
+
describe "Snapshots" do
|
4
|
+
let(:aggregate_id) {'100A' }
|
5
|
+
let(:new_aggregate) { Aggregate.new(aggregate_id) }
|
6
|
+
let(:client) { Client.new(aggregate_id) }
|
7
|
+
let(:appender) { EventAppender.new(new_aggregate) }
|
8
|
+
let(:events) { Array.new }
|
9
|
+
|
10
|
+
before do
|
11
|
+
@event_time = Time.parse("2001-01-01 00:00:00 UTC")
|
12
|
+
(0...10).to_a.each_with_index do |version|
|
13
|
+
events << EventStore::Event.new(aggregate_id, @event_time, 'event_name', "#{234532.to_s(2)}_foo}", version)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should build an empty snapshot for a new client" do
|
18
|
+
new_aggregate.snapshot.should == []
|
19
|
+
new_aggregate.version.should == -1
|
20
|
+
EventStore.redis.hget(new_aggregate.snapshot_version_table, :current_version).should == nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should rebuild a snapshot after it is deleted" do
|
24
|
+
appender.append(events)
|
25
|
+
snapshot = new_aggregate.snapshot
|
26
|
+
version = new_aggregate.version
|
27
|
+
new_aggregate.delete_snapshot!
|
28
|
+
new_aggregate.rebuild_snapshot!
|
29
|
+
new_aggregate.snapshot.should == snapshot
|
30
|
+
end
|
31
|
+
|
32
|
+
it "a client should rebuild a snapshot" do
|
33
|
+
Aggregate.any_instance.should_receive(:delete_snapshot!)
|
34
|
+
Aggregate.any_instance.should_receive(:rebuild_snapshot!)
|
35
|
+
client.rebuild_snapshot!
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should rebuild the snapshot if events exist, but the snapshot is empty" do
|
39
|
+
appender.append(events)
|
40
|
+
snapshot = new_aggregate.snapshot
|
41
|
+
new_aggregate.delete_snapshot!
|
42
|
+
new_aggregate.snapshot.should == snapshot
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|