Last active
April 20, 2021 12:05
-
-
Save krisleech/73b46d95e7ec257908a9b240069b3c11 to your computer and use it in GitHub Desktop.
Revisions
-
krisleech revised this gist
Apr 20, 2021 . 1 changed file with 8 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,33 +1,20 @@ ``` 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. -
krisleech created this gist
Apr 20, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,368 @@ ``` module BackgroundEventHandler prepend WithMetadata module WithMetadata def call(event) with_metadata(....) super end end end alias :perform :call end ``` ``` class MyEventHandler include BackgroundEventHandler def call(event) end end ``` 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: ```ruby AsyncHandler.new(MyHandler.new) class AsyncHandler < SimpleDelegator def initialize(handler) handler.include(Background) super(handler) end end ``` can we do this at class level: ```ruby AsyncHandler.new(MyHandler) class AsyncHandler < SimpleDelegator def new(*args) super.tap { |handler| handler.include(Background) } end end ``` could add a convience method: ```ruby module EventHander def self.async AsyncHandler.new(self) end end MyHandler.async ``` ```ruby # 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. ```ruby # 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. ```ruby # 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. ```ruby # 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) ```