eventsimple 1.3.3 → 1.4.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 +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +8 -4
- data/README.md +11 -14
- data/eventsimple.gemspec +1 -1
- data/lib/eventsimple/event.rb +3 -3
- data/lib/eventsimple/outbox/consumer.rb +30 -13
- data/lib/eventsimple/outbox/models/cursor.rb +5 -5
- data/lib/eventsimple/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 124fba5b3a01b411d35423265f33d4de64cf5a5824cb57491e502454557de90c
|
4
|
+
data.tar.gz: 9352b0c064d692bfc5f3623bf3c3a487a9c430a08f3526da2cb03ecaf36bd40c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 669a73959571590b6cc4ac32835c761d2aeb93a3b4c57e6043d79b94f5d271f523020f839845ae9f8aef0274757bb8de837d59a1822f6b51790fd582ff1e344a
|
7
|
+
data.tar.gz: d0740368d330aa08bfbd8e65fb7253800b1c8f055c29d9d92202b08812a79d64b74a08c8d557ab563efad57a021830a9a70fc4986b4bfd6aa652cb5e526b56db
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## Unreleased
|
8
8
|
|
9
|
+
## 1.4.0 - 2024-04-02
|
10
|
+
### Changed
|
11
|
+
- Production ready release of the outbox consumer
|
12
|
+
- Multiple consumers on an event stream are supported
|
13
|
+
- The outbox processor is instantiated once and takes the event as an argument.
|
14
|
+
- Added proper SIGTERM event handling and fixed shutdown behaviour.
|
15
|
+
|
9
16
|
## 1.3.3 - 2024-04-02
|
10
17
|
### Changed
|
11
18
|
- add `parent_record` configuration so it can be easily overwritten
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
eventsimple (1.
|
4
|
+
eventsimple (1.4.0)
|
5
5
|
dry-struct (~> 1.6)
|
6
6
|
dry-types (~> 1.7)
|
7
7
|
pg (~> 1.4)
|
8
8
|
rails (~> 7.0)
|
9
9
|
retriable (~> 3.1)
|
10
|
+
with_advisory_lock (>= 5.1)
|
10
11
|
|
11
12
|
GEM
|
12
13
|
remote: https://rubygems.org/
|
@@ -176,7 +177,7 @@ GEM
|
|
176
177
|
net-imap
|
177
178
|
net-pop
|
178
179
|
net-smtp
|
179
|
-
marcel (1.0.
|
180
|
+
marcel (1.0.4)
|
180
181
|
method_source (1.0.0)
|
181
182
|
mini_mime (1.1.5)
|
182
183
|
minitest (5.22.2)
|
@@ -189,7 +190,7 @@ GEM
|
|
189
190
|
net-protocol
|
190
191
|
net-protocol (0.2.2)
|
191
192
|
timeout
|
192
|
-
net-smtp (0.
|
193
|
+
net-smtp (0.5.0)
|
193
194
|
net-protocol
|
194
195
|
nio4r (2.7.0)
|
195
196
|
nokogiri (1.16.2-arm64-darwin)
|
@@ -205,7 +206,7 @@ GEM
|
|
205
206
|
parser (3.3.0.5)
|
206
207
|
ast (~> 2.4.1)
|
207
208
|
racc
|
208
|
-
pg (1.5.
|
209
|
+
pg (1.5.6)
|
209
210
|
polyglot (0.3.5)
|
210
211
|
pry (0.14.2)
|
211
212
|
coderay (~> 1.1)
|
@@ -348,6 +349,9 @@ GEM
|
|
348
349
|
websocket-driver (0.7.6)
|
349
350
|
websocket-extensions (>= 0.1.0)
|
350
351
|
websocket-extensions (0.1.5)
|
352
|
+
with_advisory_lock (5.1.0)
|
353
|
+
activerecord (>= 6.1)
|
354
|
+
zeitwerk (>= 2.6)
|
351
355
|
ws-style (7.4.3)
|
352
356
|
rubocop-rspec (>= 2.2.0)
|
353
357
|
rubocop-vendor (>= 0.11)
|
data/README.md
CHANGED
@@ -276,14 +276,11 @@ end
|
|
276
276
|
|
277
277
|
## Configuring an outbox consumer
|
278
278
|
|
279
|
-
For many use cases, async reactors are sufficient to handle workflows like making an API call or publishing to a message broker.
|
280
|
-
However since reactors use ActiveJob, order is not guaranteed.
|
279
|
+
For many use cases, async reactors are sufficient to handle workflows like making an API call or publishing to a message broker. However as reactors use ActiveJob, order is not guaranteed. For use cases requiring order, eventsimple provides an ordered outbox implementation.
|
281
280
|
|
282
|
-
|
281
|
+
**Caveat**: The current implementation leverages a single advisory lock to guarantee write order. This will impact write throughput on the model. On a db.rg6.large Aurora instance for example, write throughput is limited to ~300 events per second.
|
283
282
|
|
284
|
-
|
285
|
-
|
286
|
-
For more information on why an advisory lock is required:
|
283
|
+
For an explaination of why an advisory lock is required:
|
287
284
|
https://github.com/pawelpacana/account-basics
|
288
285
|
|
289
286
|
### Setup an ordered outbox
|
@@ -297,8 +294,6 @@ Generate migration to setup the outbox cursor table. This table is used to track
|
|
297
294
|
Create a consummer and processor class for the outbox.
|
298
295
|
Note: The presence of the consumer class moves all writes to the respective events table to be written using an advisory lock.
|
299
296
|
|
300
|
-
Only a single outbox consumer per events table is supported. **DO NOT** create multiple consumers for the same events table.
|
301
|
-
|
302
297
|
```ruby
|
303
298
|
require 'eventsimple/outbox/consumer'
|
304
299
|
|
@@ -306,6 +301,7 @@ module UserComponent
|
|
306
301
|
class Consumer
|
307
302
|
extend Eventsimple::Outbox::Consumer
|
308
303
|
|
304
|
+
identitfier 'UserComponent::Consumer'
|
309
305
|
consumes_event UserEvent
|
310
306
|
processor EventProcessor
|
311
307
|
end
|
@@ -315,12 +311,7 @@ end
|
|
315
311
|
```ruby
|
316
312
|
module UserComponent
|
317
313
|
class EventProcessor
|
318
|
-
def
|
319
|
-
@event = event
|
320
|
-
end
|
321
|
-
attr_reader :event
|
322
|
-
|
323
|
-
def call
|
314
|
+
def call(event)
|
324
315
|
Rails.logger.info("PROCESSING EVENT: #{event.id}")
|
325
316
|
end
|
326
317
|
end
|
@@ -339,6 +330,12 @@ Create a rake task to run the consumer
|
|
339
330
|
end
|
340
331
|
```
|
341
332
|
|
333
|
+
To set the cursor position to the latest event:
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
Eventsimple::Outbox::Cursor.set('UserComponent::Consumer', UserEvent.last.id)
|
337
|
+
```
|
338
|
+
|
342
339
|
## Helper methods
|
343
340
|
Some convenience methods are provided to help with common use cases.
|
344
341
|
|
data/eventsimple.gemspec
CHANGED
@@ -28,9 +28,9 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_runtime_dependency 'pg', '~> 1.4'
|
29
29
|
spec.add_runtime_dependency 'rails', '~> 7.0'
|
30
30
|
spec.add_runtime_dependency 'retriable', '~> 3.1'
|
31
|
+
spec.add_runtime_dependency 'with_advisory_lock', '>= 5.1'
|
31
32
|
|
32
33
|
spec.add_development_dependency 'bundle-audit'
|
33
|
-
spec.add_development_dependency 'factory_bot_rails'
|
34
34
|
spec.add_development_dependency 'fuubar'
|
35
35
|
spec.add_development_dependency 'git'
|
36
36
|
spec.add_development_dependency 'guard-rspec'
|
data/lib/eventsimple/event.rb
CHANGED
@@ -14,8 +14,8 @@ module Eventsimple
|
|
14
14
|
class_attribute :_aggregate_id
|
15
15
|
self._aggregate_id = aggregate_id
|
16
16
|
|
17
|
-
class_attribute :
|
18
|
-
class_attribute :
|
17
|
+
class_attribute :_outbox_enabled
|
18
|
+
class_attribute :_consumer_group_size
|
19
19
|
|
20
20
|
class_attribute :_on_invalid_transition
|
21
21
|
self._on_invalid_transition = ->(error) { raise error }
|
@@ -161,7 +161,7 @@ module Eventsimple
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def with_locks(&block)
|
164
|
-
if
|
164
|
+
if _outbox_enabled
|
165
165
|
base_class.with_advisory_lock(base_class.name, { transaction: true }, &block)
|
166
166
|
else
|
167
167
|
yield
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'with_advisory_lock'
|
3
4
|
require 'eventsimple/outbox/models/cursor'
|
4
5
|
|
5
6
|
module Eventsimple
|
@@ -9,39 +10,55 @@ module Eventsimple
|
|
9
10
|
klass.class_exec do
|
10
11
|
class_attribute :_event_klass
|
11
12
|
class_attribute :_processor_klass
|
13
|
+
class_attribute :_processor
|
12
14
|
class_attribute :stop_consumer, default: false
|
13
|
-
|
14
|
-
Signal.trap('SIGINT') do
|
15
|
-
self.stop_consumer = true
|
16
|
-
$stdout.puts('SIGINT received, stopping consumer')
|
17
|
-
end
|
15
|
+
class_attribute :_identifier, default: name.to_s
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
19
|
+
def identifier(name = nil)
|
20
|
+
self._identifier = name
|
21
|
+
end
|
22
|
+
|
23
|
+
def consumes_event(event_klass, group_size: 1)
|
24
|
+
event_klass._outbox_enabled = true
|
25
|
+
event_klass._consumer_group_size = group_size
|
24
26
|
|
25
27
|
self._event_klass = event_klass
|
26
28
|
end
|
27
29
|
|
28
30
|
def processor(processor_klass)
|
29
31
|
self._processor_klass = processor_klass
|
32
|
+
self._processor = processor_klass.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def start(group_number: 0) # rubocop:disable Metrics/AbcSize
|
36
|
+
Signal.trap('INT') do
|
37
|
+
self.stop_consumer = true
|
38
|
+
$stdout.puts('INT received, stopping consumer')
|
39
|
+
end
|
40
|
+
Signal.trap('TERM') do
|
41
|
+
self.stop_consumer = true
|
42
|
+
$stdout.puts('TERM received, stopping consumer')
|
43
|
+
end
|
44
|
+
|
45
|
+
run_consumer(group_number: group_number)
|
30
46
|
end
|
31
47
|
|
32
|
-
def
|
33
|
-
cursor = Outbox::Cursor.fetch(
|
48
|
+
def run_consumer(group_number:)
|
49
|
+
cursor = Outbox::Cursor.fetch(_identifier, group_number: group_number)
|
34
50
|
|
35
51
|
until stop_consumer
|
36
52
|
_event_klass.unscoped.in_batches(start: cursor + 1, load: true).each do |batch|
|
37
53
|
batch.each do |event|
|
38
|
-
|
54
|
+
_processor.call(event)
|
39
55
|
|
56
|
+
cursor = event.id
|
40
57
|
break if stop_consumer
|
41
58
|
end
|
42
59
|
|
43
|
-
cursor
|
44
|
-
|
60
|
+
Outbox::Cursor.set(_identifier, cursor, group_number: group_number)
|
61
|
+
break if stop_consumer
|
45
62
|
end
|
46
63
|
|
47
64
|
sleep(1)
|
@@ -5,19 +5,19 @@ module Eventsimple
|
|
5
5
|
class Cursor < Eventsimple.configuration.parent_record_klass
|
6
6
|
self.table_name = 'eventsimple_outbox_cursors'
|
7
7
|
|
8
|
-
def self.fetch(
|
9
|
-
existing = find_by(
|
8
|
+
def self.fetch(identifier, group_number: 0)
|
9
|
+
existing = find_by(identifier: identifier.to_s, group_number: group_number)
|
10
10
|
existing ? existing.cursor : 0
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.set(
|
13
|
+
def self.set(identifier, cursor, group_number: 0)
|
14
14
|
upsert(
|
15
15
|
{
|
16
|
-
|
16
|
+
identifier: identifier.to_s,
|
17
17
|
group_number: group_number,
|
18
18
|
cursor: cursor,
|
19
19
|
},
|
20
|
-
unique_by: [:
|
20
|
+
unique_by: [:identifier, :group_number],
|
21
21
|
)
|
22
22
|
end
|
23
23
|
end
|
data/lib/eventsimple/version.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.4.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: 2024-04-
|
11
|
+
date: 2024-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-struct
|
@@ -81,21 +81,21 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.1'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: with_advisory_lock
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
90
|
-
type: :
|
89
|
+
version: '5.1'
|
90
|
+
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '5.1'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: bundle-audit
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|