sandthorn 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +1 -3
- data/README.md +44 -0
- data/lib/sandthorn.rb +21 -4
- data/lib/sandthorn/aggregate_root_base.rb +22 -6
- data/lib/sandthorn/snapshot_store.rb +17 -0
- data/lib/sandthorn/version.rb +1 -1
- data/sandthorn.gemspec +1 -1
- data/spec/aggregate_root_spec.rb +11 -0
- data/spec/snapshot_spec.rb +68 -0
- data/spec/spec_helper.rb +2 -6
- metadata +7 -7
- data/Gemfile.lock.old +0 -54
- data/LICENSE.txt +0 -22
- data/lib/sandthorn/aggregate_root_snapshot.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6972dfa7eb47e800f74d28d1680403c805bb8f81
|
4
|
+
data.tar.gz: 5ff2d842c430fb11fe405f071698053163e943c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be7b451226e349e592599358cec26170fa32651b7b9cc27044a083a017b901ef5fe977b5c8e0673be89a167e99871538da1c4b90df32d088444e0f878f75bc5d
|
7
|
+
data.tar.gz: f3d545cd1be566ec4870e625ef99cdaa95a78d986308339a329fb251c504399349da9867ba073518e1c83996cea7947cdc10d22c05e5d8b573f4d63fac895650
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -241,6 +241,50 @@ end
|
|
241
241
|
|
242
242
|
In this case, the resulting events from the commands `new` and `mark` will have the trace `{ip: :127.0.0.1}` attached to them.
|
243
243
|
|
244
|
+
### `Sandthorn::AggregateRoot.unsaved_events?`
|
245
|
+
|
246
|
+
Check if there are unsaved events attached to the aggregate.
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
board = Board.new
|
250
|
+
board.mark :o, 0, 1
|
251
|
+
board.unsaved_events?
|
252
|
+
=> true
|
253
|
+
```
|
254
|
+
|
255
|
+
## Snapshot
|
256
|
+
|
257
|
+
If there is a lot of events saved to an aggregate it can take some time to reload the current state of the aggregate via the `.find` method. This is because all events belonging to the aggregate has to be fetched and iterated one by one to build its current state. The snapshot functionality makes it possible to store the current aggregate state and re-use it when loading the aggregate. The snapshot is used as a cache where only the events that has occurred after the snapshot has to be fetched and used to build the current state of the aggregate.
|
258
|
+
|
259
|
+
There is one global snapshot store where all snapshots are stored independent on aggregate_type. To enable snapshot on a aggregate_type the Class has to be added to the `snapshot_types` Array when configuring Sandthorn. The aggregate will now be stored to the snapshot_store on every `.save` and when using `.find` it will look for a snapshot of the requested aggregate.
|
260
|
+
|
261
|
+
Currently its only possible to store the snapshots in memory, so be careful not draining your applications memory space.
|
262
|
+
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
|
266
|
+
class Board
|
267
|
+
include Sandthorn::AggregateRoot
|
268
|
+
end
|
269
|
+
|
270
|
+
Sandthorn.configure do |c|
|
271
|
+
c.snapshot_types = [Board]
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
Its also possible to take manual snapshots without enabling snapshots on the aggregate_type.
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
board = Board.new
|
279
|
+
board.save
|
280
|
+
|
281
|
+
# Save snapshot of the board aggregate
|
282
|
+
Sandthorn.save_snapshot board
|
283
|
+
|
284
|
+
# Get snapshot
|
285
|
+
snapshot = Sandthorn.find_snapshot board.aggregate_id
|
286
|
+
```
|
287
|
+
|
244
288
|
## Bounded Context
|
245
289
|
|
246
290
|
A bounded context is a system divider that split large systems into smaller parts. [Bounded Context by Martin Fowler](http://martinfowler.com/bliki/BoundedContext.html)
|
data/lib/sandthorn.rb
CHANGED
@@ -2,6 +2,7 @@ require "sandthorn/version"
|
|
2
2
|
require "sandthorn/errors"
|
3
3
|
require "sandthorn/aggregate_root"
|
4
4
|
require "sandthorn/event_stores"
|
5
|
+
require "sandthorn/snapshot_store"
|
5
6
|
require 'yaml'
|
6
7
|
require 'securerandom'
|
7
8
|
|
@@ -10,6 +11,7 @@ module Sandthorn
|
|
10
11
|
extend Forwardable
|
11
12
|
|
12
13
|
def_delegators :configuration, :event_stores
|
14
|
+
def_delegators :configuration, :snapshot_store
|
13
15
|
|
14
16
|
def default_event_store
|
15
17
|
event_stores.default_store
|
@@ -39,12 +41,17 @@ module Sandthorn
|
|
39
41
|
event_store_for(aggregate_type).all(aggregate_type)
|
40
42
|
end
|
41
43
|
|
42
|
-
def find aggregate_id, aggregate_type
|
43
|
-
event_store_for(aggregate_type).find(aggregate_id, aggregate_type)
|
44
|
+
def find aggregate_id, aggregate_type, after_aggregate_version = 0
|
45
|
+
event_store_for(aggregate_type).find(aggregate_id, aggregate_type, after_aggregate_version)
|
44
46
|
end
|
45
47
|
|
46
|
-
def save_snapshot
|
47
|
-
raise "
|
48
|
+
def save_snapshot aggregate
|
49
|
+
raise Errors::SnapshotError, "Can't take snapshot on object with unsaved events" if aggregate.unsaved_events?
|
50
|
+
snapshot_store.save aggregate.aggregate_id, aggregate
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_snapshot aggregate_id
|
54
|
+
return snapshot_store.find aggregate_id
|
48
55
|
end
|
49
56
|
|
50
57
|
def find_event_store(name)
|
@@ -84,6 +91,16 @@ module Sandthorn
|
|
84
91
|
@event_stores.map_types data
|
85
92
|
end
|
86
93
|
|
94
|
+
def snapshot_store
|
95
|
+
@snapshot_store ||= SnapshotStore.new
|
96
|
+
end
|
97
|
+
|
98
|
+
def snapshot_types= aggregate_types
|
99
|
+
aggregate_types.each do |aggregate_type|
|
100
|
+
aggregate_type.snapshot(true)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
87
104
|
alias_method :event_stores=, :event_store=
|
88
105
|
end
|
89
106
|
end
|
@@ -29,6 +29,8 @@ module Sandthorn
|
|
29
29
|
@aggregate_originating_version = @aggregate_current_event_version
|
30
30
|
end
|
31
31
|
|
32
|
+
Sandthorn.save_snapshot self if self.class.snapshot
|
33
|
+
|
32
34
|
self
|
33
35
|
end
|
34
36
|
|
@@ -36,6 +38,10 @@ module Sandthorn
|
|
36
38
|
other.respond_to?(:aggregate_id) && aggregate_id == other.aggregate_id
|
37
39
|
end
|
38
40
|
|
41
|
+
def unsaved_events?
|
42
|
+
aggregate_events.any?
|
43
|
+
end
|
44
|
+
|
39
45
|
def aggregate_trace args
|
40
46
|
@aggregate_trace_information = args
|
41
47
|
yield self if block_given?
|
@@ -70,9 +76,17 @@ module Sandthorn
|
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
79
|
+
def snapshot(value = nil)
|
80
|
+
if value
|
81
|
+
@snapshot = value
|
82
|
+
else
|
83
|
+
@snapshot
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
73
87
|
def all
|
74
88
|
Sandthorn.all(self).map { |events|
|
75
|
-
aggregate_build events
|
89
|
+
aggregate_build events, nil
|
76
90
|
}
|
77
91
|
end
|
78
92
|
|
@@ -83,12 +97,14 @@ module Sandthorn
|
|
83
97
|
|
84
98
|
def aggregate_find aggregate_id
|
85
99
|
begin
|
86
|
-
|
87
|
-
|
100
|
+
aggregate_from_snapshot = Sandthorn.find_snapshot(aggregate_id) if self.snapshot
|
101
|
+
current_aggregate_version = aggregate_from_snapshot.nil? ? 0 : aggregate_from_snapshot.aggregate_current_event_version
|
102
|
+
events = Sandthorn.find(aggregate_id, self, current_aggregate_version)
|
103
|
+
if aggregate_from_snapshot.nil? && events.empty?
|
88
104
|
raise Errors::AggregateNotFound
|
89
105
|
end
|
90
106
|
|
91
|
-
return aggregate_build events
|
107
|
+
return aggregate_build events, aggregate_from_snapshot
|
92
108
|
rescue Exception
|
93
109
|
raise Errors::AggregateNotFound
|
94
110
|
end
|
@@ -111,8 +127,8 @@ module Sandthorn
|
|
111
127
|
|
112
128
|
end
|
113
129
|
|
114
|
-
def aggregate_build events
|
115
|
-
aggregate = create_new_empty_aggregate
|
130
|
+
def aggregate_build events, aggregate_from_snapshot = nil
|
131
|
+
aggregate = aggregate_from_snapshot || create_new_empty_aggregate
|
116
132
|
|
117
133
|
if events.any?
|
118
134
|
current_aggregate_version = events.last[:aggregate_version]
|
data/lib/sandthorn/version.rb
CHANGED
data/sandthorn.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Sandthorn::VERSION
|
9
9
|
spec.authors = ["Lars Krantz", "Morgan Hallgren", "Jesper Josefsson"]
|
10
10
|
spec.email = ["lars.krantz@alaz.se", "morgan.hallgren@gmail.com", "jesper.josefsson@gmail.com"]
|
11
|
-
spec.description = %q{Event sourcing
|
11
|
+
spec.description = %q{Event sourcing}
|
12
12
|
spec.summary = %q{Event sourcing gem}
|
13
13
|
spec.homepage = "https://github.com/Sandthorn/sandthorn"
|
14
14
|
spec.license = "MIT"
|
data/spec/aggregate_root_spec.rb
CHANGED
@@ -51,6 +51,17 @@ module Sandthorn
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
describe "::snapshot" do
|
55
|
+
let(:klass) { Class.new { include Sandthorn::AggregateRoot } }
|
56
|
+
it "is available as a class method" do
|
57
|
+
expect(klass).to respond_to(:snapshot)
|
58
|
+
end
|
59
|
+
it "sets the snapshot to true and returns it" do
|
60
|
+
klass.snapshot(true)
|
61
|
+
expect(klass.snapshot).to eq(true)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
54
65
|
describe "when get all aggregates from DirtyClass" do
|
55
66
|
|
56
67
|
before(:each) do
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Sandthorn
|
4
|
+
module Snapshot
|
5
|
+
class KlassOne
|
6
|
+
include Sandthorn::AggregateRoot
|
7
|
+
snapshot true
|
8
|
+
end
|
9
|
+
|
10
|
+
class KlassTwo
|
11
|
+
include Sandthorn::AggregateRoot
|
12
|
+
end
|
13
|
+
|
14
|
+
class KlassThree
|
15
|
+
include Sandthorn::AggregateRoot
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "::snapshot" do
|
19
|
+
before do
|
20
|
+
Sandthorn.configure do |c|
|
21
|
+
c.snapshot_types = [KlassTwo]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
it "snapshot should be enabled on KlassOne and KlassTwo but not KlassThree" do
|
25
|
+
expect(KlassOne.snapshot).to be_truthy
|
26
|
+
expect(KlassTwo.snapshot).to be_truthy
|
27
|
+
expect(KlassThree.snapshot).not_to be_truthy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "find snapshot on snapshot enabled aggregate" do
|
32
|
+
let(:klass) { KlassOne.new.save }
|
33
|
+
|
34
|
+
it "should find on snapshot enabled Class" do
|
35
|
+
copy = KlassOne.find klass.aggregate_id
|
36
|
+
expect(copy.aggregate_version).to eql(klass.aggregate_version)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should get saved snapshot" do
|
40
|
+
copy = Sandthorn.find_snapshot klass.aggregate_id
|
41
|
+
expect(copy.aggregate_version).to eql(klass.aggregate_version)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "save and find snapshot on snapshot disabled aggregate" do
|
47
|
+
let(:klass) { KlassThree.new.save }
|
48
|
+
|
49
|
+
it "should not find snapshot" do
|
50
|
+
snapshot = Sandthorn.find_snapshot klass.aggregate_id
|
51
|
+
expect(snapshot).to be_nil
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should save and get saved snapshot" do
|
55
|
+
Sandthorn.save_snapshot klass
|
56
|
+
snapshot = Sandthorn.find_snapshot klass.aggregate_id
|
57
|
+
expect(snapshot).not_to be_nil
|
58
|
+
|
59
|
+
#Check by key on the snapshot_store hash
|
60
|
+
expect(Sandthorn.snapshot_store.store.has_key?(klass.aggregate_id)).to be_truthy
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -45,17 +45,13 @@ end
|
|
45
45
|
def url
|
46
46
|
"sqlite://spec/db/sequel_driver.sqlite3"
|
47
47
|
end
|
48
|
+
|
48
49
|
def sqlite_store_setup
|
49
50
|
|
50
51
|
SandthornDriverSequel.migrate_db url: url
|
51
|
-
|
52
|
-
driver = SandthornDriverSequel.driver_from_url(url: url) do |conf|
|
53
|
-
conf.event_serializer = Proc.new { |data| YAML::dump(data) }
|
54
|
-
conf.event_deserializer = Proc.new { |data| YAML::load(data) }
|
55
|
-
end
|
56
52
|
|
57
53
|
Sandthorn.configure do |c|
|
58
|
-
c.event_store =
|
54
|
+
c.event_store = SandthornDriverSequel.driver_from_url(url: url)
|
59
55
|
end
|
60
56
|
|
61
57
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sandthorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lars Krantz
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-
|
13
|
+
date: 2018-06-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -166,7 +166,7 @@ dependencies:
|
|
166
166
|
- - ">="
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '4.0'
|
169
|
-
description: Event sourcing
|
169
|
+
description: Event sourcing
|
170
170
|
email:
|
171
171
|
- lars.krantz@alaz.se
|
172
172
|
- morgan.hallgren@gmail.com
|
@@ -182,20 +182,18 @@ files:
|
|
182
182
|
- ".ruby-version"
|
183
183
|
- ".travis.yml"
|
184
184
|
- Gemfile
|
185
|
-
- Gemfile.lock.old
|
186
185
|
- LICENSE
|
187
|
-
- LICENSE.txt
|
188
186
|
- README.md
|
189
187
|
- Rakefile
|
190
188
|
- lib/sandthorn.rb
|
191
189
|
- lib/sandthorn/aggregate_root.rb
|
192
190
|
- lib/sandthorn/aggregate_root_base.rb
|
193
191
|
- lib/sandthorn/aggregate_root_marshal.rb
|
194
|
-
- lib/sandthorn/aggregate_root_snapshot.rb
|
195
192
|
- lib/sandthorn/bounded_context.rb
|
196
193
|
- lib/sandthorn/errors.rb
|
197
194
|
- lib/sandthorn/event_inspector.rb
|
198
195
|
- lib/sandthorn/event_stores.rb
|
196
|
+
- lib/sandthorn/snapshot_store.rb
|
199
197
|
- lib/sandthorn/version.rb
|
200
198
|
- sandthorn.gemspec
|
201
199
|
- spec/aggregate_delta_spec.rb
|
@@ -209,6 +207,7 @@ files:
|
|
209
207
|
- spec/default_attributes_spec.rb
|
210
208
|
- spec/event_stores_spec.rb
|
211
209
|
- spec/initialize_signature_change_spec.rb
|
210
|
+
- spec/snapshot_spec.rb
|
212
211
|
- spec/spec_helper.rb
|
213
212
|
- spec/stateless_events_spec.rb
|
214
213
|
- spec/support/custom_matchers.rb
|
@@ -233,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
233
232
|
version: '0'
|
234
233
|
requirements: []
|
235
234
|
rubyforge_project:
|
236
|
-
rubygems_version: 2.
|
235
|
+
rubygems_version: 2.6.14
|
237
236
|
signing_key:
|
238
237
|
specification_version: 4
|
239
238
|
summary: Event sourcing gem
|
@@ -249,6 +248,7 @@ test_files:
|
|
249
248
|
- spec/default_attributes_spec.rb
|
250
249
|
- spec/event_stores_spec.rb
|
251
250
|
- spec/initialize_signature_change_spec.rb
|
251
|
+
- spec/snapshot_spec.rb
|
252
252
|
- spec/spec_helper.rb
|
253
253
|
- spec/stateless_events_spec.rb
|
254
254
|
- spec/support/custom_matchers.rb
|
data/Gemfile.lock.old
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
sandthorn (0.0.1)
|
5
|
-
upptec_event_sequel_driver (~> 1.2)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
autotest-standalone (4.5.11)
|
11
|
-
awesome_print (1.2.0)
|
12
|
-
coderay (1.1.0)
|
13
|
-
diff-lcs (1.2.5)
|
14
|
-
gem-release (0.7.1)
|
15
|
-
method_source (0.8.2)
|
16
|
-
pg (0.17.1)
|
17
|
-
pry (0.9.12.6)
|
18
|
-
coderay (~> 1.0)
|
19
|
-
method_source (~> 0.8)
|
20
|
-
slop (~> 3.4)
|
21
|
-
pry-doc (0.5.1)
|
22
|
-
pry (>= 0.9)
|
23
|
-
yard (>= 0.8)
|
24
|
-
rake (10.1.1)
|
25
|
-
rspec (2.14.1)
|
26
|
-
rspec-core (~> 2.14.0)
|
27
|
-
rspec-expectations (~> 2.14.0)
|
28
|
-
rspec-mocks (~> 2.14.0)
|
29
|
-
rspec-core (2.14.8)
|
30
|
-
rspec-expectations (2.14.5)
|
31
|
-
diff-lcs (>= 1.1.3, < 2.0)
|
32
|
-
rspec-mocks (2.14.6)
|
33
|
-
sequel (4.7.0)
|
34
|
-
slop (3.4.7)
|
35
|
-
sqlite3 (1.3.9)
|
36
|
-
upptec_event_sequel_driver (1.2.2)
|
37
|
-
pg
|
38
|
-
sequel
|
39
|
-
yard (0.8.7.3)
|
40
|
-
|
41
|
-
PLATFORMS
|
42
|
-
ruby
|
43
|
-
|
44
|
-
DEPENDENCIES
|
45
|
-
autotest-standalone
|
46
|
-
awesome_print
|
47
|
-
bundler (~> 1.3)
|
48
|
-
gem-release
|
49
|
-
pry
|
50
|
-
pry-doc
|
51
|
-
rake
|
52
|
-
rspec
|
53
|
-
sandthorn!
|
54
|
-
sqlite3
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2013 Lars Krantz
|
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.
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Sandthorn
|
2
|
-
module AggregateRootSnapshot
|
3
|
-
attr_reader :aggregate_snapshot
|
4
|
-
|
5
|
-
def snapshot
|
6
|
-
aggregate_snapshot!
|
7
|
-
save_snapshot
|
8
|
-
self
|
9
|
-
end
|
10
|
-
|
11
|
-
def aggregate_snapshot!
|
12
|
-
if @aggregate_events.count > 0
|
13
|
-
raise Errors::SnapshotError,
|
14
|
-
"Can't take snapshot on object with unsaved events"
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def save_snapshot
|
19
|
-
Sandthorn.save_snapshot(self)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|