eventsimple 1.0.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +105 -85
- data/README.md +44 -20
- data/Rakefile +1 -0
- data/app/controllers/eventsimple/entities_controller.rb +1 -1
- data/eventsimple.gemspec +2 -2
- data/lib/eventsimple/configuration.rb +10 -2
- data/lib/eventsimple/engine.rb +10 -4
- data/lib/eventsimple/event_dispatcher.rb +2 -2
- data/lib/eventsimple/outbox/consumer.rb +1 -1
- data/lib/eventsimple/reactor.rb +32 -0
- data/lib/eventsimple/reactor_worker.rb +2 -1
- data/lib/eventsimple/version.rb +1 -1
- data/lib/eventsimple.rb +2 -0
- metadata +5 -5
- data/.DS_Store +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46f6a80be5a43cb84c321b67e9a27dde596b2eb3aa3d55ce8f635e395f42ce19
|
4
|
+
data.tar.gz: c3abab5c2bd25bee61695d24cdb9104cd11d403bb2b360ad1417f71f3006cd54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3eb05c140311c7466f6cc27fe2a63e033f4464123449d9c9bc20b4b3ae9dd207b03bfe3acfd00cbf6c04bfa4c55456910fb2cee80056e04b5ec5aff70a4b73c1
|
7
|
+
data.tar.gz: fe56e5c4b5fa1608bd05a01f2b05f2051c8d7491170dc0c06790279a134a5d2b19465feca5104420d7dc43dcf2816e982d7aafbaa0aa65bfeadcca0ad476948d
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## Unreleased
|
8
8
|
|
9
|
+
## 1.1.1 - 2023-06-19
|
10
|
+
### Changed
|
11
|
+
- Remove active_job_parent_klass config introduced in 1.1.0. There isn't a usecase for it yet,
|
12
|
+
and it was causing loading issues.
|
13
|
+
|
14
|
+
## 1.1.0 - 2023-06-11
|
15
|
+
### Changed
|
16
|
+
- Reactors now use ActiveJob instead of Sidekiq directly. This allows for more
|
17
|
+
flexibility in the future and allows for the use of other job backends.
|
18
|
+
The reactor class definition must now inherit from Eventsimple::Reactor and are no longer
|
19
|
+
instantiated with the event as an argument. Instead, the event is passed to the `call` method.
|
20
|
+
This change is backwards compatible with existing inflight reactor jobs.
|
21
|
+
|
9
22
|
## 1.0.0 - 2023-05-03
|
10
23
|
### Changed
|
11
24
|
- Release under MIT license
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
eventsimple (1.
|
4
|
+
eventsimple (1.1.1)
|
5
5
|
dry-struct (~> 1.6)
|
6
6
|
dry-types (~> 1.7)
|
7
7
|
pg (~> 1.4)
|
@@ -12,67 +12,67 @@ PATH
|
|
12
12
|
GEM
|
13
13
|
remote: https://rubygems.org/
|
14
14
|
specs:
|
15
|
-
actioncable (7.0.
|
16
|
-
actionpack (= 7.0.
|
17
|
-
activesupport (= 7.0.
|
15
|
+
actioncable (7.0.5)
|
16
|
+
actionpack (= 7.0.5)
|
17
|
+
activesupport (= 7.0.5)
|
18
18
|
nio4r (~> 2.0)
|
19
19
|
websocket-driver (>= 0.6.1)
|
20
|
-
actionmailbox (7.0.
|
21
|
-
actionpack (= 7.0.
|
22
|
-
activejob (= 7.0.
|
23
|
-
activerecord (= 7.0.
|
24
|
-
activestorage (= 7.0.
|
25
|
-
activesupport (= 7.0.
|
20
|
+
actionmailbox (7.0.5)
|
21
|
+
actionpack (= 7.0.5)
|
22
|
+
activejob (= 7.0.5)
|
23
|
+
activerecord (= 7.0.5)
|
24
|
+
activestorage (= 7.0.5)
|
25
|
+
activesupport (= 7.0.5)
|
26
26
|
mail (>= 2.7.1)
|
27
27
|
net-imap
|
28
28
|
net-pop
|
29
29
|
net-smtp
|
30
|
-
actionmailer (7.0.
|
31
|
-
actionpack (= 7.0.
|
32
|
-
actionview (= 7.0.
|
33
|
-
activejob (= 7.0.
|
34
|
-
activesupport (= 7.0.
|
30
|
+
actionmailer (7.0.5)
|
31
|
+
actionpack (= 7.0.5)
|
32
|
+
actionview (= 7.0.5)
|
33
|
+
activejob (= 7.0.5)
|
34
|
+
activesupport (= 7.0.5)
|
35
35
|
mail (~> 2.5, >= 2.5.4)
|
36
36
|
net-imap
|
37
37
|
net-pop
|
38
38
|
net-smtp
|
39
39
|
rails-dom-testing (~> 2.0)
|
40
|
-
actionpack (7.0.
|
41
|
-
actionview (= 7.0.
|
42
|
-
activesupport (= 7.0.
|
43
|
-
rack (~> 2.0, >= 2.2.
|
40
|
+
actionpack (7.0.5)
|
41
|
+
actionview (= 7.0.5)
|
42
|
+
activesupport (= 7.0.5)
|
43
|
+
rack (~> 2.0, >= 2.2.4)
|
44
44
|
rack-test (>= 0.6.3)
|
45
45
|
rails-dom-testing (~> 2.0)
|
46
46
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
47
|
-
actiontext (7.0.
|
48
|
-
actionpack (= 7.0.
|
49
|
-
activerecord (= 7.0.
|
50
|
-
activestorage (= 7.0.
|
51
|
-
activesupport (= 7.0.
|
47
|
+
actiontext (7.0.5)
|
48
|
+
actionpack (= 7.0.5)
|
49
|
+
activerecord (= 7.0.5)
|
50
|
+
activestorage (= 7.0.5)
|
51
|
+
activesupport (= 7.0.5)
|
52
52
|
globalid (>= 0.6.0)
|
53
53
|
nokogiri (>= 1.8.5)
|
54
|
-
actionview (7.0.
|
55
|
-
activesupport (= 7.0.
|
54
|
+
actionview (7.0.5)
|
55
|
+
activesupport (= 7.0.5)
|
56
56
|
builder (~> 3.1)
|
57
57
|
erubi (~> 1.4)
|
58
58
|
rails-dom-testing (~> 2.0)
|
59
59
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
60
|
-
activejob (7.0.
|
61
|
-
activesupport (= 7.0.
|
60
|
+
activejob (7.0.5)
|
61
|
+
activesupport (= 7.0.5)
|
62
62
|
globalid (>= 0.3.6)
|
63
|
-
activemodel (7.0.
|
64
|
-
activesupport (= 7.0.
|
65
|
-
activerecord (7.0.
|
66
|
-
activemodel (= 7.0.
|
67
|
-
activesupport (= 7.0.
|
68
|
-
activestorage (7.0.
|
69
|
-
actionpack (= 7.0.
|
70
|
-
activejob (= 7.0.
|
71
|
-
activerecord (= 7.0.
|
72
|
-
activesupport (= 7.0.
|
63
|
+
activemodel (7.0.5)
|
64
|
+
activesupport (= 7.0.5)
|
65
|
+
activerecord (7.0.5)
|
66
|
+
activemodel (= 7.0.5)
|
67
|
+
activesupport (= 7.0.5)
|
68
|
+
activestorage (7.0.5)
|
69
|
+
actionpack (= 7.0.5)
|
70
|
+
activejob (= 7.0.5)
|
71
|
+
activerecord (= 7.0.5)
|
72
|
+
activesupport (= 7.0.5)
|
73
73
|
marcel (~> 1.0)
|
74
74
|
mini_mime (>= 1.1.0)
|
75
|
-
activesupport (7.0.
|
75
|
+
activesupport (7.0.5)
|
76
76
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
77
77
|
i18n (>= 1.6, < 2)
|
78
78
|
minitest (>= 5.1)
|
@@ -88,7 +88,7 @@ GEM
|
|
88
88
|
thor (~> 1.0)
|
89
89
|
coderay (1.1.3)
|
90
90
|
concurrent-ruby (1.2.2)
|
91
|
-
connection_pool (2.4.
|
91
|
+
connection_pool (2.4.1)
|
92
92
|
crass (1.0.6)
|
93
93
|
date (3.3.3)
|
94
94
|
diff-lcs (1.5.0)
|
@@ -136,16 +136,18 @@ GEM
|
|
136
136
|
guard (~> 2.1)
|
137
137
|
guard-compat (~> 1.1)
|
138
138
|
rspec (>= 2.99.0, < 4.0)
|
139
|
-
i18n (1.
|
139
|
+
i18n (1.14.1)
|
140
140
|
concurrent-ruby (~> 1.0)
|
141
141
|
ice_nine (0.11.2)
|
142
142
|
json (2.6.3)
|
143
|
+
language_server-protocol (3.17.0.3)
|
144
|
+
lint_roller (1.0.0)
|
143
145
|
listen (3.8.0)
|
144
146
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
145
147
|
rb-inotify (~> 0.9, >= 0.9.10)
|
146
|
-
loofah (2.
|
148
|
+
loofah (2.21.3)
|
147
149
|
crass (~> 1.0.2)
|
148
|
-
nokogiri (>= 1.
|
150
|
+
nokogiri (>= 1.12.0)
|
149
151
|
lumberjack (1.2.8)
|
150
152
|
mail (2.8.1)
|
151
153
|
mini_mime (>= 0.1.1)
|
@@ -157,7 +159,7 @@ GEM
|
|
157
159
|
mini_mime (1.1.2)
|
158
160
|
minitest (5.18.0)
|
159
161
|
nenv (0.3.0)
|
160
|
-
net-imap (0.3.
|
162
|
+
net-imap (0.3.6)
|
161
163
|
date
|
162
164
|
net-protocol
|
163
165
|
net-pop (0.1.2)
|
@@ -167,9 +169,9 @@ GEM
|
|
167
169
|
net-smtp (0.3.3)
|
168
170
|
net-protocol
|
169
171
|
nio4r (2.5.9)
|
170
|
-
nokogiri (1.
|
172
|
+
nokogiri (1.15.2-arm64-darwin)
|
171
173
|
racc (~> 1.4)
|
172
|
-
nokogiri (1.
|
174
|
+
nokogiri (1.15.2-x86_64-linux)
|
173
175
|
racc (~> 1.4)
|
174
176
|
notiffany (0.1.3)
|
175
177
|
nenv (~> 0.1)
|
@@ -177,42 +179,44 @@ GEM
|
|
177
179
|
parallel (1.23.0)
|
178
180
|
parse_a_changelog (1.2.0)
|
179
181
|
treetop (~> 1.6)
|
180
|
-
parser (3.2.2.
|
182
|
+
parser (3.2.2.3)
|
181
183
|
ast (~> 2.4.1)
|
184
|
+
racc
|
182
185
|
pg (1.5.3)
|
183
186
|
polyglot (0.3.5)
|
184
187
|
pry (0.14.2)
|
185
188
|
coderay (~> 1.1)
|
186
189
|
method_source (~> 1.0)
|
187
190
|
public_suffix (5.0.1)
|
188
|
-
puma (6.
|
191
|
+
puma (6.3.0)
|
189
192
|
nio4r (~> 2.0)
|
190
|
-
racc (1.
|
193
|
+
racc (1.7.0)
|
191
194
|
rack (2.2.7)
|
192
195
|
rack-test (2.1.0)
|
193
196
|
rack (>= 1.3)
|
194
|
-
rails (7.0.
|
195
|
-
actioncable (= 7.0.
|
196
|
-
actionmailbox (= 7.0.
|
197
|
-
actionmailer (= 7.0.
|
198
|
-
actionpack (= 7.0.
|
199
|
-
actiontext (= 7.0.
|
200
|
-
actionview (= 7.0.
|
201
|
-
activejob (= 7.0.
|
202
|
-
activemodel (= 7.0.
|
203
|
-
activerecord (= 7.0.
|
204
|
-
activestorage (= 7.0.
|
205
|
-
activesupport (= 7.0.
|
197
|
+
rails (7.0.5)
|
198
|
+
actioncable (= 7.0.5)
|
199
|
+
actionmailbox (= 7.0.5)
|
200
|
+
actionmailer (= 7.0.5)
|
201
|
+
actionpack (= 7.0.5)
|
202
|
+
actiontext (= 7.0.5)
|
203
|
+
actionview (= 7.0.5)
|
204
|
+
activejob (= 7.0.5)
|
205
|
+
activemodel (= 7.0.5)
|
206
|
+
activerecord (= 7.0.5)
|
207
|
+
activestorage (= 7.0.5)
|
208
|
+
activesupport (= 7.0.5)
|
206
209
|
bundler (>= 1.15.0)
|
207
|
-
railties (= 7.0.
|
210
|
+
railties (= 7.0.5)
|
208
211
|
rails-dom-testing (2.0.3)
|
209
212
|
activesupport (>= 4.2.0)
|
210
213
|
nokogiri (>= 1.6)
|
211
|
-
rails-html-sanitizer (1.
|
212
|
-
loofah (~> 2.
|
213
|
-
|
214
|
-
|
215
|
-
|
214
|
+
rails-html-sanitizer (1.6.0)
|
215
|
+
loofah (~> 2.21)
|
216
|
+
nokogiri (~> 1.14)
|
217
|
+
railties (7.0.5)
|
218
|
+
actionpack (= 7.0.5)
|
219
|
+
activesupport (= 7.0.5)
|
216
220
|
method_source
|
217
221
|
rake (>= 12.2)
|
218
222
|
thor (~> 1.0)
|
@@ -225,7 +229,7 @@ GEM
|
|
225
229
|
rchardet (1.8.0)
|
226
230
|
redis-client (0.14.1)
|
227
231
|
connection_pool
|
228
|
-
regexp_parser (2.8.
|
232
|
+
regexp_parser (2.8.1)
|
229
233
|
retriable (3.1.2)
|
230
234
|
rexml (3.2.5)
|
231
235
|
rspec (3.12.0)
|
@@ -240,16 +244,16 @@ GEM
|
|
240
244
|
rspec-mocks (3.12.5)
|
241
245
|
diff-lcs (>= 1.2.0, < 2.0)
|
242
246
|
rspec-support (~> 3.12.0)
|
243
|
-
rspec-rails (6.0.
|
247
|
+
rspec-rails (6.0.3)
|
244
248
|
actionpack (>= 6.1)
|
245
249
|
activesupport (>= 6.1)
|
246
250
|
railties (>= 6.1)
|
247
|
-
rspec-core (~> 3.
|
248
|
-
rspec-expectations (~> 3.
|
249
|
-
rspec-mocks (~> 3.
|
250
|
-
rspec-support (~> 3.
|
251
|
+
rspec-core (~> 3.12)
|
252
|
+
rspec-expectations (~> 3.12)
|
253
|
+
rspec-mocks (~> 3.12)
|
254
|
+
rspec-support (~> 3.12)
|
251
255
|
rspec-support (3.12.0)
|
252
|
-
rubocop (1.
|
256
|
+
rubocop (1.52.0)
|
253
257
|
json (~> 2.3)
|
254
258
|
parallel (~> 1.10)
|
255
259
|
parser (>= 3.2.0.0)
|
@@ -259,30 +263,47 @@ GEM
|
|
259
263
|
rubocop-ast (>= 1.28.0, < 2.0)
|
260
264
|
ruby-progressbar (~> 1.7)
|
261
265
|
unicode-display_width (>= 2.4.0, < 3.0)
|
262
|
-
rubocop-ast (1.
|
266
|
+
rubocop-ast (1.29.0)
|
263
267
|
parser (>= 3.2.1.0)
|
264
268
|
rubocop-capybara (2.18.0)
|
265
269
|
rubocop (~> 1.41)
|
266
|
-
rubocop-
|
270
|
+
rubocop-factory_bot (2.23.1)
|
271
|
+
rubocop (~> 1.33)
|
272
|
+
rubocop-performance (1.18.0)
|
267
273
|
rubocop (>= 1.7.0, < 2.0)
|
268
274
|
rubocop-ast (>= 0.4.0)
|
269
275
|
rubocop-rails (2.19.1)
|
270
276
|
activesupport (>= 4.2.0)
|
271
277
|
rack (>= 1.1)
|
272
278
|
rubocop (>= 1.33.0, < 2.0)
|
273
|
-
rubocop-rspec (2.
|
279
|
+
rubocop-rspec (2.22.0)
|
274
280
|
rubocop (~> 1.33)
|
275
281
|
rubocop-capybara (~> 2.17)
|
276
|
-
|
282
|
+
rubocop-factory_bot (~> 2.22)
|
283
|
+
rubocop-vendor (0.11.0)
|
277
284
|
rubocop (>= 0.53.0)
|
278
285
|
ruby-progressbar (1.13.0)
|
279
286
|
shellany (0.0.1)
|
280
|
-
sidekiq (7.1.
|
287
|
+
sidekiq (7.1.2)
|
281
288
|
concurrent-ruby (< 2)
|
282
289
|
connection_pool (>= 2.3.0)
|
283
290
|
rack (>= 2.2.4)
|
284
291
|
redis-client (>= 0.14.0)
|
285
|
-
|
292
|
+
standard (1.29.0)
|
293
|
+
language_server-protocol (~> 3.17.0.2)
|
294
|
+
lint_roller (~> 1.0)
|
295
|
+
rubocop (~> 1.52.0)
|
296
|
+
standard-custom (~> 1.0.0)
|
297
|
+
standard-performance (~> 1.1.0)
|
298
|
+
standard-custom (1.0.1)
|
299
|
+
lint_roller (~> 1.0)
|
300
|
+
standard-performance (1.1.0)
|
301
|
+
lint_roller (~> 1.0)
|
302
|
+
rubocop-performance (~> 1.18.0)
|
303
|
+
standard-rails (0.1.0)
|
304
|
+
lint_roller (~> 1.0)
|
305
|
+
rubocop-rails (~> 2.19.0)
|
306
|
+
thor (1.2.2)
|
286
307
|
timeout (0.3.2)
|
287
308
|
treetop (1.6.12)
|
288
309
|
polyglot (~> 0.3)
|
@@ -292,12 +313,11 @@ GEM
|
|
292
313
|
websocket-driver (0.7.5)
|
293
314
|
websocket-extensions (>= 0.1.0)
|
294
315
|
websocket-extensions (0.1.5)
|
295
|
-
ws-style (
|
296
|
-
rubocop (>= 1.36)
|
297
|
-
rubocop-performance (>= 1.10.2)
|
298
|
-
rubocop-rails (>= 2.9.1)
|
316
|
+
ws-style (7.1.0)
|
299
317
|
rubocop-rspec (>= 2.2.0)
|
300
|
-
rubocop-vendor (>= 0.
|
318
|
+
rubocop-vendor (>= 0.11)
|
319
|
+
standard (>= 1.28.2)
|
320
|
+
standard-rails (>= 0.1.0)
|
301
321
|
zeitwerk (2.6.8)
|
302
322
|
|
303
323
|
PLATFORMS
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Eventsimple
|
2
|
-
[![Github Actions
|
2
|
+
[![Github Actions](https://github.com/wealthsimple/eventsimple/actions/workflows/default.yml/badge.svg)](https://github.com/wealthsimple/eventsimple/actions/workflows/default.yml) [![Gem Version](https://badge.fury.io/rb/eventsimple.svg?v=1)](https://rubygems.org/gems/eventsimple)
|
3
3
|
|
4
4
|
## What
|
5
|
-
Eventsimple implements a simple deterministic event driven system using ActiveRecord and
|
5
|
+
Eventsimple implements a simple deterministic event driven system using ActiveRecord and ActiveJob.
|
6
6
|
|
7
7
|
Use Eventsimple to:
|
8
8
|
|
@@ -11,10 +11,10 @@ Use Eventsimple to:
|
|
11
11
|
* Implement a transactional outbox.
|
12
12
|
* Store audit logs of changes to your ActiveRecord objects.
|
13
13
|
|
14
|
-
Eventsimple uses standard Rails features like [Single Table Inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html) and [Optimistic Locking](https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html)
|
15
|
-
Async workflows are handled using [
|
14
|
+
Eventsimple uses standard Rails features like [Single Table Inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html) and [Optimistic Locking](https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html).
|
15
|
+
Async workflows are handled using [ActiveJob](https://guides.rubyonrails.org/active_job_basics.html).
|
16
16
|
|
17
|
-
Typical events in Eventsimple are ActiveRecord models
|
17
|
+
Typical events in Eventsimple are ActiveRecord models that look like this:
|
18
18
|
|
19
19
|
```ruby
|
20
20
|
<UserComponent::Events::Created
|
@@ -40,19 +40,48 @@ Typical events in Eventsimple are ActiveRecord models using STI and look like th
|
|
40
40
|
|
41
41
|
## Setup
|
42
42
|
|
43
|
-
Add the following line to your Gemfile
|
43
|
+
Add the following line to your Gemfile and run `bundle install`:
|
44
44
|
|
45
45
|
```
|
46
46
|
gem 'eventsimple'
|
47
47
|
```
|
48
|
-
Then run `bundle install`
|
49
48
|
|
50
|
-
The eventsimple UI allows you to view and navigate event history. Add the following line to your routes.rb
|
49
|
+
The eventsimple UI allows you to view and navigate event history. Add the following line to your routes.rb:
|
51
50
|
|
52
51
|
```
|
53
52
|
mount Eventsimple::Engine => '/eventsimple'
|
54
53
|
```
|
55
54
|
|
55
|
+
Setup an initializer in `config/initializers/eventsimple.rb`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
Eventsimple.configure do |config|
|
59
|
+
# Optional: Register your dispatch classes here.
|
60
|
+
# Dispatch classes are used to register reactors to events.
|
61
|
+
# Reactors are used to implement side effects.
|
62
|
+
# See the Reactors section below for more details.
|
63
|
+
config.dispatchers = []
|
64
|
+
|
65
|
+
# Optional: Entity updates use optimistic locking to enforce sequential updates.
|
66
|
+
# Set the max number of times to retry on concurrency failures.
|
67
|
+
# Defaults to 2
|
68
|
+
config.max_concurrency_retries = 2
|
69
|
+
|
70
|
+
# Optional: the metadata column is used to store optional metadata associated with the event.
|
71
|
+
# The default implemention enforces a typed constraint on the metadata column
|
72
|
+
# with the following two properties: `actor_id` and `reason`
|
73
|
+
# Use a custom metadata class to override this behaviour.
|
74
|
+
# Defaults to `Eventsimple::Metadata`
|
75
|
+
config.metadata_klass = 'Eventsimple::Metadata'
|
76
|
+
|
77
|
+
# Optional: When using an ActiveJob adapter that writes to a different data store like redis,
|
78
|
+
# it is possible that the reactor is executed before the transaction persisting the event is committed. This can result in noisy errors when using processors like Sidekiq.
|
79
|
+
# Enable this option to retry the reactor inline if the event is not found.
|
80
|
+
# Defaults to false.
|
81
|
+
config.retry_reactor_on_record_not_found = true
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
56
85
|
Generate a migration and add `Eventsimple` to an existing ActiveRecord model.
|
57
86
|
|
58
87
|
```ruby
|
@@ -93,7 +122,7 @@ add_column :users, :lock_version, :integer
|
|
93
122
|
|
94
123
|
Adding lock_version to the model enables [optimistic locking](https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html) and protects against concurrent updates to stale versions of the model. Eventsimple will automatically retry on concurrency failures.
|
95
124
|
|
96
|
-
`events_namespace` is an optional argument pointing to the directory where your events classes are defined. If you do not specify this argument, Eventsimple will store the full namespace of the event classes in the STI
|
125
|
+
`events_namespace` is an optional argument pointing to the directory where your events classes are defined. If you do not specify this argument, Eventsimple will store the full namespace of the event classes in the STI column.
|
97
126
|
|
98
127
|
### Event Table definition
|
99
128
|
|
@@ -192,14 +221,14 @@ They should **only** contain business logic that make additional database writes
|
|
192
221
|
This is because executing writes to other data stores, e.g API call or writes to kafka/sqs, will result in the transaction being non-deterministic.
|
193
222
|
|
194
223
|
#### Async Reactors
|
195
|
-
Async reactors are executed via
|
224
|
+
Async reactors are executed via ActiveJob. Eventsimple implements checks to enforce reliable eventually consistent behaviour.
|
196
225
|
|
197
226
|
Use Async reactors to kick off async workflows or writes to external data sources as a side effect of model updates.
|
198
227
|
|
199
228
|
Reactor example:
|
200
229
|
|
201
230
|
```ruby
|
202
|
-
# Register your dispatch
|
231
|
+
# Register your dispatch classes in config/initializers/eventsimple.rb.
|
203
232
|
Eventsimple.configure do |config|
|
204
233
|
config.dispatchers = %w[
|
205
234
|
UserComponent::Dispatcher
|
@@ -224,15 +253,10 @@ end
|
|
224
253
|
|
225
254
|
# Reactor classes accept the event as the only argument in the constructor
|
226
255
|
# and must define a `call` method
|
227
|
-
module UserComponent::Reactors::Created
|
256
|
+
module UserComponent::Reactors::Created < Eventsimple::Reactor
|
228
257
|
class SendNotification
|
229
|
-
def
|
230
|
-
|
231
|
-
@user = event.aggregate
|
232
|
-
end
|
233
|
-
attr_reader :event, :user
|
234
|
-
|
235
|
-
def call
|
258
|
+
def call(event)
|
259
|
+
user = event.aggregate
|
236
260
|
# do something
|
237
261
|
end
|
238
262
|
end
|
@@ -242,7 +266,7 @@ end
|
|
242
266
|
## Configuring an outbox consumer
|
243
267
|
|
244
268
|
For many use cases, async reactors are sufficient to handle workflows like making an API call or publishing to a message broker.
|
245
|
-
However since reactors use
|
269
|
+
However since reactors use ActiveJob, order is not guaranteed.
|
246
270
|
|
247
271
|
Eventsimple provides an outbox implementation with order and eventual consistency guarantees.
|
248
272
|
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ module Eventsimple
|
|
6
6
|
@model_class = event_classes.find { |d| d.name == @model_name }
|
7
7
|
@aggregate_id = params[:id]
|
8
8
|
@event_id = params[:e] || -1
|
9
|
-
@tab_id = params[:t] == 'event' ? 'event' : 'entity'
|
9
|
+
@tab_id = (params[:t] == 'event') ? 'event' : 'entity'
|
10
10
|
|
11
11
|
primary_key = @model_class.event_class._aggregate_id
|
12
12
|
@entity = @model_class.find_by!(primary_key => @aggregate_id)
|
data/eventsimple.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ['Zulfiqar Ali']
|
9
9
|
spec.email = ['zulfiqar@wealthsimple.com']
|
10
10
|
|
11
|
-
spec.summary = 'Event
|
12
|
-
spec.description = 'Event
|
11
|
+
spec.summary = 'Event sourcing toolkit using Rails and ActiveJob'
|
12
|
+
spec.description = 'Event sourcing toolkit using Rails and ActiveJob'
|
13
13
|
spec.homepage = 'https://github.com/wealthsimple/eventsimple'
|
14
14
|
spec.required_ruby_version = ">= 3.2.0"
|
15
15
|
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
module Eventsimple
|
4
4
|
class Configuration
|
5
|
-
attr_reader :max_concurrency_retries
|
5
|
+
attr_reader :max_concurrency_retries
|
6
6
|
attr_writer :metadata_klass
|
7
|
+
attr_accessor :retry_reactor_on_record_not_found
|
7
8
|
|
8
9
|
attr_accessor :ui_visible_models
|
9
10
|
|
@@ -11,6 +12,7 @@ module Eventsimple
|
|
11
12
|
@dispatchers = []
|
12
13
|
@max_concurrency_retries = 2
|
13
14
|
@metadata_klass = 'Eventsimple::Metadata'
|
15
|
+
@retry_reactor_on_record_not_found = false
|
14
16
|
|
15
17
|
@ui_visible_models = [] # internal use only
|
16
18
|
end
|
@@ -29,8 +31,14 @@ module Eventsimple
|
|
29
31
|
@dispatchers = value
|
30
32
|
end
|
31
33
|
|
34
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
35
|
+
def dispatchers
|
36
|
+
@dispatchers_klass_consts ||= @dispatchers.map(&:constantize)
|
37
|
+
end
|
38
|
+
|
32
39
|
def metadata_klass
|
33
|
-
@
|
40
|
+
@metadata_klass_const ||= @metadata_klass.constantize
|
34
41
|
end
|
42
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
35
43
|
end
|
36
44
|
end
|
data/lib/eventsimple/engine.rb
CHANGED
@@ -11,11 +11,9 @@ module Eventsimple
|
|
11
11
|
end
|
12
12
|
|
13
13
|
config.after_initialize do
|
14
|
-
|
14
|
+
require 'eventsimple/reactor'
|
15
15
|
|
16
|
-
|
17
|
-
raise ArgumentError, 'dispatchers must inherit from Eventsimple::Dispatcher'
|
18
|
-
end
|
16
|
+
verify_dispatchers!
|
19
17
|
|
20
18
|
retry_intervals = Array.new(Eventsimple.configuration.max_concurrency_retries) { 0 }
|
21
19
|
|
@@ -33,5 +31,13 @@ module Eventsimple
|
|
33
31
|
}
|
34
32
|
end
|
35
33
|
end
|
34
|
+
|
35
|
+
def verify_dispatchers!
|
36
|
+
unless Eventsimple.configuration.dispatchers.all? { |dispatcher|
|
37
|
+
dispatcher < Eventsimple::Dispatcher
|
38
|
+
}
|
39
|
+
raise ArgumentError, 'dispatchers must inherit from Eventsimple::Dispatcher'
|
40
|
+
end
|
41
|
+
end
|
36
42
|
end
|
37
43
|
end
|
@@ -29,11 +29,11 @@ module Eventsimple
|
|
29
29
|
def self.dispatch(event)
|
30
30
|
reactors = rules.for(event)
|
31
31
|
reactors.sync.each do |reactor|
|
32
|
-
reactor.
|
32
|
+
reactor.perform_now(event)
|
33
33
|
event.reload
|
34
34
|
end
|
35
35
|
reactors.async.each do |reactor|
|
36
|
-
|
36
|
+
reactor.perform_later(event)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Eventsimple
|
4
|
+
class Reactor < ActiveJob::Base # rubocop:disable Rails/ApplicationJob
|
5
|
+
queue_as :eventsimple
|
6
|
+
|
7
|
+
def perform(event)
|
8
|
+
call(event)
|
9
|
+
end
|
10
|
+
|
11
|
+
around_perform do |job, block|
|
12
|
+
event_global_id = job.arguments.first
|
13
|
+
reactor_class = job.arguments.second
|
14
|
+
|
15
|
+
# For non database based processors like sidekiq, the reactor may trigger before the
|
16
|
+
# transaction is committed. Attempt to wait for the transaction to be commited before
|
17
|
+
# running the reactor. This is not a perfect solution, but it's better than nothing.
|
18
|
+
if Eventsimple.configuration.retry_reactor_on_record_not_found
|
19
|
+
begin
|
20
|
+
Retriable.with_context(:reactor) do
|
21
|
+
ApplicationRecord.uncached { GlobalID::Locator.locate(event_global_id) }
|
22
|
+
end
|
23
|
+
rescue ActiveRecord::RecordNotFound
|
24
|
+
Rails.logger.error("Event #{event_global_id} not found for reactor: #{reactor_class}")
|
25
|
+
return
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
block.call
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# legacy worker for backwards compatibility when upgrading from Eventsimple <= 1.0.0
|
3
4
|
module Eventsimple
|
4
5
|
class ReactorWorker
|
5
6
|
include Sidekiq::Worker
|
@@ -12,7 +13,7 @@ module Eventsimple
|
|
12
13
|
Rails.logger.error("Event #{event_global_id} not found for reactor: #{reactor_class}")
|
13
14
|
else
|
14
15
|
reactor = reactor_class.constantize
|
15
|
-
reactor.new(event)
|
16
|
+
reactor.new.call(event)
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
data/lib/eventsimple/version.rb
CHANGED
data/lib/eventsimple.rb
CHANGED
@@ -4,6 +4,7 @@ require "eventsimple/version"
|
|
4
4
|
require "eventsimple/engine"
|
5
5
|
|
6
6
|
require 'active_model'
|
7
|
+
require 'active_job'
|
7
8
|
require 'active_support'
|
8
9
|
require 'dry-types'
|
9
10
|
require 'dry-struct'
|
@@ -19,6 +20,7 @@ require 'eventsimple/metadata_type'
|
|
19
20
|
require 'eventsimple/metadata'
|
20
21
|
require 'eventsimple/dispatcher'
|
21
22
|
require 'eventsimple/event_dispatcher'
|
23
|
+
require 'eventsimple/reactor'
|
22
24
|
require 'eventsimple/reactor_worker'
|
23
25
|
require 'eventsimple/invalid_transition'
|
24
26
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eventsimple
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zulfiqar Ali
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-struct
|
@@ -220,14 +220,13 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
-
description: Event
|
223
|
+
description: Event sourcing toolkit using Rails and ActiveJob
|
224
224
|
email:
|
225
225
|
- zulfiqar@wealthsimple.com
|
226
226
|
executables: []
|
227
227
|
extensions: []
|
228
228
|
extra_rdoc_files: []
|
229
229
|
files:
|
230
|
-
- ".DS_Store"
|
231
230
|
- ".rspec"
|
232
231
|
- ".rubocop.yml"
|
233
232
|
- ".ruby-version"
|
@@ -272,6 +271,7 @@ files:
|
|
272
271
|
- lib/eventsimple/metadata_type.rb
|
273
272
|
- lib/eventsimple/outbox/consumer.rb
|
274
273
|
- lib/eventsimple/outbox/models/cursor.rb
|
274
|
+
- lib/eventsimple/reactor.rb
|
275
275
|
- lib/eventsimple/reactor_worker.rb
|
276
276
|
- lib/eventsimple/support/spec_helpers.rb
|
277
277
|
- lib/eventsimple/version.rb
|
@@ -300,5 +300,5 @@ requirements: []
|
|
300
300
|
rubygems_version: 3.4.10
|
301
301
|
signing_key:
|
302
302
|
specification_version: 4
|
303
|
-
summary: Event
|
303
|
+
summary: Event sourcing toolkit using Rails and ActiveJob
|
304
304
|
test_files: []
|
data/.DS_Store
DELETED
Binary file
|