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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b1d2a6b5eb979f7ba40f0bd7500b001b43497259e56432cb0f6c4d3bf9d2816
4
- data.tar.gz: e0d2cc3ca58637a1d0267f634b03575f03db026bcba7f62d25d6b82b7228c610
3
+ metadata.gz: c89fd452fb688ea84659fb341c78b8c30ce14632a948750fab7ec831d34baf0e
4
+ data.tar.gz: ef3abdd6bd8158e65e476a23170c9e9b42329974abd7de679f2ca27c2203972e
5
5
  SHA512:
6
- metadata.gz: 76d5867b765ae7cb39a87ee8f094a70f2fdd5d9cb81f69e198cf26e4dffa01ae15fccc40afce852922f8f1fc408c01c59d13aa87e6d59f81b52d0f1ad446cd6a
7
- data.tar.gz: 2713b5f26dbd5ada7db2135e2cd7453a1dea13e853f8046b57d989553bbe4ae08052dc1aa1b59b87bfdc3e5eb782cbf8ae90fc7e47f116641444efcd57a06e2f
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.0.0)
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.4.3)
16
- actionpack (= 7.0.4.3)
17
- activesupport (= 7.0.4.3)
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.4.3)
21
- actionpack (= 7.0.4.3)
22
- activejob (= 7.0.4.3)
23
- activerecord (= 7.0.4.3)
24
- activestorage (= 7.0.4.3)
25
- activesupport (= 7.0.4.3)
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.4.3)
31
- actionpack (= 7.0.4.3)
32
- actionview (= 7.0.4.3)
33
- activejob (= 7.0.4.3)
34
- activesupport (= 7.0.4.3)
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.4.3)
41
- actionview (= 7.0.4.3)
42
- activesupport (= 7.0.4.3)
43
- rack (~> 2.0, >= 2.2.0)
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.4.3)
48
- actionpack (= 7.0.4.3)
49
- activerecord (= 7.0.4.3)
50
- activestorage (= 7.0.4.3)
51
- activesupport (= 7.0.4.3)
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.4.3)
55
- activesupport (= 7.0.4.3)
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.4.3)
61
- activesupport (= 7.0.4.3)
60
+ activejob (7.0.5)
61
+ activesupport (= 7.0.5)
62
62
  globalid (>= 0.3.6)
63
- activemodel (7.0.4.3)
64
- activesupport (= 7.0.4.3)
65
- activerecord (7.0.4.3)
66
- activemodel (= 7.0.4.3)
67
- activesupport (= 7.0.4.3)
68
- activestorage (7.0.4.3)
69
- actionpack (= 7.0.4.3)
70
- activejob (= 7.0.4.3)
71
- activerecord (= 7.0.4.3)
72
- activesupport (= 7.0.4.3)
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.4.3)
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.0)
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.13.0)
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.20.0)
148
+ loofah (2.21.3)
147
149
  crass (~> 1.0.2)
148
- nokogiri (>= 1.5.9)
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.14.3-arm64-darwin)
172
+ nokogiri (1.15.2-arm64-darwin)
171
173
  racc (~> 1.4)
172
- nokogiri (1.14.3-x86_64-linux)
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.1)
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.2.2)
191
+ puma (6.3.0)
189
192
  nio4r (~> 2.0)
190
- racc (1.6.2)
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.4.3)
195
- actioncable (= 7.0.4.3)
196
- actionmailbox (= 7.0.4.3)
197
- actionmailer (= 7.0.4.3)
198
- actionpack (= 7.0.4.3)
199
- actiontext (= 7.0.4.3)
200
- actionview (= 7.0.4.3)
201
- activejob (= 7.0.4.3)
202
- activemodel (= 7.0.4.3)
203
- activerecord (= 7.0.4.3)
204
- activestorage (= 7.0.4.3)
205
- activesupport (= 7.0.4.3)
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.4.3)
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.5.0)
212
- loofah (~> 2.19, >= 2.19.1)
213
- railties (7.0.4.3)
214
- actionpack (= 7.0.4.3)
215
- activesupport (= 7.0.4.3)
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.0)
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.1)
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.11)
248
- rspec-expectations (~> 3.11)
249
- rspec-mocks (~> 3.11)
250
- rspec-support (~> 3.11)
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.50.2)
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.28.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-performance (1.17.1)
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.20.0)
279
+ rubocop-rspec (2.22.0)
274
280
  rubocop (~> 1.33)
275
281
  rubocop-capybara (~> 2.17)
276
- rubocop-vendor (0.9.2)
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.0)
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
- thor (1.2.1)
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 (6.14.6)
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.6.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 Badge](https://github.com/wealthsimple/eventsimple/actions/workflows/main.yml/badge.svg)](https://github.com/wealthsimple/eventsimple/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 Sidekiq.
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) to implement a simple event driven system.
15
- Async workflows are handled using [Sidekiq](https://github.com/sidekiq/sidekiq).
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 using STI and look like this:
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 to use it:
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 type column.
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 Sidekiq. Eventsimple implements checks to enforce reliable eventually consistent behaviour.
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 class in an initializer.
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 initialize(event)
230
- @event = event
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 Sidekiq, order is not guaranteed.
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
@@ -3,6 +3,7 @@
3
3
  require "bundler/setup"
4
4
 
5
5
  APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
6
+ Rake.load_rakefile 'spec/dummy/Rakefile'
6
7
  load 'rails/tasks/engine.rake'
7
8
 
8
9
  load 'rails/tasks/statistics.rake'
@@ -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 driven toolkit using Rails and Sidekiq'
12
- spec.description = 'Event driven architecture using Rails and Sidekiq'
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, :dispatchers
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
- @klass ||= @metadata_klass.constantize # rubocop:disable Naming/MemoizedInstanceVariableName
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
@@ -11,11 +11,9 @@ module Eventsimple
11
11
  end
12
12
 
13
13
  config.after_initialize do
14
- dispatchers = Eventsimple.configuration.dispatchers.map(&:constantize)
14
+ require 'eventsimple/reactor'
15
15
 
16
- unless dispatchers.all? { |dispatcher| dispatcher.superclass == Eventsimple::Dispatcher }
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.new(event).call
32
+ reactor.perform_now(event)
33
33
  event.reload
34
34
  end
35
35
  reactors.async.each do |reactor|
36
- ReactorWorker.perform_async(event.to_global_id.to_s, reactor.to_s)
36
+ reactor.perform_later(event)
37
37
  end
38
38
  end
39
39
 
@@ -13,7 +13,7 @@ module Eventsimple
13
13
 
14
14
  Signal.trap('SIGINT') do
15
15
  self.stop_consumer = true
16
- STDOUT.puts('SIGINT received, stopping consumer')
16
+ $stdout.puts('SIGINT received, stopping consumer')
17
17
  end
18
18
  end
19
19
  end
@@ -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).call
16
+ reactor.new.call(event)
16
17
  end
17
18
  end
18
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Eventsimple
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
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'
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.0.0
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-05-03 00:00:00.000000000 Z
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 driven architecture using Rails and Sidekiq
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 driven toolkit using Rails and Sidekiq
303
+ summary: Event sourcing toolkit using Rails and ActiveJob
304
304
  test_files: []
data/.DS_Store DELETED
Binary file