event_store 0.1.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.
- 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
|