entity_store 1.2.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/entity_store/config.rb +9 -8
- data/lib/entity_store/event_bus.rb +49 -11
- data/lib/entity_store/not_found.rb +2 -2
- data/lib/entity_store/store.rb +55 -8
- data/lib/entity_store/version.rb +1 -1
- data/lib/entity_store.rb +0 -2
- data/spec/entity_store/entity_spec.rb +1 -1
- data/spec/entity_store/store_spec.rb +50 -2
- data/spec/entity_store_spec.rb +54 -1
- metadata +3 -5
- data/lib/entity_store/utils.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4575519de291e09a6f02085a07daeb09818edc01a5d8895204d443fc03db6b9
|
4
|
+
data.tar.gz: ce27f46beb087f7bcd48b6d5e690db1239e75adaffdcb524b1c24df0cbeea49f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de737107e012f7a07b5aea26280b425a6db374e13325054344e7c9fac196ee98bde2dbcc7d231fc0cd28b26992d805188a06d727373ef7c46e3552598c9c6e52
|
7
|
+
data.tar.gz: a624bb070c990b68b0a436d07c940f656238a244a80bfe2c789f3bb7feb4f8de0e66c65696496d75b936557e5bb4465466cd1e5f05f1b4583e8cf3bab3cc31ee
|
data/lib/entity_store/config.rb
CHANGED
@@ -4,9 +4,11 @@ module EntityStore
|
|
4
4
|
# Stores
|
5
5
|
attr_accessor :store, :feed_store
|
6
6
|
|
7
|
+
attr_accessor :cache_event_subscribers
|
8
|
+
|
7
9
|
# Allows config to pass in a lambda or Proc to use as the type loader in place
|
8
|
-
# of the default.
|
9
|
-
# Original use case was migration of entity classes to new module namespace when
|
10
|
+
# of the default.
|
11
|
+
# Original use case was migration of entity classes to new module namespace when
|
10
12
|
# extracting to a shared library
|
11
13
|
attr_accessor :type_loader
|
12
14
|
|
@@ -17,15 +19,15 @@ module EntityStore
|
|
17
19
|
yield self
|
18
20
|
|
19
21
|
raise StandardError.new("store not assigned") unless store
|
20
|
-
store.open
|
22
|
+
store.open
|
21
23
|
feed_store.open if feed_store
|
22
24
|
end
|
23
|
-
|
25
|
+
|
24
26
|
def event_subscribers
|
25
27
|
@_event_subscribers ||=[]
|
26
28
|
end
|
27
|
-
|
28
|
-
# Public - indicates the version increment that is used to
|
29
|
+
|
30
|
+
# Public - indicates the version increment that is used to
|
29
31
|
# decided whether a snapshot of an entity should be created when it's saved
|
30
32
|
def snapshot_threshold
|
31
33
|
@_snapshot_threshold ||= 10
|
@@ -35,7 +37,6 @@ module EntityStore
|
|
35
37
|
@_snapshot_threshold = value
|
36
38
|
end
|
37
39
|
|
38
|
-
|
39
40
|
def load_type(type_name)
|
40
41
|
if EntityStore::Config.type_loader
|
41
42
|
EntityStore::Config.type_loader.call(type_name)
|
@@ -50,4 +51,4 @@ module EntityStore
|
|
50
51
|
end
|
51
52
|
|
52
53
|
end
|
53
|
-
end
|
54
|
+
end
|
@@ -4,11 +4,19 @@ module EntityStore
|
|
4
4
|
|
5
5
|
ALL_METHOD = :all_events
|
6
6
|
|
7
|
+
def initialize(event_subscribers = nil)
|
8
|
+
@_event_subscribers = event_subscribers if event_subscribers
|
9
|
+
end
|
10
|
+
|
11
|
+
def event_subscribers
|
12
|
+
@_event_subscribers || EntityStore::Config.event_subscribers
|
13
|
+
end
|
14
|
+
|
7
15
|
def publish(entity_type, event)
|
8
|
-
publish_to_feed
|
16
|
+
publish_to_feed(entity_type, event)
|
9
17
|
|
10
|
-
subscribers_to(event.receiver_name).each
|
11
|
-
subscribers_to_all.each
|
18
|
+
subscribers_to(event.receiver_name).each { |s| send_to_subscriber(s, event.receiver_name, event) }
|
19
|
+
subscribers_to_all.each { |s| send_to_subscriber(s, ALL_METHOD, event) }
|
12
20
|
end
|
13
21
|
|
14
22
|
def send_to_subscriber subscriber, receiver_name, event
|
@@ -19,18 +27,48 @@ module EntityStore
|
|
19
27
|
end
|
20
28
|
|
21
29
|
def subscribers_to(event_name)
|
22
|
-
|
30
|
+
subscriber_lookup[event_name.to_sym].dup
|
23
31
|
end
|
24
32
|
|
25
33
|
def subscribers_to_all
|
26
|
-
|
34
|
+
subscribers_to(ALL_METHOD)
|
35
|
+
end
|
36
|
+
|
37
|
+
def subscriber_lookup_cache
|
38
|
+
@@lookup_cache ||= Hash.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def subscriber_lookup
|
42
|
+
return generate_subscriber_lookup unless EntityStore::Config.cache_event_subscribers
|
43
|
+
|
44
|
+
@lookup ||= begin
|
45
|
+
lookup_cache_key = event_subscribers.map(&:to_s).join
|
46
|
+
|
47
|
+
if subscriber_lookup_cache[lookup_cache_key]
|
48
|
+
subscriber_lookup_cache[lookup_cache_key]
|
49
|
+
else
|
50
|
+
subscriber_lookup_cache[lookup_cache_key] = generate_subscriber_lookup
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate_subscriber_lookup
|
56
|
+
lookup = Hash.new { |h, k| h[k] = Array.new }
|
57
|
+
|
58
|
+
subscribers.each do |s|
|
59
|
+
s.instance_methods.each do |m|
|
60
|
+
lookup[m] << s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
lookup
|
27
65
|
end
|
28
66
|
|
29
67
|
def subscribers
|
30
|
-
|
68
|
+
event_subscribers.map do |subscriber|
|
31
69
|
case subscriber
|
32
70
|
when String
|
33
|
-
|
71
|
+
EntityStore::Config.load_type(subscriber)
|
34
72
|
else
|
35
73
|
subscriber
|
36
74
|
end
|
@@ -46,17 +84,17 @@ module EntityStore
|
|
46
84
|
end
|
47
85
|
|
48
86
|
# Public - replay events of a given type to a given subscriber
|
49
|
-
#
|
87
|
+
#
|
50
88
|
# since - Time reference point
|
51
89
|
# type - String type name of event
|
52
|
-
# subscriber - Class of the subscriber to replay events to
|
53
|
-
#
|
90
|
+
# subscriber - Class of the subscriber to replay events to
|
91
|
+
#
|
54
92
|
# Returns nothing
|
55
93
|
def replay(since, type, subscriber)
|
56
94
|
max_items = 100
|
57
95
|
event_data_objects = feed_store.get_events(since, type, max_items)
|
58
96
|
|
59
|
-
while event_data_objects.count > 0 do
|
97
|
+
while event_data_objects.count > 0 do
|
60
98
|
event_data_objects.each do |event_data_object|
|
61
99
|
begin
|
62
100
|
event = EntityStore::Config.load_type(event_data_object.type).new(event_data_object.attrs)
|
data/lib/entity_store/store.rb
CHANGED
@@ -2,10 +2,19 @@ module EntityStore
|
|
2
2
|
class Store
|
3
3
|
include Logging
|
4
4
|
|
5
|
+
def initialize(storage_client = nil, event_bus = nil)
|
6
|
+
@_storage_client = storage_client if storage_client
|
7
|
+
@_event_bus = event_bus if event_bus
|
8
|
+
end
|
9
|
+
|
5
10
|
def storage_client
|
6
11
|
@_storage_client ||= EntityStore::Config.store
|
7
12
|
end
|
8
13
|
|
14
|
+
def event_bus
|
15
|
+
@_event_bus ||= EventBus.new
|
16
|
+
end
|
17
|
+
|
9
18
|
def add(entity)
|
10
19
|
entity.id = storage_client.add_entity(entity)
|
11
20
|
add_events(entity)
|
@@ -31,8 +40,10 @@ module EntityStore
|
|
31
40
|
entity.id = storage_client.add_entity(entity)
|
32
41
|
end
|
33
42
|
|
34
|
-
add_events(entity)
|
35
|
-
|
43
|
+
added_events = add_events(entity)
|
44
|
+
|
45
|
+
if entity.snapshot_due? || added_events >= Config.snapshot_threshold
|
46
|
+
snapshot_entity(entity)
|
36
47
|
end
|
37
48
|
|
38
49
|
# publish version increment signal event to the bus
|
@@ -44,6 +55,33 @@ module EntityStore
|
|
44
55
|
raise e
|
45
56
|
end
|
46
57
|
|
58
|
+
# Upsert an entity where events have existed previously
|
59
|
+
# for example when migrating data
|
60
|
+
#
|
61
|
+
# Please note this method requires that the events expose their id property
|
62
|
+
# as a method named _id.
|
63
|
+
#
|
64
|
+
def upsert(entity)
|
65
|
+
unless entity.pending_events.empty?
|
66
|
+
entity.version = entity.pending_events.map(&:entity_version).max || 1
|
67
|
+
|
68
|
+
if entity.id
|
69
|
+
storage_client.save_entity(entity)
|
70
|
+
else
|
71
|
+
entity.id = storage_client.add_entity(entity)
|
72
|
+
end
|
73
|
+
|
74
|
+
upsert_events(entity)
|
75
|
+
|
76
|
+
# publish version increment signal event to the bus
|
77
|
+
event_bus.publish(entity.type, entity.generate_version_incremented_event)
|
78
|
+
end
|
79
|
+
entity
|
80
|
+
rescue => e
|
81
|
+
log_error "Store#upsert error: #{e.inspect} - #{entity.inspect}", e
|
82
|
+
raise e
|
83
|
+
end
|
84
|
+
|
47
85
|
def snapshot_entity(entity)
|
48
86
|
log_info { "Store#snapshot_entity : Snapshotting #{entity.id}"}
|
49
87
|
storage_client.snapshot_entity(entity)
|
@@ -69,9 +107,22 @@ module EntityStore
|
|
69
107
|
end
|
70
108
|
storage_client.add_events(items)
|
71
109
|
|
72
|
-
|
110
|
+
items.each { |e| event_bus.publish(entity.type, e) }
|
111
|
+
|
112
|
+
entity.clear_pending_events
|
113
|
+
items.count
|
114
|
+
end
|
115
|
+
|
116
|
+
def upsert_events(entity)
|
117
|
+
items = entity.pending_events.map do |event|
|
118
|
+
event.entity_id ||= entity.id.to_s
|
119
|
+
event.entity_version ||= entity.version
|
120
|
+
event
|
121
|
+
end
|
122
|
+
|
123
|
+
filtered_items = storage_client.upsert_events(items)
|
73
124
|
|
74
|
-
|
125
|
+
filtered_items.each { |e| event_bus.publish(entity.type, e) }
|
75
126
|
|
76
127
|
entity.clear_pending_events
|
77
128
|
end
|
@@ -146,10 +197,6 @@ module EntityStore
|
|
146
197
|
@_storage_client = nil
|
147
198
|
end
|
148
199
|
|
149
|
-
def event_bus
|
150
|
-
@_event_bus ||= EventBus.new
|
151
|
-
end
|
152
|
-
|
153
200
|
# Public: returns an array representing a full audit trail for the entity.
|
154
201
|
# After each event is applied the state of the entity is rendered.
|
155
202
|
# Optionally accepts a block which should return true or false to indicate
|
data/lib/entity_store/version.rb
CHANGED
data/lib/entity_store.rb
CHANGED
@@ -84,7 +84,7 @@ describe Entity do
|
|
84
84
|
subject { DummyEntity.new(1) }
|
85
85
|
|
86
86
|
it "should raise a readable error" do
|
87
|
-
expect { subject }.to raise_error(RuntimeError,
|
87
|
+
expect { subject }.to raise_error(RuntimeError, /\ADo not know how to create DummyEntity from (Integer|Fixnum)\z/)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -74,6 +74,39 @@ describe Store do
|
|
74
74
|
|
75
75
|
end
|
76
76
|
|
77
|
+
describe "#upsert_events" do
|
78
|
+
before(:each) do
|
79
|
+
@entity = DummyEntityForStore.new(:name => random_string)
|
80
|
+
@entity.id = random_string
|
81
|
+
@entity.version = random_integer
|
82
|
+
@entity.pending_events << double(Event, :entity_id => @entity.id, :entity_version => @entity.version)
|
83
|
+
@entity.pending_events << double(Event, :entity_id => @entity.id, :entity_version => @entity.version)
|
84
|
+
@entity.pending_events << double(Event, :entity_id => @entity.id, :entity_version => @entity.version)
|
85
|
+
@storage_client = double("StorageClient", :upsert_events => filtered_events)
|
86
|
+
@store = Store.new
|
87
|
+
@store.stub(:storage_client) { @storage_client }
|
88
|
+
@event_bus = double(EventBus, :publish => true)
|
89
|
+
@store.stub(:event_bus) { @event_bus}
|
90
|
+
end
|
91
|
+
|
92
|
+
subject { @store.upsert_events(@entity) }
|
93
|
+
|
94
|
+
let(:filtered_events) { @entity.pending_events.take(2) }
|
95
|
+
|
96
|
+
it "adds each of the events" do
|
97
|
+
@storage_client.should_receive(:upsert_events).with(@entity.pending_events)
|
98
|
+
subject
|
99
|
+
end
|
100
|
+
|
101
|
+
it "publishes each event to the EventBus" do
|
102
|
+
filtered_events.each do |e|
|
103
|
+
@event_bus.should_receive(:publish).with(@entity.type, e)
|
104
|
+
end
|
105
|
+
subject
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
77
110
|
describe "#save" do
|
78
111
|
|
79
112
|
before(:each) do
|
@@ -81,7 +114,7 @@ describe Store do
|
|
81
114
|
@entity = DummyEntityForStore.new(:id => random_string)
|
82
115
|
@storage_client = double("StorageClient", :save_entity => true)
|
83
116
|
@store = Store.new
|
84
|
-
@store.stub(:add_events).
|
117
|
+
@store.stub(:add_events) { |entity| entity.pending_events.count }
|
85
118
|
@store.stub(:storage_client) { @storage_client }
|
86
119
|
@entity.stub(:pending_events) { [ double('Event') ] }
|
87
120
|
end
|
@@ -161,8 +194,23 @@ describe Store do
|
|
161
194
|
subject
|
162
195
|
end
|
163
196
|
end
|
164
|
-
end
|
165
197
|
|
198
|
+
context "when flushed events exceeds snapshotting threshold" do
|
199
|
+
before(:each) do
|
200
|
+
@entity.version = 1
|
201
|
+
@entity.stub(:pending_events) do
|
202
|
+
(1..EntityStore::Config.snapshot_threshold).map do
|
203
|
+
double('Event')
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should snapshot the entity" do
|
209
|
+
@storage_client.should_receive(:snapshot_entity).with(@entity)
|
210
|
+
subject
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
166
214
|
end
|
167
215
|
|
168
216
|
describe "getters" do
|
data/spec/entity_store_spec.rb
CHANGED
@@ -26,7 +26,17 @@ class DummyEntitySubscriber
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def dummy_entity_name_set(event)
|
29
|
-
|
29
|
+
self.class.event_name = event.name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class AnotherEntitySubscriber
|
34
|
+
class << self
|
35
|
+
attr_accessor :event_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def dummy_entity_name_set(event)
|
39
|
+
self.class.event_name = event.name
|
30
40
|
end
|
31
41
|
end
|
32
42
|
|
@@ -80,6 +90,49 @@ class DummyStore
|
|
80
90
|
end
|
81
91
|
end
|
82
92
|
|
93
|
+
describe "creation without static instances" do
|
94
|
+
let(:store) do
|
95
|
+
storage_client = DummyStore.new
|
96
|
+
Store.new(storage_client, event_bus)
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:event_bus) do
|
100
|
+
event_subscribers = []
|
101
|
+
event_subscribers << AnotherEntitySubscriber
|
102
|
+
|
103
|
+
EventBus.new(event_subscribers)
|
104
|
+
end
|
105
|
+
|
106
|
+
before do
|
107
|
+
EntityStore::Config.setup do |config|
|
108
|
+
config.store = DummyStore.new
|
109
|
+
config.event_subscribers << DummyEntitySubscriber
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when save entity" do
|
114
|
+
let(:name) { random_string }
|
115
|
+
before(:each) do
|
116
|
+
@entity = DummyEntity.new
|
117
|
+
@entity.set_name name
|
118
|
+
@id = store.save @entity
|
119
|
+
end
|
120
|
+
|
121
|
+
it "does not publish event to the non configured subscriber" do
|
122
|
+
DummyEntitySubscriber.event_name.should_not eq(name)
|
123
|
+
end
|
124
|
+
it "publishes event to the subscriber" do
|
125
|
+
AnotherEntitySubscriber.event_name.should eq(name)
|
126
|
+
end
|
127
|
+
it "is retrievable with the events applied" do
|
128
|
+
store.get(@entity.id).name.should eq(name)
|
129
|
+
end
|
130
|
+
it "is not retrievable from the static store instance" do
|
131
|
+
EntityStore::Store.new.get(@entity.id).should eq(nil)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
83
136
|
describe "end to end" do
|
84
137
|
before(:each) do
|
85
138
|
EntityStore::Config.setup do |config|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: entity_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Bird
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bson
|
@@ -57,7 +57,6 @@ files:
|
|
57
57
|
- lib/entity_store/not_found.rb
|
58
58
|
- lib/entity_store/store.rb
|
59
59
|
- lib/entity_store/time_factory.rb
|
60
|
-
- lib/entity_store/utils.rb
|
61
60
|
- lib/entity_store/version.rb
|
62
61
|
- lib/tasks/entity_store.rake
|
63
62
|
- spec/entity_store/config_spec.rb
|
@@ -87,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
86
|
- !ruby/object:Gem::Version
|
88
87
|
version: '0'
|
89
88
|
requirements: []
|
90
|
-
|
91
|
-
rubygems_version: 2.7.4
|
89
|
+
rubygems_version: 3.2.33
|
92
90
|
signing_key:
|
93
91
|
specification_version: 4
|
94
92
|
summary: Event sourced entity store with a replaceable body
|