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