eventsimple 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +104 -84
- data/README.md +49 -20
- data/Rakefile +1 -0
- data/app/controllers/eventsimple/entities_controller.rb +1 -1
- data/eventsimple.gemspec +2 -2
- data/lib/eventsimple/configuration.rb +17 -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 +1 -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: c89fd452fb688ea84659fb341c78b8c30ce14632a948750fab7ec831d34baf0e
|
4
|
+
data.tar.gz: ef3abdd6bd8158e65e476a23170c9e9b42329974abd7de679f2ca27c2203972e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 804e17de52e7f4f5c172b1e3a8735c1037966298aaf40952ffc36446ad37514890fabe4cf602164932723598c83d8060a6ab0f9a05d39d01b932b9fbcd0bd401
|
7
|
+
data.tar.gz: 77589ed9dbb33bd4142282599f00a53790468ebdd1695d74bd19a1a7d515937988c31c70db84cbc2b91a76ce0e3b32aab910735cacbfc3f4e81ce055fc4023c7
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## Unreleased
|
8
8
|
|
9
|
+
## 1.1.0 - 2023-06-11
|
10
|
+
### Changed
|
11
|
+
- Reactors now use ActiveJob instead of Sidekiq directly. This allows for more
|
12
|
+
flexibility in the future and allows for the use of other job backends.
|
13
|
+
The reactor class definition must now inherit from Eventsimple::Reactor and are no longer
|
14
|
+
instantiated with the event as an argument. Instead, the event is passed to the `call` method.
|
15
|
+
This change is backwards compatible with existing inflight reactor jobs.
|
16
|
+
|
9
17
|
## 1.0.0 - 2023-05-03
|
10
18
|
### Changed
|
11
19
|
- 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.0)
|
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)
|
@@ -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.1)
|
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,53 @@ 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: Reactors inherit from an ActiveJob base class.
|
78
|
+
# Set the parent class for reactors.
|
79
|
+
# Defaults to ActiveJob::Base.
|
80
|
+
config.active_job_parent_klass = 'ApplicationJob'
|
81
|
+
|
82
|
+
# Optional: When using an ActiveJob adapter that writes to a different data store like redis,
|
83
|
+
# 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.
|
84
|
+
# Enable this option to retry the reactor inline if the event is not found.
|
85
|
+
# Defaults to false.
|
86
|
+
config.retry_reactor_on_record_not_found = true
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
56
90
|
Generate a migration and add `Eventsimple` to an existing ActiveRecord model.
|
57
91
|
|
58
92
|
```ruby
|
@@ -93,7 +127,7 @@ add_column :users, :lock_version, :integer
|
|
93
127
|
|
94
128
|
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
129
|
|
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
|
130
|
+
`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
131
|
|
98
132
|
### Event Table definition
|
99
133
|
|
@@ -192,14 +226,14 @@ They should **only** contain business logic that make additional database writes
|
|
192
226
|
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
227
|
|
194
228
|
#### Async Reactors
|
195
|
-
Async reactors are executed via
|
229
|
+
Async reactors are executed via ActiveJob. Eventsimple implements checks to enforce reliable eventually consistent behaviour.
|
196
230
|
|
197
231
|
Use Async reactors to kick off async workflows or writes to external data sources as a side effect of model updates.
|
198
232
|
|
199
233
|
Reactor example:
|
200
234
|
|
201
235
|
```ruby
|
202
|
-
# Register your dispatch
|
236
|
+
# Register your dispatch classes in config/initializers/eventsimple.rb.
|
203
237
|
Eventsimple.configure do |config|
|
204
238
|
config.dispatchers = %w[
|
205
239
|
UserComponent::Dispatcher
|
@@ -224,15 +258,10 @@ end
|
|
224
258
|
|
225
259
|
# Reactor classes accept the event as the only argument in the constructor
|
226
260
|
# and must define a `call` method
|
227
|
-
module UserComponent::Reactors::Created
|
261
|
+
module UserComponent::Reactors::Created < Eventsimple::Reactor
|
228
262
|
class SendNotification
|
229
|
-
def
|
230
|
-
|
231
|
-
@user = event.aggregate
|
232
|
-
end
|
233
|
-
attr_reader :event, :user
|
234
|
-
|
235
|
-
def call
|
263
|
+
def call(event)
|
264
|
+
user = event.aggregate
|
236
265
|
# do something
|
237
266
|
end
|
238
267
|
end
|
@@ -242,7 +271,7 @@ end
|
|
242
271
|
## Configuring an outbox consumer
|
243
272
|
|
244
273
|
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
|
274
|
+
However since reactors use ActiveJob, order is not guaranteed.
|
246
275
|
|
247
276
|
Eventsimple provides an outbox implementation with order and eventual consistency guarantees.
|
248
277
|
|
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,10 @@
|
|
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_writer :active_job_parent_klass
|
8
|
+
attr_accessor :retry_reactor_on_record_not_found
|
7
9
|
|
8
10
|
attr_accessor :ui_visible_models
|
9
11
|
|
@@ -11,6 +13,8 @@ module Eventsimple
|
|
11
13
|
@dispatchers = []
|
12
14
|
@max_concurrency_retries = 2
|
13
15
|
@metadata_klass = 'Eventsimple::Metadata'
|
16
|
+
@active_job_parent_klass = 'ActiveJob::Base'
|
17
|
+
@retry_reactor_on_record_not_found = false
|
14
18
|
|
15
19
|
@ui_visible_models = [] # internal use only
|
16
20
|
end
|
@@ -29,8 +33,19 @@ module Eventsimple
|
|
29
33
|
@dispatchers = value
|
30
34
|
end
|
31
35
|
|
36
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
37
|
+
|
38
|
+
def dispatchers
|
39
|
+
@dispatchers_klass_consts ||= @dispatchers.map(&:constantize)
|
40
|
+
end
|
41
|
+
|
32
42
|
def metadata_klass
|
33
|
-
@
|
43
|
+
@metadata_klass_const ||= @metadata_klass.constantize
|
44
|
+
end
|
45
|
+
|
46
|
+
def active_job_parent_klass
|
47
|
+
@active_job_parent_klass_const ||= @active_job_parent_klass.constantize
|
34
48
|
end
|
49
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
35
50
|
end
|
36
51
|
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 < Eventsimple.configuration.active_job_parent_klass
|
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
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.0
|
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-15 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
|