sandthorn_driver_sequel 1.0.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/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +7 -0
- data/lib/sandthorn_driver_sequel/errors.rb +7 -0
- data/lib/sandthorn_driver_sequel/event_store.rb +187 -0
- data/lib/sandthorn_driver_sequel/event_store_context.rb +17 -0
- data/lib/sandthorn_driver_sequel/migration.rb +109 -0
- data/lib/sandthorn_driver_sequel/sequel_driver.rb +20 -0
- data/lib/sandthorn_driver_sequel/version.rb +3 -0
- data/lib/sandthorn_driver_sequel.rb +17 -0
- data/sandthorn_driver_sequel.gemspec +36 -0
- data/spec/asking_for_aggregates_to_snapshot_spec.rb +61 -0
- data/spec/db/event_store.sqlite3 +0 -0
- data/spec/driver_interface_spec.rb +48 -0
- data/spec/event_store_with_context_spec.rb +25 -0
- data/spec/get_events_spec.rb +150 -0
- data/spec/migration_specifying_domain_spec.rb +35 -0
- data/spec/saving_events_spec.rb +71 -0
- data/spec/saving_snapshot_spec.rb +76 -0
- data/spec/spec_helper.rb +29 -0
- metadata +263 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ea2f0e2d9a31debe7bd55f2e109e1ca2bacf952d
|
4
|
+
data.tar.gz: 1eb940ef766f8a4d0ee00dd493b25a625987234c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dbe827666513d4a09c9ae9893d5242083030ac198237381cdfc7d60582712343efc6696da32e096b0727df3b8bff0d87d09344717ef0c84cc02656d3da91d405
|
7
|
+
data.tar.gz: 3e86d8133f06049141abfbca327c3530bc68ee05462ad4bea92cce9401e9a53dc08f1fb0f24e47ae9b7472bcfa1b655be61ff3579a21f488d0142e1f85e89ee7
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sandthorn_driver_sequel
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Morgan Hallgren
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# SandthornDriverSequel
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'sandthorn_driver_sequel'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install sandthorn_driver_sequel
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'sandthorn_driver_sequel/sequel_driver'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
class EventStore
|
5
|
+
include EventStoreContext
|
6
|
+
attr_reader :driver, :context, :url
|
7
|
+
def initialize url: nil, context: nil
|
8
|
+
@driver = SequelDriver.new url: url
|
9
|
+
@context = context
|
10
|
+
@url = url
|
11
|
+
end
|
12
|
+
|
13
|
+
def save_events aggregate_events, originating_aggregate_version, aggregate_id, *class_name
|
14
|
+
current_aggregate_version = originating_aggregate_version
|
15
|
+
aggregate_type = class_name.first.to_s
|
16
|
+
driver.execute_in_transaction do |db|
|
17
|
+
if current_aggregate_version == 0
|
18
|
+
to_insert = {aggregate_id: aggregate_id, aggregate_type: aggregate_type, aggregate_version: 0}
|
19
|
+
pk_id = db[aggregates_table_name].insert(to_insert)
|
20
|
+
else
|
21
|
+
current_aggregate = get_current_aggregate_from_aggregates_table aggregate_id, aggregate_type, db
|
22
|
+
pk_id = current_aggregate[:id]
|
23
|
+
if current_aggregate[:aggregate_version] != current_aggregate_version
|
24
|
+
error_message = "#{aggregate_type} with id #{aggregate_id} should be att version #{current_aggregate_version} but was #{current_aggregate[:aggregate_version]} in the event store."
|
25
|
+
raise SandthornDriverSequel::Errors::WrongAggregateVersionError.new(error_message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
timestamp = Time.now.utc
|
29
|
+
aggregate_events.each do |event|
|
30
|
+
current_aggregate_version += 1
|
31
|
+
if current_aggregate_version != event[:aggregate_version]
|
32
|
+
error_message = "#{aggregate_type} with id #{aggregate_id}: expected event with version #{current_aggregate_version}, but got #{event[:aggregate_version]}"
|
33
|
+
raise SandthornDriverSequel::Errors::ConcurrencyError.new(error_message)
|
34
|
+
end
|
35
|
+
to_insert = {aggregate_table_id: pk_id, aggregate_version: event[:aggregate_version], event_name: event[:event_name], event_data: event[:event_data], timestamp: timestamp}
|
36
|
+
db[events_table_name].insert(to_insert)
|
37
|
+
end
|
38
|
+
db[aggregates_table_name].where(id: pk_id).update(aggregate_version: current_aggregate_version)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_snapshot aggregate_snapshot, aggregate_id, class_name
|
43
|
+
#ar_snapshot.event_name = snapshot[:event_name]
|
44
|
+
#ar_snapshot.event_data = snapshot[:event_data]
|
45
|
+
#ar_snapshot.aggregate_version = snapshot[:aggregate_version]
|
46
|
+
#ar_snapshot.aggregate_id = aggregate_id
|
47
|
+
driver.execute_in_transaction do |db|
|
48
|
+
current_aggregate = get_current_aggregate_from_aggregates_table aggregate_id, class_name, db
|
49
|
+
pk_id = current_aggregate[:id]
|
50
|
+
current_snapshot = get_current_snapshot pk_id, db
|
51
|
+
aggregate_version = aggregate_snapshot[:aggregate_version]
|
52
|
+
return if !current_snapshot.nil? && current_snapshot[:aggregate_version] == aggregate_version
|
53
|
+
if current_aggregate[:aggregate_version] < aggregate_version
|
54
|
+
error_message = "#{class_name} with id #{aggregate_id}: tried to save snapshot with version #{aggregate_version}, but current version is at #{current_aggregate[:aggregate_version]}"
|
55
|
+
raise SandthornDriverSequel::Errors::WrongAggregateVersionError.new error_message
|
56
|
+
end
|
57
|
+
if current_snapshot.nil?
|
58
|
+
to_insert = {aggregate_version: aggregate_version, snapshot_data: aggregate_snapshot[:event_data], aggregate_table_id: pk_id}
|
59
|
+
db[snapshots_table_name].insert(to_insert)
|
60
|
+
else
|
61
|
+
to_update = {aggregate_version: aggregate_version, snapshot_data: aggregate_snapshot[:event_data] }
|
62
|
+
db[snapshots_table_name].where(aggregate_table_id: pk_id).update(to_update)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_aggregate_events aggregate_id, *class_name
|
68
|
+
#aggregate_type = class_name.to_s unless class_name.nil?
|
69
|
+
return aggregate_events aggregate_id: aggregate_id
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_aggregate aggregate_id, *class_name
|
73
|
+
snapshot = get_snapshot aggregate_id, class_name
|
74
|
+
after_aggregate_version = 0
|
75
|
+
after_aggregate_version = snapshot[:aggregate_version] unless snapshot.nil?
|
76
|
+
events = aggregate_events after_aggregate_version: after_aggregate_version, aggregate_id: aggregate_id
|
77
|
+
unless snapshot.nil?
|
78
|
+
snap_event = snapshot
|
79
|
+
snap_event[:event_name] = "aggregate_set_from_snapshot"
|
80
|
+
events = events.unshift(snap_event)
|
81
|
+
end
|
82
|
+
events
|
83
|
+
end
|
84
|
+
def get_aggregate_list_by_typename class_name
|
85
|
+
aggregate_type = class_name.to_s
|
86
|
+
driver.execute do |db|
|
87
|
+
db[aggregates_table_name].where(aggregate_type: aggregate_type).select(:aggregate_id).map { |e| e[:aggregate_id] }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_all_typenames
|
92
|
+
driver.execute do |db|
|
93
|
+
db[aggregates_table_name].select(:aggregate_type).distinct.order(:aggregate_type).map{|e| e[:aggregate_type]}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_snapshot aggregate_id, *class_name
|
98
|
+
aggregate_type = class_name.first.to_s
|
99
|
+
driver.execute do |db|
|
100
|
+
current_aggregate = get_current_aggregate_from_aggregates_table aggregate_id, aggregate_type, db
|
101
|
+
snap = get_current_snapshot current_aggregate[:id], db
|
102
|
+
return nil if snap.nil?
|
103
|
+
return {aggregate_version: snap[:aggregate_version], event_data: snap[:snapshot_data]}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_new_events_after_event_id_matching_classname event_id, class_name, args = {}
|
108
|
+
take = args.fetch(:take, 0)
|
109
|
+
aggregate_type = class_name.to_s
|
110
|
+
driver.execute do |db|
|
111
|
+
query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id, aggregate_type: aggregate_type)
|
112
|
+
query = query.where{sequence_number > event_id}
|
113
|
+
rel = "#{events_table_name}__aggregate_version".to_sym
|
114
|
+
query = query.select(:aggregate_type, rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp)
|
115
|
+
query = query.limit(take) if take > 0
|
116
|
+
return query.order(:sequence_number).all
|
117
|
+
end
|
118
|
+
end
|
119
|
+
def get_events args = {}
|
120
|
+
classes = args.fetch(:classes, [])
|
121
|
+
after_sequence_number = args.fetch(:after_sequence_number) { raise ArgumentError.new "Must provide a :after_sequence_number-argument. 1 is first sequence_number, so use 0 if starting over." }
|
122
|
+
take = args.fetch(:take, 0)
|
123
|
+
include_events = args.fetch(:include_events, []).collect { |e| e.to_s }
|
124
|
+
exclude_events = args.fetch(:exclude_events, []).collect { |e| e.to_s }
|
125
|
+
aggregate_types = classes.collect { |e| e.to_s }
|
126
|
+
driver.execute do |db|
|
127
|
+
if aggregate_types.empty?
|
128
|
+
query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id)
|
129
|
+
else
|
130
|
+
query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id, aggregate_type: aggregate_types)
|
131
|
+
end
|
132
|
+
query = query.where{sequence_number > after_sequence_number}
|
133
|
+
unless include_events.empty?
|
134
|
+
query = query.where(event_name: include_events)
|
135
|
+
end
|
136
|
+
unless exclude_events.empty?
|
137
|
+
query = query.exclude(event_name: exclude_events)
|
138
|
+
end
|
139
|
+
rel = "#{events_table_name}__aggregate_version".to_sym
|
140
|
+
query = query.select(:aggregate_type, rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp)
|
141
|
+
query = query.limit(take) if take > 0
|
142
|
+
return query.order(:sequence_number).all
|
143
|
+
end
|
144
|
+
end
|
145
|
+
def obsolete_snapshots class_names: [], max_event_distance: 100
|
146
|
+
driver.execute do |db|
|
147
|
+
rel = "#{snapshots_table_name}__aggregate_version".to_sym
|
148
|
+
aggr_rel = "#{aggregates_table_name}__aggregate_version".to_sym
|
149
|
+
query_select = eval("lambda{(#{aggr_rel} - coalesce(#{rel},0)).as(distance)}")
|
150
|
+
query = db[aggregates_table_name].left_outer_join(snapshots_table_name, aggregate_table_id: :id)
|
151
|
+
query = query.select &query_select
|
152
|
+
query = query.select_append(:aggregate_id, :aggregate_type)
|
153
|
+
query_where = eval("lambda{(#{aggr_rel} - coalesce(#{rel},0)) > max_event_distance}")
|
154
|
+
query = query.where &query_where
|
155
|
+
unless class_names.empty?
|
156
|
+
class_names.map! {|c|c.to_s}
|
157
|
+
query = query.where(aggregate_type: class_names)
|
158
|
+
end
|
159
|
+
query.all
|
160
|
+
end
|
161
|
+
end
|
162
|
+
private
|
163
|
+
def aggregate_events after_aggregate_version: 0, aggregate_id: nil
|
164
|
+
rel = "#{events_table_name}__aggregate_version".to_sym
|
165
|
+
where_proc = eval("lambda{ #{rel} > after_aggregate_version }")
|
166
|
+
driver.execute do |db|
|
167
|
+
query = db[events_table_name].join(aggregates_table_name, id: :aggregate_table_id, aggregate_id: aggregate_id)
|
168
|
+
query = query.where &where_proc
|
169
|
+
return query.select(rel, :aggregate_id, :sequence_number, :event_name, :event_data, :timestamp).order(:sequence_number).all
|
170
|
+
end
|
171
|
+
end
|
172
|
+
def get_current_aggregate_from_aggregates_table aggregate_id, aggregate_type, db
|
173
|
+
aggregate_type = aggregate_type.to_s
|
174
|
+
current_aggregate = db[aggregates_table_name].where(aggregate_id: aggregate_id)
|
175
|
+
if current_aggregate.empty?
|
176
|
+
error_message = "#{aggregate_type} with id #{aggregate_id} was not found in the eventstore."
|
177
|
+
raise SandthornDriverSequel::Errors::NoAggregateError.new(error_message)
|
178
|
+
end
|
179
|
+
current_aggregate.first
|
180
|
+
end
|
181
|
+
def get_current_snapshot aggregate_table_id, db
|
182
|
+
snap = db[snapshots_table_name].where(aggregate_table_id: aggregate_table_id)
|
183
|
+
return nil if snap.empty?
|
184
|
+
snap.first
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SandthornDriverSequel
|
2
|
+
module EventStoreContext
|
3
|
+
def events_table_name
|
4
|
+
with_context_if_exists :events
|
5
|
+
end
|
6
|
+
def aggregates_table_name
|
7
|
+
with_context_if_exists :aggregates
|
8
|
+
end
|
9
|
+
def snapshots_table_name
|
10
|
+
with_context_if_exists :snapshots
|
11
|
+
end
|
12
|
+
def with_context_if_exists name
|
13
|
+
name = "#{context}_#{name}".to_sym if context
|
14
|
+
name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'sandthorn_driver_sequel/sequel_driver'
|
2
|
+
module SandthornDriverSequel
|
3
|
+
class Migration
|
4
|
+
include EventStoreContext
|
5
|
+
attr_reader :driver, :context
|
6
|
+
def initialize url: nil, context: nil
|
7
|
+
@driver = SequelDriver.new url: url
|
8
|
+
@context = context
|
9
|
+
end
|
10
|
+
def migrate!
|
11
|
+
ensure_migration_table!
|
12
|
+
aggregates
|
13
|
+
events
|
14
|
+
snapshots
|
15
|
+
end
|
16
|
+
private
|
17
|
+
def clear_for_test
|
18
|
+
driver.execute do |db|
|
19
|
+
db[snapshots_table_name].truncate
|
20
|
+
db[events_table_name].truncate
|
21
|
+
db[aggregates_table_name].truncate
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def aggregates
|
25
|
+
aggr_migration_0 = "#{aggregates_table_name}-20130308"
|
26
|
+
unless has_been_migrated?(aggr_migration_0)
|
27
|
+
driver.execute_in_transaction do |db|
|
28
|
+
db.create_table(aggregates_table_name) do
|
29
|
+
primary_key :id
|
30
|
+
String :aggregate_id, fixed: true, size: 36, null: false
|
31
|
+
Integer :aggregate_version, null: false
|
32
|
+
String :aggregate_type, size: 255, null: false
|
33
|
+
index [:aggregate_type]
|
34
|
+
index [:aggregate_type, :aggregate_id], unique: true
|
35
|
+
index [:aggregate_id], unique: true
|
36
|
+
end
|
37
|
+
was_migrated aggr_migration_0, db
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def events
|
42
|
+
events_migration_0 = "#{events_table_name}-20130308"
|
43
|
+
unless has_been_migrated?(events_migration_0)
|
44
|
+
driver.execute_in_transaction do |db|
|
45
|
+
aggr_table = aggregates_table_name
|
46
|
+
db.create_table(events_table_name) do
|
47
|
+
primary_key :sequence_number
|
48
|
+
foreign_key :aggregate_table_id, aggr_table, on_update: :cascade
|
49
|
+
Integer :aggregate_version, null: false
|
50
|
+
String :event_name, size: 255, null: false
|
51
|
+
String :event_data, text: true, null: true
|
52
|
+
DateTime :timestamp, null: false
|
53
|
+
|
54
|
+
index [:event_name]
|
55
|
+
end
|
56
|
+
was_migrated events_migration_0, db
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
events_migration_1 = "#{events_table_name}-20131004"
|
61
|
+
unless has_been_migrated?(events_migration_1)
|
62
|
+
driver.execute_in_transaction do |db|
|
63
|
+
db.alter_table events_table_name do
|
64
|
+
add_index [:aggregate_table_id]
|
65
|
+
add_index [:aggregate_table_id,:aggregate_version], unique: true
|
66
|
+
end
|
67
|
+
was_migrated events_migration_1, db
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
def snapshots
|
72
|
+
snapshot_migration_0 = "#{snapshots_table_name}-20130312"
|
73
|
+
unless has_been_migrated?(snapshot_migration_0)
|
74
|
+
driver.execute_in_transaction do |db|
|
75
|
+
aggr_table = aggregates_table_name
|
76
|
+
db.create_table(snapshots_table_name) do
|
77
|
+
primary_key :id
|
78
|
+
Integer :aggregate_version, null: false
|
79
|
+
String :snapshot_data, text: true, null: false
|
80
|
+
foreign_key :aggregate_table_id, aggr_table, on_delete: :cascade, on_update: :cascade
|
81
|
+
index [:aggregate_table_id], unique: true
|
82
|
+
end
|
83
|
+
was_migrated snapshot_migration_0, db
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def migration_table_name
|
89
|
+
:event_store_sequel_migrations
|
90
|
+
end
|
91
|
+
def ensure_migration_table!
|
92
|
+
driver.execute do |db|
|
93
|
+
db.create_table?(migration_table_name) do
|
94
|
+
primary_key :id
|
95
|
+
String :migration_name, null: false
|
96
|
+
index [:migration_name], unique: true
|
97
|
+
DateTime :timestamp, :null=>false
|
98
|
+
index [:migration_name], unique: true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
def has_been_migrated? migration_name
|
103
|
+
driver.execute {|db| db[migration_table_name].all.any? { |e| e[:migration_name]==migration_name } }
|
104
|
+
end
|
105
|
+
def was_migrated migration_name, db
|
106
|
+
db[migration_table_name].insert timestamp: Time.now.utc, migration_name: migration_name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
class SequelDriver
|
5
|
+
def initialize args = {}
|
6
|
+
@url = args.fetch(:url)
|
7
|
+
Sequel.default_timezone = :utc
|
8
|
+
end
|
9
|
+
def execute &block
|
10
|
+
Sequel.connect(@url) { |db| return block.call db}
|
11
|
+
end
|
12
|
+
def execute_in_transaction &block
|
13
|
+
Sequel.connect(@url) do |db|
|
14
|
+
db.transaction do
|
15
|
+
return block.call db
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "sandthorn_driver_sequel/version"
|
2
|
+
require "sandthorn_driver_sequel/event_store_context"
|
3
|
+
require 'sandthorn_driver_sequel/event_store'
|
4
|
+
require 'sandthorn_driver_sequel/errors'
|
5
|
+
require 'sandthorn_driver_sequel/migration'
|
6
|
+
|
7
|
+
module SandthornDriverSequel
|
8
|
+
class << self
|
9
|
+
def driver_from_url url: nil, context: nil
|
10
|
+
EventStore.new url: url, context: context
|
11
|
+
end
|
12
|
+
def migrate_db url: nil, context: nil
|
13
|
+
migrator = Migration.new url: url, context: context
|
14
|
+
migrator.migrate!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sandthorn_driver_sequel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sandthorn_driver_sequel"
|
8
|
+
spec.version = SandthornDriverSequel::VERSION
|
9
|
+
spec.authors = ["Lars Krantz", "Morgan Hallgren"]
|
10
|
+
spec.email = ["lars.krantz@alaz.se", "morgan.hallgren@gmail.com"]
|
11
|
+
spec.description = %q{sequel driver for sandthorn}
|
12
|
+
spec.summary = %q{sequel driver for sandthorn}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "gem-release"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "pry-doc"
|
29
|
+
spec.add_development_dependency "awesome_print"
|
30
|
+
spec.add_development_dependency "autotest-standalone"
|
31
|
+
spec.add_development_dependency "uuidtools"
|
32
|
+
spec.add_development_dependency "ruby-beautify"
|
33
|
+
|
34
|
+
spec.add_runtime_dependency "sequel"
|
35
|
+
spec.add_runtime_dependency "pg"
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Foo; end
|
4
|
+
class Bar; end
|
5
|
+
|
6
|
+
module SandthornDriverSequel
|
7
|
+
|
8
|
+
describe EventStore do
|
9
|
+
before(:each) { prepare_for_test }
|
10
|
+
context "when asking for aggregates to snapshot" do
|
11
|
+
let(:aggregates) {
|
12
|
+
[{id: "1", class_name: Foo}, {id: "2", class_name: Bar},{id: "3", class_name: Foo}]}
|
13
|
+
|
14
|
+
before(:each) {save_test_events}
|
15
|
+
|
16
|
+
context "when asking for type 'Bar' and max event count 5" do
|
17
|
+
let(:needs_snapshot) { event_store.obsolete_snapshots class_names: [Bar], max_event_distance: 5 }
|
18
|
+
context "and no snapshots exist" do
|
19
|
+
it "should return that id 2 with class Bar need to be snapshotted" do
|
20
|
+
expect(needs_snapshot.length).to eql 1
|
21
|
+
expect(needs_snapshot.first[:aggregate_id]).to eql aggregates[1][:id]
|
22
|
+
expect(needs_snapshot.first[:aggregate_type]).to eql "Bar"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
context "and a recent snapshot exists" do
|
26
|
+
before(:each) { event_store.save_snapshot({ event_data: "YO MAN", aggregate_version: 11 }, aggregates[1][:id], aggregates[1][:class_name])}
|
27
|
+
it "should return empty array" do
|
28
|
+
expect(needs_snapshot).to be_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def save_test_events
|
35
|
+
for_1 = event_generator count: 4, start_at: 1
|
36
|
+
for_2 = event_generator count: 3, start_at: 1
|
37
|
+
for_3 = event_generator count: 6, start_at: 1
|
38
|
+
for_2_2 = event_generator count: 10, start_at: 4
|
39
|
+
for_1_2 = event_generator count: 1, start_at: 5
|
40
|
+
save_events for_1, 0
|
41
|
+
save_events for_2, 1
|
42
|
+
save_events for_3, 2
|
43
|
+
save_events for_1_2, 0
|
44
|
+
save_events for_2_2, 1
|
45
|
+
end
|
46
|
+
def save_events events, aggregate_index
|
47
|
+
event_store.save_events events, events.first[:aggregate_version]-1, aggregates[aggregate_index][:id], aggregates[aggregate_index][:class_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
def event_generator count: 1, start_at: 1
|
51
|
+
events = []
|
52
|
+
i = 0
|
53
|
+
while i < count do
|
54
|
+
events << { aggregate_version: i+start_at, event_data: nil, event_name: "event_foo_#{i}" }
|
55
|
+
i += 1
|
56
|
+
end
|
57
|
+
events
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
Binary file
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
describe EventStore do
|
5
|
+
before(:each) { prepare_for_test }
|
6
|
+
context "interface structure" do
|
7
|
+
let(:subject) {event_store}
|
8
|
+
it "should respond to save_events" do
|
9
|
+
subject.should respond_to("save_events")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should respond to save_snapshot" do
|
13
|
+
subject.should respond_to("save_snapshot")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should respond to get_aggregate" do
|
17
|
+
subject.should respond_to("get_aggregate")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should respond to get_aggregate_events" do
|
21
|
+
subject.should respond_to("get_aggregate_events")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should respond to get_aggregate_list_by_typename" do
|
25
|
+
subject.should respond_to("get_aggregate_list_by_typename")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should respond to get_all_typenames" do
|
29
|
+
subject.should respond_to("get_all_typenames")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should respond to get_snapshot" do
|
33
|
+
subject.should respond_to("get_snapshot")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should respond to get_new_events_after_event_id_matching_classname" do
|
37
|
+
subject.should respond_to("get_new_events_after_event_id_matching_classname")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should respond to get_events_after_sequence_id" do
|
41
|
+
subject.should respond_to(:get_events)
|
42
|
+
end
|
43
|
+
it("should respond to url"){ expect(subject).to respond_to :url }
|
44
|
+
it("should respond to context"){ expect(subject).to respond_to :context }
|
45
|
+
it("should respond to driver"){ expect(subject).to respond_to :driver }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SandthornDriverSequel
|
3
|
+
describe EventStore do
|
4
|
+
let(:context) { :event_store_spec }
|
5
|
+
before(:each) { prepare_for_test context: context; prepare_for_test context: nil; }
|
6
|
+
let(:event_store_with_context) { EventStore.new url: event_store_url, context: context }
|
7
|
+
let(:event_store_without_context) { EventStore.new url: event_store_url }
|
8
|
+
context("when saving in one context and retreiving in another") do
|
9
|
+
let(:test_events) do
|
10
|
+
e = []
|
11
|
+
e << {aggregate_version: 1, event_name: "new", event_args: nil, event_data: "---\n:method_name: new\n:method_args: []\n:attribute_deltas:\n- :attribute_name: :@aggregate_id\n :old_value: \n :new_value: 0a74e545-be84-4506-8b0a-73e947856327\n"}
|
12
|
+
e << {aggregate_version: 2, event_name: "foo", event_args: ["bar"], event_data: "noop"}
|
13
|
+
e << {aggregate_version: 3, event_name: "flubber", event_args: ["bar"] , event_data: "noop"}
|
14
|
+
end
|
15
|
+
let(:aggregate_id) {"c0456e26-e29a-4f67-92fa-130b3a31a39b"}
|
16
|
+
it "should not find them" do
|
17
|
+
event_store_without_context.save_events test_events, 0, aggregate_id, String
|
18
|
+
events = event_store_without_context.get_aggregate_events aggregate_id, String
|
19
|
+
expect(events.length).to eql test_events.length
|
20
|
+
events_2 = event_store_with_context.get_aggregate_events aggregate_id, String
|
21
|
+
expect(events_2.length).to eql 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
describe EventStore do
|
5
|
+
before(:each) { prepare_for_test }
|
6
|
+
let(:test_events_a) do
|
7
|
+
e = []
|
8
|
+
e << {aggregate_version: 1, event_name: "new", event_data: "---\n:method_name: new\n:method_args: []\n:attribute_deltas:\n- :attribute_name: :@aggregate_id\n :old_value: \n :new_value: 0a74e545-be84-4506-8b0a-73e947856327\n"}
|
9
|
+
e << {aggregate_version: 2, event_name: "foo", event_data: "A2"}
|
10
|
+
e << {aggregate_version: 3, event_name: "bard", event_data: "A3"}
|
11
|
+
end
|
12
|
+
let(:aggregate_id_a) {"c0456e26-e29a-4f67-92fa-130b3a31a39a"}
|
13
|
+
let(:test_events_b) do
|
14
|
+
e = []
|
15
|
+
e << {aggregate_version: 1, event_name: "new", event_data: "B1" }
|
16
|
+
e << {aggregate_version: 2, event_name: "foo", event_data: "B2"}
|
17
|
+
e << {aggregate_version: 3, event_name: "bar", event_data: "B3"}
|
18
|
+
end
|
19
|
+
let(:aggregate_id_b) {"c0456e26-1234-4f67-92fa-130b3a31a39a"}
|
20
|
+
let(:test_events_c) do
|
21
|
+
e = []
|
22
|
+
e << {aggregate_version: 1, event_name: "new", event_data: "C1" }
|
23
|
+
end
|
24
|
+
let(:test_events_c_2) do
|
25
|
+
e = []
|
26
|
+
e << {aggregate_version: 2, event_name: "flubber", event_data: "C2" }
|
27
|
+
end
|
28
|
+
let(:aggregate_id_c) {"c0456e26-2345-4f67-92fa-130b3a31a39a"}
|
29
|
+
before(:each) do
|
30
|
+
event_store.save_events test_events_a, 0, aggregate_id_a, SandthornDriverSequel::EventStore
|
31
|
+
event_store.save_events test_events_c, 0, aggregate_id_c, String
|
32
|
+
event_store.save_events test_events_b, 0, aggregate_id_b, SandthornDriverSequel::SequelDriver
|
33
|
+
event_store.save_events test_events_c_2, 1, aggregate_id_c, String
|
34
|
+
end
|
35
|
+
context "when using get_events" do
|
36
|
+
context "and using take" do
|
37
|
+
let(:events) {event_store.get_events after_sequence_number: 0, include_events: [:new], take: 2}
|
38
|
+
it "should find 2 events" do
|
39
|
+
events.length.should eql 2
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context "and getting events of type :new" do
|
43
|
+
let(:events) {event_store.get_events after_sequence_number: 0, include_events: [:new]}
|
44
|
+
it "should find 3 events" do
|
45
|
+
events.length.should eql 3
|
46
|
+
end
|
47
|
+
it "should only be new-events" do
|
48
|
+
events.all? { |e| e[:event_name] == "new" }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context "and combining args" do
|
52
|
+
let(:events) do
|
53
|
+
all = event_store.get_events after_sequence_number: 0
|
54
|
+
first_seq_number = all[0][:sequence_number]
|
55
|
+
event_store.get_events after_sequence_number: first_seq_number , exclude_events: [:foo], include_events: [:new, :foo, "bar", :flubber], take: 100
|
56
|
+
end
|
57
|
+
it "should find 4 events" do
|
58
|
+
events.length.should eql 4
|
59
|
+
end
|
60
|
+
it "should not be any foo-events" do
|
61
|
+
events.all? { |e| e[:event_name] != "foo" }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
context "and getting all events but excluding new" do
|
65
|
+
let(:events) {event_store.get_events after_sequence_number: 0, exclude_events: [:new] }
|
66
|
+
it "should find 5 events" do
|
67
|
+
events.length.should eql 5
|
68
|
+
end
|
69
|
+
it "should only be new and foo-events" do
|
70
|
+
events.all? { |e| e[:event_name] != "new" }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
context "and getting events of type :new and foo" do
|
74
|
+
let(:events) {event_store.get_events after_sequence_number: 0, classes: ["String", SandthornDriverSequel::EventStore], include_events: [:new, "foo"]}
|
75
|
+
it "should find 3 events" do
|
76
|
+
events.length.should eql 3
|
77
|
+
end
|
78
|
+
it "should only be new and foo-events" do
|
79
|
+
events.all? { |e| e[:event_name] == "new" || e[:event_name] == "foo" }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
context "and getting events for SandthornDriverSequel::EventStore, and String after 0" do
|
83
|
+
let(:events) {event_store.get_events after_sequence_number: 0, classes: [SandthornDriverSequel::EventStore, String]}
|
84
|
+
it "should find 5 events" do
|
85
|
+
events.length.should eql 5
|
86
|
+
end
|
87
|
+
it "should be in sequence_number order" do
|
88
|
+
check = 0
|
89
|
+
events.each { |e| e[:sequence_number].should be > check; check = e[:sequence_number] }
|
90
|
+
end
|
91
|
+
it "should contain only events for aggregate_id_a and aggregate_id_c" do
|
92
|
+
events.each { |e| [aggregate_id_a, aggregate_id_c].include?(e[:aggregate_id]).should be_true }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
context "and getting events for SandthornDriverSequel::EventStore after 0" do
|
96
|
+
let(:events) {event_store.get_events after_sequence_number: 0, classes: [SandthornDriverSequel::EventStore]}
|
97
|
+
it "should find 3 events" do
|
98
|
+
events.length.should eql 3
|
99
|
+
end
|
100
|
+
it "should be in sequence_number order" do
|
101
|
+
check = 0
|
102
|
+
events.each { |e| e[:sequence_number].should be > check; check = e[:sequence_number] }
|
103
|
+
end
|
104
|
+
it "should contain only events for aggregate_id_a" do
|
105
|
+
events.each { |e| e[:aggregate_id].should eql aggregate_id_a }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
context "when using :get_new_events_after_event_id_matching_classname to get events" do
|
110
|
+
context "and getting events for SandthornDriverSequel::EventStore after 0" do
|
111
|
+
let(:events) {event_store.get_new_events_after_event_id_matching_classname 0, SandthornDriverSequel::EventStore}
|
112
|
+
it "should find 3 events" do
|
113
|
+
events.length.should eql 3
|
114
|
+
end
|
115
|
+
it "should be in sequence_number order" do
|
116
|
+
check = 0
|
117
|
+
events.each { |e| e[:sequence_number].should be > check; check = e[:sequence_number] }
|
118
|
+
end
|
119
|
+
it "should contain only events for aggregate_id_a" do
|
120
|
+
events.each { |e| e[:aggregate_id].should eql aggregate_id_a }
|
121
|
+
end
|
122
|
+
it "should be able to get events after a sequence number" do
|
123
|
+
new_from = events[1][:sequence_number]
|
124
|
+
ev = event_store.get_new_events_after_event_id_matching_classname new_from, SandthornDriverSequel::EventStore
|
125
|
+
ev.last[:aggregate_version].should eql 3
|
126
|
+
ev.length.should eql 1
|
127
|
+
end
|
128
|
+
it "should be able to limit the number of results" do
|
129
|
+
ev = event_store.get_new_events_after_event_id_matching_classname 0, SandthornDriverSequel::EventStore, take: 2
|
130
|
+
ev.length.should eql 2
|
131
|
+
ev.last[:aggregate_version].should eql 2
|
132
|
+
end
|
133
|
+
end
|
134
|
+
context "and getting events for String after 0" do
|
135
|
+
let(:events) {event_store.get_new_events_after_event_id_matching_classname 0, "String"}
|
136
|
+
it "should find 3 events" do
|
137
|
+
events.length.should eql 2
|
138
|
+
end
|
139
|
+
it "should be in sequence_number order" do
|
140
|
+
check = 0
|
141
|
+
events.each { |e| e[:sequence_number].should be > check; check = e[:sequence_number] }
|
142
|
+
end
|
143
|
+
it "should contain only events for aggregate_id_c" do
|
144
|
+
events.each { |e| e[:aggregate_id].should eql aggregate_id_c }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
describe Migration do
|
5
|
+
def check_tables context = nil
|
6
|
+
events = :events
|
7
|
+
aggregates = :aggregates
|
8
|
+
snapshots = :snapshots
|
9
|
+
if context
|
10
|
+
events = "#{context}_#{events}".to_sym
|
11
|
+
aggregates = "#{context}_#{aggregates}".to_sym
|
12
|
+
snapshots = "#{context}_#{snapshots}".to_sym
|
13
|
+
end
|
14
|
+
migration.driver.execute do |db|
|
15
|
+
expect(db.table_exists? events).to be_true, "expected table :#{events} to exist, but it didn't"
|
16
|
+
expect(db.table_exists? aggregates).to be_true, "expected table :#{aggregates} to exist, but it didn't"
|
17
|
+
expect(db.table_exists? snapshots).to be_true, "expected table :#{snapshots} to exist, but it didn't"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
let(:migration) { Migration.new url: event_store_url, context: context }
|
21
|
+
before(:each) { migration.migrate! }
|
22
|
+
context "when default (nil) eventstore contex" do
|
23
|
+
let(:context) { nil }
|
24
|
+
it "should create the tables events, aggregates and snapshots" do
|
25
|
+
check_tables
|
26
|
+
end
|
27
|
+
end
|
28
|
+
context "when specifying context" do
|
29
|
+
let(:context) { :another_domain }
|
30
|
+
it "should create the tables events, aggregates and snapshots" do
|
31
|
+
check_tables context
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
describe EventStore do
|
5
|
+
before(:each) { prepare_for_test }
|
6
|
+
context "when saving a prefectly sane event stream" do
|
7
|
+
let(:test_events) do
|
8
|
+
e = []
|
9
|
+
e << {aggregate_version: 1, event_name: "new", event_args: nil, event_data: "---\n:method_name: new\n:method_args: []\n:attribute_deltas:\n- :attribute_name: :@aggregate_id\n :old_value: \n :new_value: 0a74e545-be84-4506-8b0a-73e947856327\n"}
|
10
|
+
e << {aggregate_version: 2, event_name: "foo", event_args: ["bar"], event_data: "noop"}
|
11
|
+
e << {aggregate_version: 3, event_name: "flubber", event_args: ["bar"] , event_data: "noop"}
|
12
|
+
end
|
13
|
+
let(:aggregate_id) {"c0456e26-e29a-4f67-92fa-130b3a31a39a"}
|
14
|
+
it "should be able to save and retreive events on the aggregate" do
|
15
|
+
event_store.save_events test_events, 0, aggregate_id, String
|
16
|
+
events = event_store.get_aggregate_events aggregate_id, String
|
17
|
+
events.length.should eql test_events.length
|
18
|
+
end
|
19
|
+
it "should fail if aggregate does not exist and version is above 0" do
|
20
|
+
expect { event_store.save_events test_events, 1, aggregate_id, String }.to raise_error SandthornDriverSequel::Errors::NoAggregateError
|
21
|
+
end
|
22
|
+
it "should fail if originating version is wrong" do
|
23
|
+
event_store.save_events test_events, 0, aggregate_id, String
|
24
|
+
expect { event_store.save_events test_events, 102, aggregate_id, String }.to raise_error SandthornDriverSequel::Errors::WrongAggregateVersionError
|
25
|
+
end
|
26
|
+
it "should have correct keys when asking for events" do
|
27
|
+
event_store.save_events test_events, 0, aggregate_id, String
|
28
|
+
events = event_store.get_aggregate aggregate_id, String
|
29
|
+
event = events.first
|
30
|
+
event[:event_data].should eql(test_events.first[:event_data])
|
31
|
+
event[:event_name].should eql("new")
|
32
|
+
event[:aggregate_id].should eql aggregate_id
|
33
|
+
event[:aggregate_version].should eql 1
|
34
|
+
event[:sequence_number].should be_a(Fixnum)
|
35
|
+
event[:timestamp].should be_a(Time)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context "when saving two aggregate types" do
|
39
|
+
let(:test_events_1) do
|
40
|
+
e = []
|
41
|
+
e << {aggregate_version: 1, event_name: "new", event_args: nil, event_data: "---\n:method_name: new\n:method_args: []\n:attribute_deltas:\n- :attribute_name: :@aggregate_id\n :old_value: \n :new_value: 0a74e545-be84-4506-8b0a-73e947856327\n"}
|
42
|
+
e << {aggregate_version: 2, event_name: "foo", event_args: ["bar", event_data: "noop"]}
|
43
|
+
e << {aggregate_version: 3, event_name: "flubber", event_args: ["bar", event_data: "noop"]}
|
44
|
+
end
|
45
|
+
let(:test_events_2) do
|
46
|
+
e = []
|
47
|
+
e << {aggregate_version: 1, event_name: "new", event_args: nil, event_data: "---\n:method_name: new\n:method_args: []\n:attribute_deltas:\n- :attribute_name: :@aggregate_id\n :old_value: \n :new_value: 0a74e545-be84-4506-8b0a-73e947856327\n"}
|
48
|
+
end
|
49
|
+
let(:aggregate_id_1) {"c0456e26-e29a-4f67-92fa-130b3a31a39a"}
|
50
|
+
let(:aggregate_id_2) {"c0456e26-e92b-4f67-92fa-130b3a31b93b"}
|
51
|
+
let(:aggregate_id_3) {"c0456e26-e92b-1234-92fa-130b3a31b93b"}
|
52
|
+
before(:each) do
|
53
|
+
event_store.save_events test_events_1, 0, aggregate_id_1, String
|
54
|
+
event_store.save_events test_events_2, 0, aggregate_id_2, Hash
|
55
|
+
event_store.save_events test_events_2, 0, aggregate_id_3, String
|
56
|
+
end
|
57
|
+
it "both types should exist in get_all_typenames in alphabetical order" do
|
58
|
+
names = event_store.get_all_typenames
|
59
|
+
names.length.should eql 2
|
60
|
+
names.first.should eql "Hash"
|
61
|
+
names.last.should eql "String"
|
62
|
+
end
|
63
|
+
it "should list the aggregate ids when asking for get_aggregate_list_by_typename" do
|
64
|
+
ids = event_store.get_aggregate_list_by_typename String
|
65
|
+
ids.length.should eql 2
|
66
|
+
ids.any? { |e| e == aggregate_id_1 }.should be_true
|
67
|
+
ids.any? { |e| e == aggregate_id_3 }.should be_true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SandthornDriverSequel
|
4
|
+
describe EventStore do
|
5
|
+
before(:each) { prepare_for_test }
|
6
|
+
let(:aggregate_id) { @id ||= UUIDTools::UUID.random_create.to_s }
|
7
|
+
let(:test_events) { [{aggregate_version: 1, event_data: nil, event_name: "new"},{aggregate_version: 2, event_data: nil, event_name: "foo"}] }
|
8
|
+
let(:additional_events) { [{aggregate_version: 3, event_data: nil, event_name: "klopp"},{aggregate_version: 4, event_data: nil, event_name: "flipp"}] }
|
9
|
+
let(:snapshot_data) { { event_data: "YO MAN", aggregate_version: 2 } }
|
10
|
+
let(:save_snapshot) {event_store.save_snapshot snapshot_data, aggregate_id, SandthornDriverSequel::EventStore}
|
11
|
+
let(:save_events) {event_store.save_events test_events, 0, aggregate_id, SandthornDriverSequel::EventStore}
|
12
|
+
let(:save_additional_events) {event_store.save_events additional_events, 2, aggregate_id, SandthornDriverSequel::EventStore}
|
13
|
+
context "when loading an aggregate using get_aggregate" do
|
14
|
+
context "and it has a snapshot" do
|
15
|
+
before(:each) do
|
16
|
+
save_events
|
17
|
+
save_snapshot
|
18
|
+
save_additional_events
|
19
|
+
end
|
20
|
+
let(:events) { event_store.get_aggregate aggregate_id, SandthornDriverSequel::EventStore }
|
21
|
+
it "should have the first event as :aggregate_set_from_snapshot" do
|
22
|
+
expect(events.first[:event_name]).to eql "aggregate_set_from_snapshot"
|
23
|
+
end
|
24
|
+
it "should have additional events after first snapshot-event" do
|
25
|
+
expect(events.length).to eql 1+additional_events.length
|
26
|
+
expect(events[1][:aggregate_version]).to eql additional_events[0][:aggregate_version]
|
27
|
+
expect(events.last[:aggregate_version]).to eql additional_events.last[:aggregate_version]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
context "when saving a snapshot" do
|
33
|
+
|
34
|
+
context "and events are saved beforehand" do
|
35
|
+
before(:each) { save_events }
|
36
|
+
it "should be able to save snapshot" do
|
37
|
+
expect { save_snapshot }.to_not raise_error
|
38
|
+
end
|
39
|
+
it "should be able to save and get snapshot" do
|
40
|
+
save_snapshot
|
41
|
+
snap = event_store.get_snapshot aggregate_id, SandthornDriverSequel::EventStore
|
42
|
+
snap.should eql snapshot_data
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context "when trying to save a snapshot on a non-existing aggregate" do
|
46
|
+
it "should raise a NonAggregateError" do
|
47
|
+
expect { save_snapshot }.to raise_error SandthornDriverSequel::Errors::NoAggregateError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
context "when trying to save a snapshot with a non-existing aggregate_version" do
|
51
|
+
before(:each) { save_events }
|
52
|
+
it "should raise a WrongAggregateVersion error" do
|
53
|
+
data = snapshot_data
|
54
|
+
data[:aggregate_version] = 100
|
55
|
+
expect {event_store.save_snapshot data, aggregate_id, SandthornDriverSequel::EventStore}.to raise_error SandthornDriverSequel::Errors::WrongAggregateVersionError
|
56
|
+
end
|
57
|
+
end
|
58
|
+
context "when saving a snapshot twice" do
|
59
|
+
before(:each) { save_events; save_snapshot }
|
60
|
+
it "should not raise error" do
|
61
|
+
expect { save_snapshot }.to_not raise_error
|
62
|
+
end
|
63
|
+
end
|
64
|
+
context "when saving a snapshot on a version less than current version" do
|
65
|
+
before(:each) { save_events; }
|
66
|
+
it "should save without protesting" do
|
67
|
+
data = snapshot_data
|
68
|
+
data[:aggregate_version] = 1
|
69
|
+
event_store.save_snapshot data, aggregate_id, SandthornDriverSequel::EventStore
|
70
|
+
snap = event_store.get_snapshot aggregate_id, SandthornDriverSequel::EventStore
|
71
|
+
snap.should eql data
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'sandthorn_driver_sequel'
|
2
|
+
require 'sandthorn_driver_sequel/migration'
|
3
|
+
require 'ap'
|
4
|
+
require 'uuidtools'
|
5
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
6
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
7
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
8
|
+
# loaded once.
|
9
|
+
#
|
10
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
17
|
+
def prepare_for_test context: :test
|
18
|
+
migrator = SandthornDriverSequel::Migration.new url: event_store_url, context: context
|
19
|
+
migrator.migrate!
|
20
|
+
migrator.send(:clear_for_test)
|
21
|
+
end
|
22
|
+
|
23
|
+
def event_store_url
|
24
|
+
"sqlite://spec/db/event_store.sqlite3"
|
25
|
+
end
|
26
|
+
|
27
|
+
def event_store context: :test
|
28
|
+
SandthornDriverSequel::EventStore.new url: event_store_url, context: context
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sandthorn_driver_sequel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lars Krantz
|
8
|
+
- Morgan Hallgren
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-02-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: gem-release
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: sqlite3
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: pry-doc
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: awesome_print
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: autotest-standalone
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: uuidtools
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: ruby-beautify
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: sequel
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
type: :runtime
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: pg
|
184
|
+
requirement: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
type: :runtime
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
description: sequel driver for sandthorn
|
197
|
+
email:
|
198
|
+
- lars.krantz@alaz.se
|
199
|
+
- morgan.hallgren@gmail.com
|
200
|
+
executables: []
|
201
|
+
extensions: []
|
202
|
+
extra_rdoc_files: []
|
203
|
+
files:
|
204
|
+
- ".gitignore"
|
205
|
+
- ".rspec"
|
206
|
+
- ".ruby-gemset"
|
207
|
+
- ".ruby-version"
|
208
|
+
- Gemfile
|
209
|
+
- LICENSE.txt
|
210
|
+
- README.md
|
211
|
+
- Rakefile
|
212
|
+
- lib/sandthorn_driver_sequel.rb
|
213
|
+
- lib/sandthorn_driver_sequel/errors.rb
|
214
|
+
- lib/sandthorn_driver_sequel/event_store.rb
|
215
|
+
- lib/sandthorn_driver_sequel/event_store_context.rb
|
216
|
+
- lib/sandthorn_driver_sequel/migration.rb
|
217
|
+
- lib/sandthorn_driver_sequel/sequel_driver.rb
|
218
|
+
- lib/sandthorn_driver_sequel/version.rb
|
219
|
+
- sandthorn_driver_sequel.gemspec
|
220
|
+
- spec/asking_for_aggregates_to_snapshot_spec.rb
|
221
|
+
- spec/db/event_store.sqlite3
|
222
|
+
- spec/driver_interface_spec.rb
|
223
|
+
- spec/event_store_with_context_spec.rb
|
224
|
+
- spec/get_events_spec.rb
|
225
|
+
- spec/migration_specifying_domain_spec.rb
|
226
|
+
- spec/saving_events_spec.rb
|
227
|
+
- spec/saving_snapshot_spec.rb
|
228
|
+
- spec/spec_helper.rb
|
229
|
+
homepage: ''
|
230
|
+
licenses:
|
231
|
+
- MIT
|
232
|
+
metadata: {}
|
233
|
+
post_install_message:
|
234
|
+
rdoc_options: []
|
235
|
+
require_paths:
|
236
|
+
- lib
|
237
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
238
|
+
requirements:
|
239
|
+
- - ">="
|
240
|
+
- !ruby/object:Gem::Version
|
241
|
+
version: '0'
|
242
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
|
+
requirements:
|
244
|
+
- - ">="
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
requirements: []
|
248
|
+
rubyforge_project:
|
249
|
+
rubygems_version: 2.2.2
|
250
|
+
signing_key:
|
251
|
+
specification_version: 4
|
252
|
+
summary: sequel driver for sandthorn
|
253
|
+
test_files:
|
254
|
+
- spec/asking_for_aggregates_to_snapshot_spec.rb
|
255
|
+
- spec/db/event_store.sqlite3
|
256
|
+
- spec/driver_interface_spec.rb
|
257
|
+
- spec/event_store_with_context_spec.rb
|
258
|
+
- spec/get_events_spec.rb
|
259
|
+
- spec/migration_specifying_domain_spec.rb
|
260
|
+
- spec/saving_events_spec.rb
|
261
|
+
- spec/saving_snapshot_spec.rb
|
262
|
+
- spec/spec_helper.rb
|
263
|
+
has_rdoc:
|