koine-event_manager 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +57 -0
- data/lib/koine/event_manager/event_listener.rb +12 -3
- data/lib/koine/event_manager/event_manager.rb +18 -3
- data/lib/koine/event_manager/rails.rb +65 -0
- data/lib/koine/event_manager/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48dfe1c350d523d6dc0b09cec81848a225fa41cf02edd07fbe3c902f4e9ba826
|
|
4
|
+
data.tar.gz: c5d594bac4dc7e8ed338d485e5e5a2fcd4558f35533cd496d4497783644a543f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a465c356d1944acb9cb2c2aea7f4f269877a253dce7fce66798cb9cd6560c80c196c0151f7fbba0c746e6816bbca3038ac31f6e0a1bc272d42696f52b6af7755
|
|
7
|
+
data.tar.gz: 87700e2f619c41c382cace295d90b3d76afba14c3af5174769117b78196796199ed6921faa4b6e97ece79bb1889589b6b48089d9dc811d71d82081347230fcd1
|
data/README.md
CHANGED
|
@@ -242,12 +242,69 @@ class SessionsController < ApplicationController
|
|
|
242
242
|
end
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
+
### Error Isolation
|
|
246
|
+
|
|
247
|
+
By default, an exception raised by a listener or subscriber propagates and aborts
|
|
248
|
+
the dispatch. Pass an `on_error` callback to isolate failures so the remaining
|
|
249
|
+
listeners still run:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
event_manager = Koine::EventManager::EventManager.new(
|
|
253
|
+
on_error: ->(error, event:, listener:) { Bugsnag.notify(error) }
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The callback is invoked as `on_error.call(error, event:, listener:)`, so it **must
|
|
258
|
+
accept the `event:` and `listener:` keyword arguments** (the failing event and the
|
|
259
|
+
listener/subscriber that raised). Use them to attribute the failure, or ignore them
|
|
260
|
+
with a splat:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
on_error: ->(error, **) { Bugsnag.notify(error) }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Without `on_error` the behavior is unchanged (errors re-raise).
|
|
267
|
+
|
|
268
|
+
### Post-commit Dispatch (Rails)
|
|
269
|
+
|
|
270
|
+
`require "koine/event_manager/rails"` adds `TransactionalEventManager`, a drop-in
|
|
271
|
+
subclass that defers dispatch until the current database transaction commits (or
|
|
272
|
+
runs immediately when none is open), via `ActiveRecord.after_all_transactions_commit`.
|
|
273
|
+
This keeps listeners from reacting to changes that a rollback would undo.
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
require "koine/event_manager/rails"
|
|
277
|
+
|
|
278
|
+
bus = Koine::EventManager::TransactionalEventManager.new(
|
|
279
|
+
on_error: ->(error, **) { Bugsnag.notify(error) }
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Requires ActiveRecord >= 7.2; the core library remains dependency-free, so only load
|
|
284
|
+
this file from within a Rails/ActiveRecord process.
|
|
285
|
+
|
|
286
|
+
Subscribers (and block listeners) are deferred by default. A reaction that must run
|
|
287
|
+
**inside** the transaction — e.g. to write a related record atomically, or to raise
|
|
288
|
+
and roll the whole operation back — can opt out with `after_commit: false`:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
bus.subscribe(AuditWriter.new, to: ProposalCreated) # post-commit, isolated
|
|
292
|
+
bus.subscribe(InventoryReserver.new, to: ProposalCreated, after_commit: false) # in-transaction
|
|
293
|
+
bus.listen_to(ProposalCreated, after_commit: false) { |e| ... } # in-transaction block
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Synchronous (`after_commit: false`) listeners run inline and their errors **propagate**
|
|
297
|
+
(bypassing `on_error`), so a failed integrity reaction aborts the surrounding
|
|
298
|
+
transaction. Deferred listeners stay isolated by `on_error`.
|
|
299
|
+
|
|
245
300
|
## API Reference
|
|
246
301
|
|
|
247
302
|
### EventManager
|
|
248
303
|
|
|
304
|
+
- `EventManager.new(on_error: nil)` - Optionally isolate listener errors (see Error Isolation)
|
|
249
305
|
- `listen_to(event_class, &block)` - Register a block to handle events
|
|
250
306
|
- `trigger(event)` - Dispatch an event to all listeners and subscribers
|
|
307
|
+
- `publish(event)` - Alias for `trigger` (pub/sub vocabulary)
|
|
251
308
|
- `subscribe(subscriber, to: event_type)` - Add a subscriber for an event type
|
|
252
309
|
- `unsubscribe(subscriber, from: event_type)` - Remove a subscriber
|
|
253
310
|
- `attach_listener(listener)` - Attach an EventListener instance
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
module Koine
|
|
4
4
|
module EventManager
|
|
5
5
|
class EventListener
|
|
6
|
-
def initialize
|
|
6
|
+
def initialize(on_error: nil)
|
|
7
7
|
@listeners = {}
|
|
8
8
|
@subscribers = {}
|
|
9
|
+
@on_error = on_error
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def listen_to(event_type, &block)
|
|
@@ -30,13 +31,13 @@ module Koine
|
|
|
30
31
|
|
|
31
32
|
def trigger(event_object)
|
|
32
33
|
listeners_for(event_object.class).each do |block|
|
|
33
|
-
block.call(event_object)
|
|
34
|
+
dispatch(event_object, block) { block.call(event_object) }
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
subscribers.each do |subscriber, events|
|
|
37
38
|
events.each do |event|
|
|
38
39
|
if event_object.class.ancestors.map(&:to_s).include?(event.to_s)
|
|
39
|
-
subscriber.publish(event_object)
|
|
40
|
+
dispatch(event_object, subscriber) { subscriber.publish(event_object) }
|
|
40
41
|
end
|
|
41
42
|
end
|
|
42
43
|
end
|
|
@@ -50,6 +51,14 @@ module Koine
|
|
|
50
51
|
|
|
51
52
|
private
|
|
52
53
|
|
|
54
|
+
def dispatch(event, listener)
|
|
55
|
+
yield
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
raise if @on_error.nil?
|
|
58
|
+
|
|
59
|
+
@on_error.call(e, event: event, listener: listener)
|
|
60
|
+
end
|
|
61
|
+
|
|
53
62
|
def add_listener(event_type, &block)
|
|
54
63
|
listeners[event_type.to_s] ||= []
|
|
55
64
|
listeners[event_type.to_s] << block
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
module Koine
|
|
4
4
|
module EventManager
|
|
5
5
|
class EventManager
|
|
6
|
-
def initialize
|
|
7
|
-
@
|
|
6
|
+
def initialize(on_error: nil)
|
|
7
|
+
@on_error = on_error
|
|
8
|
+
@internal_listener = EventListener.new(on_error: on_error)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def publish(event)
|
|
12
|
+
trigger(event)
|
|
8
13
|
end
|
|
9
14
|
|
|
10
15
|
def listen_to(event, &block)
|
|
@@ -31,13 +36,23 @@ module Koine
|
|
|
31
36
|
@internal_listener.trigger(event)
|
|
32
37
|
|
|
33
38
|
listeners.each do |listener|
|
|
34
|
-
listener.trigger(event)
|
|
39
|
+
dispatch(event, listener) { listener.trigger(event) }
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
|
|
38
43
|
def listeners
|
|
39
44
|
@listeners ||= []
|
|
40
45
|
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def dispatch(event, listener)
|
|
50
|
+
yield
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
raise if @on_error.nil?
|
|
53
|
+
|
|
54
|
+
@on_error.call(e, event: event, listener: listener)
|
|
55
|
+
end
|
|
41
56
|
end
|
|
42
57
|
end
|
|
43
58
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'koine/event_manager'
|
|
4
|
+
|
|
5
|
+
module Koine
|
|
6
|
+
module EventManager
|
|
7
|
+
# Dispatches listeners after the current DB transaction commits (or runs
|
|
8
|
+
# immediately when none is open), so they never react to changes a rollback
|
|
9
|
+
# would undo. Requires ActiveRecord >= 7.2.
|
|
10
|
+
#
|
|
11
|
+
# Individual subscribers/listeners may opt into synchronous, in-transaction
|
|
12
|
+
# dispatch with `after_commit: false`. Those run inline and their errors
|
|
13
|
+
# propagate (bypassing `on_error` isolation), so an integrity reaction can
|
|
14
|
+
# abort the surrounding transaction. Post-commit listeners stay isolated.
|
|
15
|
+
class TransactionalEventManager < EventManager
|
|
16
|
+
alias deferred_dispatch trigger
|
|
17
|
+
|
|
18
|
+
def initialize(on_error: nil)
|
|
19
|
+
super
|
|
20
|
+
# In-transaction listeners: no on_error isolation, so a failure propagates
|
|
21
|
+
# and can roll the surrounding transaction back.
|
|
22
|
+
@synchronous = EventListener.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def subscribe(subscriber, to:, after_commit: true)
|
|
26
|
+
return @synchronous.subscribe(subscriber, to: to) unless after_commit
|
|
27
|
+
|
|
28
|
+
super(subscriber, to: to)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def listen_to(event, after_commit: true, &block)
|
|
32
|
+
return @synchronous.listen_to(event, &block) unless after_commit
|
|
33
|
+
|
|
34
|
+
super(event, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def unsubscribe(subscriber, from:)
|
|
38
|
+
@synchronous.unsubscribe(subscriber, from: from)
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def trigger(event)
|
|
43
|
+
@synchronous.trigger(event)
|
|
44
|
+
|
|
45
|
+
unless active_record_available?
|
|
46
|
+
raise LoadError, 'TransactionalEventManager requires ActiveRecord >= 7.2 ' \
|
|
47
|
+
'(after_all_transactions_commit). Load Rails before using it.'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
::ActiveRecord.after_all_transactions_commit do
|
|
51
|
+
deferred_dispatch(event)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private :deferred_dispatch
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def active_record_available?
|
|
60
|
+
defined?(::ActiveRecord) &&
|
|
61
|
+
::ActiveRecord.respond_to?(:after_all_transactions_commit)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: koine-event_manager
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marcelo Jacobus
|
|
@@ -32,6 +32,7 @@ files:
|
|
|
32
32
|
- lib/koine/event_manager.rb
|
|
33
33
|
- lib/koine/event_manager/event_listener.rb
|
|
34
34
|
- lib/koine/event_manager/event_manager.rb
|
|
35
|
+
- lib/koine/event_manager/rails.rb
|
|
35
36
|
- lib/koine/event_manager/version.rb
|
|
36
37
|
homepage: https://github.com/mjacobus/koine-event-manager
|
|
37
38
|
licenses:
|