event_store 0.1.5 → 0.1.6
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/.simplecov +16 -0
- data/Rakefile +11 -1
- data/db/event_store_sample_data_generator.rb +2 -2
- data/event_store.gemspec +1 -0
- data/lib/event_store/aggregate.rb +1 -1
- data/lib/event_store/client.rb +1 -1
- data/lib/event_store/event_appender.rb +1 -1
- data/lib/event_store/version.rb +1 -1
- data/lib/event_store.rb +14 -0
- data/spec/event_store/client_spec.rb +38 -29
- metadata +25 -3
data/.simplecov
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-rcov'
|
3
|
+
|
4
|
+
class SimpleCov::Formatter::MergedFormatter
|
5
|
+
def format(result)
|
6
|
+
SimpleCov::Formatter::HTMLFormatter.new.format(result)
|
7
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
add_filter "_spec.rb"
|
14
|
+
|
15
|
+
SimpleCov.minimum_coverage 96
|
16
|
+
end
|
data/Rakefile
CHANGED
@@ -4,6 +4,11 @@ RSpec::Core::RakeTask.new(:'spec:ci')
|
|
4
4
|
|
5
5
|
task :default => :'spec:ci'
|
6
6
|
|
7
|
+
def rspec_out_file
|
8
|
+
require 'rspec_junit_formatter'
|
9
|
+
"-f RspecJunitFormatter -o results.xml"
|
10
|
+
end
|
11
|
+
|
7
12
|
desc "Seed the performance db with millions of events"
|
8
13
|
task :'db:seed:perf' do
|
9
14
|
sh 'time bundle exec ruby spec/benchmark/seed_db.rb'
|
@@ -12,4 +17,9 @@ end
|
|
12
17
|
desc "Run the performance benchmarks on the performance db"
|
13
18
|
task :benchmark do
|
14
19
|
sh 'bundle exec ruby spec/benchmark/bench.rb'
|
15
|
-
end
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Run all tests and generate coverage xml"
|
23
|
+
task :'spec:cov' do
|
24
|
+
sh "bundle exec rspec #{rspec_out_file} spec"
|
25
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# require_relative '../../protocol_buffers/lib/protocol_buffers'
|
2
2
|
require 'faceplate_api'
|
3
|
-
require "faceplate_api/thermostats/test_support
|
3
|
+
require "faceplate_api/thermostats/test_support"
|
4
4
|
require 'securerandom'
|
5
5
|
require 'time'
|
6
6
|
include FaceplateApi
|
@@ -15,7 +15,7 @@ versions_per_device = (0..(event_names.length * ITERATIONS)).to_a
|
|
15
15
|
|
16
16
|
mothers = {}
|
17
17
|
aggregate_ids.each do |aggregate_id|
|
18
|
-
mother =
|
18
|
+
mother = FaceplateApi::EventFixture.new(header: {device_id: aggregate_id}).event_mother
|
19
19
|
mothers[mother] = versions_per_device.dup
|
20
20
|
end
|
21
21
|
|
data/event_store.gemspec
CHANGED
@@ -23,7 +23,7 @@ module EventStore
|
|
23
23
|
raw_event = value.split(EventStore::SNAPSHOT_DELIMITER)
|
24
24
|
fully_qualified_name = key
|
25
25
|
version = raw_event.first.to_i
|
26
|
-
serialized_event = raw_event[1]
|
26
|
+
serialized_event = EventStore.unescape_bytea(raw_event[1])
|
27
27
|
occurred_at = Time.parse(raw_event.last)
|
28
28
|
snap << SerializedEvent.new(fully_qualified_name, serialized_event, version, occurred_at)
|
29
29
|
end
|
data/lib/event_store/client.rb
CHANGED
@@ -81,7 +81,7 @@ module EventStore
|
|
81
81
|
|
82
82
|
def translate_event(event_hash)
|
83
83
|
occurred_at = TimeHacker.translate_occurred_at_from_local_to_gmt(event_hash[:occurred_at])
|
84
|
-
SerializedEvent.new event_hash[:fully_qualified_name], event_hash[:serialized_event], event_hash[:version], occurred_at
|
84
|
+
SerializedEvent.new event_hash[:fully_qualified_name], EventStore.unescape_bytea(event_hash[:serialized_event]), event_hash[:version], occurred_at
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
@@ -57,7 +57,7 @@ module EventStore
|
|
57
57
|
{ :version => raw_event.version.to_i,
|
58
58
|
:aggregate_id => raw_event.aggregate_id,
|
59
59
|
:occurred_at => Time.parse(raw_event.occurred_at.to_s).utc, #to_s truncates microseconds, which brake Time equality
|
60
|
-
:serialized_event => raw_event.serialized_event,
|
60
|
+
:serialized_event => EventStore.escape_bytea(raw_event.serialized_event),
|
61
61
|
:fully_qualified_name => raw_event.fully_qualified_name }
|
62
62
|
end
|
63
63
|
|
data/lib/event_store/version.rb
CHANGED
data/lib/event_store.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'sequel'
|
2
|
+
require 'sequel_core'
|
2
3
|
require 'vertica'
|
3
4
|
require 'sequel-vertica'
|
4
5
|
require 'redis'
|
@@ -90,6 +91,19 @@ module EventStore
|
|
90
91
|
create_db
|
91
92
|
end
|
92
93
|
|
94
|
+
def self.escape_bytea(binary_string)
|
95
|
+
@adapter == 'vertica' ? binary_string : EventStore.db.literal(binary_string.to_sequel_blob)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.unescape_bytea(binary_string)
|
99
|
+
if @adapter == 'vertica'
|
100
|
+
binary_string
|
101
|
+
else
|
102
|
+
unescaped = Sequel::Postgres::Adapter.unescape_bytea(binary_string)
|
103
|
+
unescaped[0] == "'" && unescaped[-1] == "'" ? unescaped[1...-1] : unescaped #postgres adds an extra set of quotes when you insert it, Redis does not. Therefore we need to pull off the extra quotes if they are there
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
93
107
|
def self.custom_config(database_config, redis_config, envrionment = 'production')
|
94
108
|
self.redis_connect(redis_config)
|
95
109
|
@adapter = database_config['adapter'].to_s
|
@@ -1,24 +1,28 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
AGGREGATE_ID_ONE = SecureRandom.uuid
|
4
|
+
AGGREGATE_ID_TWO = SecureRandom.uuid
|
5
|
+
AGGREGATE_ID_THREE = SecureRandom.uuid
|
2
6
|
|
3
7
|
describe EventStore::Client do
|
4
8
|
let(:es_client) { EventStore::Client }
|
5
9
|
|
6
10
|
before do
|
7
|
-
client_1 = es_client.new(
|
8
|
-
client_2 = es_client.new(
|
11
|
+
client_1 = es_client.new(AGGREGATE_ID_ONE, :device)
|
12
|
+
client_2 = es_client.new(AGGREGATE_ID_TWO, :device)
|
9
13
|
|
10
|
-
events_by_aggregate_id = {
|
14
|
+
events_by_aggregate_id = {AGGREGATE_ID_ONE => [], AGGREGATE_ID_TWO => []}
|
11
15
|
@event_time = Time.parse("2001-01-01 00:00:00 UTC")
|
12
|
-
([
|
13
|
-
events_by_aggregate_id[aggregate_id.to_s] << EventStore::Event.new(aggregate_id.to_s, @event_time, 'event_name',
|
16
|
+
([AGGREGATE_ID_ONE]*10 + [AGGREGATE_ID_TWO]*10).shuffle.each_with_index do |aggregate_id, version|
|
17
|
+
events_by_aggregate_id[aggregate_id.to_s] << EventStore::Event.new(aggregate_id.to_s, @event_time, 'event_name', serialized_binary_event_data, version)
|
14
18
|
end
|
15
|
-
client_1.append events_by_aggregate_id[
|
16
|
-
client_2.append events_by_aggregate_id[
|
19
|
+
client_1.append events_by_aggregate_id[AGGREGATE_ID_ONE]
|
20
|
+
client_2.append events_by_aggregate_id[AGGREGATE_ID_TWO]
|
17
21
|
end
|
18
22
|
|
19
23
|
describe '#raw_event_stream' do
|
20
24
|
it "should be an array of hashes that represent database records, not EventStore::SerializedEvent objects" do
|
21
|
-
raw_stream = es_client.new(
|
25
|
+
raw_stream = es_client.new(AGGREGATE_ID_ONE, :device).raw_event_stream
|
22
26
|
raw_stream.class.should == Array
|
23
27
|
raw_event = raw_stream.first
|
24
28
|
raw_event.class.should == Hash
|
@@ -31,19 +35,19 @@ describe EventStore::Client do
|
|
31
35
|
end
|
32
36
|
|
33
37
|
it 'should only have events for a single aggregate' do
|
34
|
-
stream = es_client.new(
|
35
|
-
stream.each { |event| event[:aggregate_id].should ==
|
38
|
+
stream = es_client.new(AGGREGATE_ID_ONE, :device).raw_event_stream
|
39
|
+
stream.each { |event| event[:aggregate_id].should == AGGREGATE_ID_ONE }
|
36
40
|
end
|
37
41
|
|
38
42
|
it 'should have all events for that aggregate' do
|
39
|
-
stream = es_client.new(
|
43
|
+
stream = es_client.new(AGGREGATE_ID_ONE, :device).raw_event_stream
|
40
44
|
expect(stream.count).to eq(10)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
describe '#event_stream' do
|
45
49
|
it "should be an array of EventStore::SerializedEvent objects" do
|
46
|
-
stream = es_client.new(
|
50
|
+
stream = es_client.new(AGGREGATE_ID_ONE, :device).event_stream
|
47
51
|
stream.class.should == Array
|
48
52
|
event = stream.first
|
49
53
|
event.class.should == EventStore::SerializedEvent
|
@@ -55,20 +59,20 @@ describe EventStore::Client do
|
|
55
59
|
end
|
56
60
|
|
57
61
|
it 'should only have events for a single aggregate' do
|
58
|
-
raw_stream = es_client.new(
|
59
|
-
stream = es_client.new(
|
62
|
+
raw_stream = es_client.new(AGGREGATE_ID_ONE, :device).raw_event_stream
|
63
|
+
stream = es_client.new(AGGREGATE_ID_ONE, :device).event_stream
|
60
64
|
stream.map(&:fully_qualified_name).should == raw_stream.inject([]){|m, event| m << event[:fully_qualified_name]; m}
|
61
65
|
end
|
62
66
|
|
63
67
|
it 'should have all events for that aggregate' do
|
64
|
-
stream = es_client.new(
|
68
|
+
stream = es_client.new(AGGREGATE_ID_ONE, :device).event_stream
|
65
69
|
expect(stream.count).to eq(10)
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
73
|
|
70
74
|
describe '#raw_event_streams_from_version' do
|
71
|
-
subject { es_client.new(
|
75
|
+
subject { es_client.new(AGGREGATE_ID_ONE, :device) }
|
72
76
|
|
73
77
|
it 'should return all the raw events in the stream starting from a certain version' do
|
74
78
|
minimum_event_version = 2
|
@@ -91,7 +95,7 @@ describe EventStore::Client do
|
|
91
95
|
end
|
92
96
|
|
93
97
|
describe 'event_stream_from_version' do
|
94
|
-
subject { es_client.new(
|
98
|
+
subject { es_client.new(AGGREGATE_ID_ONE, :device) }
|
95
99
|
|
96
100
|
it 'should return all the raw events in the stream starting from a certain version' do
|
97
101
|
minimum_event_version = 2
|
@@ -114,24 +118,24 @@ describe EventStore::Client do
|
|
114
118
|
end
|
115
119
|
|
116
120
|
describe '#peek' do
|
117
|
-
let(:client) {es_client.new(
|
121
|
+
let(:client) {es_client.new(AGGREGATE_ID_ONE, :device)}
|
118
122
|
subject { client.peek }
|
119
123
|
|
120
124
|
it 'should return the last event in the event stream' do
|
121
|
-
last_event = EventStore.db.from(client.event_table).where(aggregate_id:
|
122
|
-
subject.should == EventStore::SerializedEvent.new(last_event[:fully_qualified_name], last_event[:serialized_event]
|
125
|
+
last_event = EventStore.db.from(client.event_table).where(aggregate_id: AGGREGATE_ID_ONE).order(:version).last
|
126
|
+
subject.should == EventStore::SerializedEvent.new(last_event[:fully_qualified_name], EventStore.unescape_bytea(last_event[:serialized_event]), last_event[:version], @event_time)
|
123
127
|
end
|
124
128
|
end
|
125
129
|
|
126
130
|
describe '#append' do
|
127
131
|
before do
|
128
|
-
@client = EventStore::Client.new(
|
132
|
+
@client = EventStore::Client.new(AGGREGATE_ID_ONE, :device)
|
129
133
|
@event = @client.peek
|
130
134
|
version = @client.version
|
131
|
-
@old_event = EventStore::Event.new(
|
132
|
-
@new_event = EventStore::Event.new(
|
133
|
-
@really_new_event = EventStore::Event.new(
|
134
|
-
@duplicate_event = EventStore::Event.new(
|
135
|
+
@old_event = EventStore::Event.new(AGGREGATE_ID_ONE, (@event_time - 2000).utc, "old", "#{1000.to_s(2)}_foo", version += 1)
|
136
|
+
@new_event = EventStore::Event.new(AGGREGATE_ID_ONE, (@event_time - 1000).utc, "new", "#{1001.to_s(2)}_foo", version += 1)
|
137
|
+
@really_new_event = EventStore::Event.new(AGGREGATE_ID_ONE, (@event_time + 100).utc, "really_new", "#{1002.to_s(2)}_foo", version += 1)
|
138
|
+
@duplicate_event = EventStore::Event.new(AGGREGATE_ID_ONE, (@event_time).utc, 'duplicate', "#{12.to_s(2)}_foo", version += 1)
|
135
139
|
end
|
136
140
|
|
137
141
|
describe "when expected version number is greater than the last version" do
|
@@ -250,17 +254,17 @@ describe EventStore::Client do
|
|
250
254
|
|
251
255
|
describe 'snapshot' do
|
252
256
|
before do
|
253
|
-
@client = es_client.new(
|
257
|
+
@client = es_client.new(AGGREGATE_ID_THREE, :device)
|
254
258
|
@client.snapshot.length.should == 0
|
255
259
|
version = @client.version
|
256
|
-
@client.append %w{ e1 e2 e3 e1 e2 e4 e5 e2 e5 e4}.map {|fqn|EventStore::Event.new(
|
260
|
+
@client.append %w{ e1 e2 e3 e1 e2 e4 e5 e2 e5 e4}.map {|fqn|EventStore::Event.new(AGGREGATE_ID_THREE, Time.now.utc, fqn, serialized_binary_event_data, version += 1)}
|
257
261
|
end
|
258
262
|
|
259
263
|
it "finds the most recent records for each type" do
|
260
264
|
version = @client.version
|
261
|
-
expected_snapshot = %w{ e1 e2 e3 e4 e5 }.map {|fqn| EventStore::SerializedEvent.new(fqn,
|
262
|
-
@client.event_stream.length.should == 10
|
265
|
+
expected_snapshot = %w{ e1 e2 e3 e4 e5 }.map {|fqn| EventStore::SerializedEvent.new(fqn, serialized_binary_event_data, version +=1 ) }
|
263
266
|
actual_snapshot = @client.snapshot
|
267
|
+
@client.event_stream.length.should == 10
|
264
268
|
actual_snapshot.length.should == 5
|
265
269
|
actual_snapshot.map(&:fully_qualified_name).should == ["e3", "e1", "e2", "e5", "e4"] #sorted by version no
|
266
270
|
actual_snapshot.map(&:serialized_event).should == expected_snapshot.map(&:serialized_event)
|
@@ -283,5 +287,10 @@ describe EventStore::Client do
|
|
283
287
|
aggregate = client.instance_variable_get("@aggregate")
|
284
288
|
EventStore.redis.hset(aggregate.snapshot_version_table, :current_version, 1000)
|
285
289
|
end
|
290
|
+
|
291
|
+
end
|
292
|
+
def serialized_binary_event_data
|
293
|
+
@event_data ||= File.open(File.expand_path("../serialized_binary_event_data.txt", __FILE__), 'rb') {|f| f.read}
|
294
|
+
@event_data
|
286
295
|
end
|
287
296
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -220,6 +220,22 @@ dependencies:
|
|
220
220
|
- - ! '>='
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: rspec_junit_formatter
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
none: false
|
227
|
+
requirements:
|
228
|
+
- - ! '>='
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
type: :development
|
232
|
+
prerelease: false
|
233
|
+
version_requirements: !ruby/object:Gem::Requirement
|
234
|
+
none: false
|
235
|
+
requirements:
|
236
|
+
- - ! '>='
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
223
239
|
description: ! '["A Ruby implementation of an EventSource (A+ES) tuned for Vertica
|
224
240
|
or Postgres"]'
|
225
241
|
email:
|
@@ -230,6 +246,7 @@ extra_rdoc_files: []
|
|
230
246
|
files:
|
231
247
|
- .gitignore
|
232
248
|
- .rspec
|
249
|
+
- .simplecov
|
233
250
|
- Gemfile
|
234
251
|
- Guardfile
|
235
252
|
- LICENSE.txt
|
@@ -269,12 +286,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
269
286
|
- - ! '>='
|
270
287
|
- !ruby/object:Gem::Version
|
271
288
|
version: '0'
|
289
|
+
segments:
|
290
|
+
- 0
|
291
|
+
hash: -3767686812779732667
|
272
292
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
273
293
|
none: false
|
274
294
|
requirements:
|
275
295
|
- - ! '>='
|
276
296
|
- !ruby/object:Gem::Version
|
277
297
|
version: '0'
|
298
|
+
segments:
|
299
|
+
- 0
|
300
|
+
hash: -3767686812779732667
|
278
301
|
requirements: []
|
279
302
|
rubyforge_project:
|
280
303
|
rubygems_version: 1.8.25
|
@@ -289,4 +312,3 @@ test_files:
|
|
289
312
|
- spec/event_store/snapshot_spec.rb
|
290
313
|
- spec/event_store/vertica guy notes.txt
|
291
314
|
- spec/spec_helper.rb
|
292
|
-
has_rdoc:
|