This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.
Message originally sent by slack user U71N49ZTMNC
Hi all! A pleasure being part of this community!
My team is working on a new rails project and we want to implement packwerk. So far so good!
We want to come up with a solution to invert dependencies by using some kind of pub/sub mechanism. Say we have a Sales package, and a Reports package, Reports depending on Sales.
We’re thinking on using ActiveSupport::Notifications for that, which looks pretty standard. Are you using that yourselves? How are you dealing with exceptions handling?
Our concern is that, if we have this code
sale = Sale.create!
ActiveSupport::Notifications.instrument("created.sale", id: sale.id)
PaymentsManager.charge!(sale) # will not run if any subscriber failed!
if any of the subscribers of the created.sale event raises an exception, then the sale will never be charged!
We’re thinking of building a thin layer on top of ActiveSupport::Notifications to individually rescue any exception that any subscriber might raise, but… is there a better way?
Hey, there’s nothing wrong with what you’re trying to do here, so by all means go to the bottom of it and I hope someone can help!
However, I just wanted to share our view on this, and that’s that if you need something to happen synchronously, then you should be calling the responsible pack/component directly via their public APIs.
If, instead, you’re using a pub/sub mechanism, you should probably try to make this as async as possible, meaning subscribers should not be running any code.
For example, assume when creating a sale, another pack/component needs to update a few records in the database.
Rather than doing that in the subscribe block (synchronously), that could enqueue a Sidekiq Job instead that will then be picked up.
This way, your subscribers would be pretty dumb and will hardly fail (still a good idea to put safety nets in place though!).
We created a wrapper around Hanami::Events to create the experience we were looking for:
• Uses Redis pub/sub capability (instead of Redis LIST), for multiple listeners in different stacks
• Can be configured (for unit tests) in synchronous in-memory mode (using their in-memory adapter)
• Can be configured (for production) in async mode pushing events to redis, and depending upon a separate worker process to listen for events & handle them
• Can be configured (for developers, review apps and staging) in asynchronous mode pushing events to redis, but without needing a separate worker; create a background thread in the current web application process (puma) to listen for & handle events.
We’ve done what Matt suggests above and it works well so far. Our rule for the subscribers is that they can only enqueue Sidekiq jobs based on the data in the payload, they aren’t allowed to perform any heavy lifting or touch the database at all.
thanks for your responses! That’s our idea, trusting our subscribers that they won’t raise and they won’t take more than a few ms to do their job, but… is there a way to enforce that? Because people make mistakes Gary’s solution sounds super interesting, but this early we’d like to stick with in-process notifications