class MyEventHandler
include BackgroundEventHandler
def call(event)
end
end
class MyOtherEventHandler
include ForegroundEventHandler
def call(event)
end
end
in the background event handler call is alised to perform. This means the background event handler can be used sync by just calling call
but this means causation and correlation ids are not set as they are wrapped in perform.
Sidekiq require to impliment perform, but calls perform_async based on https://railseventstore.org/docs/v2/subscribe/ we register multiple dispatchers, the first one to match is used. the match is based on the event handler responding to "perform_async" so we could make event handlers either sync or async by adding/removing this method, or patching respond_to? Maybe we don't include Sidekiq until the object is initalized, so by default it is sync.
e.g.
# this would work for an individual handler, but we register classes.
module EventHandler
def async
self.include(Background)
end
end
class MyEventHandler
include EventHandler
def call(event)
end
end
MyEventHandler.async.call(event)
However we always call new
on classes, we don't register objects.
Registering objects would mean the existing ones must all be stateless. This is proberbly true, TBC.
We could also use SimpleDelegator to do this:
AsyncHandler.new(MyHandler.new)
class AsyncHandler < SimpleDelegator
def initialize(handler)
handler.include(Background)
super(handler)
end
end
can we do this at class level:
AsyncHandler.new(MyHandler)
class AsyncHandler < SimpleDelegator
def new(*args)
super.tap { |handler| handler.include(Background) }
end
end
could add a convience method:
module EventHander
def self.async
AsyncHandler.new(self)
end
end
MyHandler.async
# class level (default) flag for sync/async and instance level one which has presidence
module EventHandler
def self.async # class macro
@async = true
end
def self.sync(*args)
instance = allocate
instance.async = true
instance.initialize(*args)
instance
end
attr_accessor :async
def initialize(*args)
include Background if !sync || self.class.async
super
end
end
class MyBackEventHandler < EventHandler
async
def call(event)
# ...
end
end
class MyForeEventHandler < EventHandler
def call(event)
# ...
end
end
MyBackEventHandler.new.call(event) # runs in background
MyBackForeHandler.new.call(event) # runs in foreground
how to run background in foreground for backfill:
MyBackEventHandler.new(async: true).call(event) # runs in foreground
MyBackEventHandler.sync.call(event) # runs in foreground
Problem (minor-ish), async and sync methods seem like oppisites but do very different things, one is a class macro, the other constructs an oject.
# class level (default) flag for sync/async and instance level one which has presidence
module EventHandler
def self.async(*args)
instance = allocate
instance.async = true
instance.initialize(*args)
instance
end
def self.sync(*args)
instance = allocate
instance.async = false
instance.initialize(*args)
instance
end
def initialize(*args)
include Background if async
super
end
attr_accessor :async # nil (falsey) by default
end
class MyBackEventHandler < EventHandler
background
def call(event)
# ...
end
end
class MyForeEventHandler < EventHandler
def call(event)
# ...
end
end
MyBackEventHandler.new.call(event) # runs in background
MyBackEventHandler.sync.call(event) # runs in background
MyBackForeHandler.async.call(event) # runs in foreground
how to run background in foreground for backfill:
MyBackEventHandler.sync.call(event) # runs in foreground
problem is we always call #new
and this defaults to sync.
To do this we would need to include an sync/async flag when registering the event handler so we know if to initialize using sync or async.
# class level flag for sync/async and instance level one which has presidence
# Background (Sidekiq, ::RailsEventStore::AsyncHandler) added at runtime
module EventHandler
def self.async # class macro
@async = true
end
def self.sync # class marco
@sync = false
end
def self.sync?
!!@async
end
def sync
@async = false
end
def async
@async = true
end
def async?
async || self.class.sync?
end
def initialize(*args)
include Background if async?
super
end
end
class MyBackEventHandler < EventHandler
async
def call(event)
# ...
end
end
class MyForeEventHandler < EventHandler
async # optional since default
def call(event)
# ...
end
end
MyBackEventHandler.new.call(event) # runs in background
MyBackForeHandler.new.call(event) # runs in foreground
how to run background in foreground for backfill:
MyBackEventHandler.new.sync.call(event) # runs in foreground (will not work, Background already included or not)
^ too late to call sync after new, since background has already been included.
# class level flag for sync/async and instance level one which has presidence
# Background (Sidekiq, ::RailsEventStore::AsyncHandler) added at runtime
module EventHandler
def self.async # class macro
@async = true
end
def self.sync # class marco
@sync = false
end
def self.sync?
!!@async
end
def sync
@async = false
end
def async
@async = true
end
def async?
async || self.class.sync?
end
def self.new_sync(*args)
instance = allocate
insatnce.sync = true
instance.initialize(*args)
instance
end
def initialize(*args)
include Background if async?
super
end
end
class MyBackEventHandler < EventHandler
async
def call(event)
# ...
end
end
class MyForeEventHandler < EventHandler
sync # optional since default
def call(event)
# ...
end
end
MyBackEventHandler.new.call(event) # runs in background
MyBackForeHandler.new.call(event) # runs in foreground
how to run background in foreground for backfill:
MyBackEventHandler.new_sync.call(event) # runs in foreground)