This article explains the motives behind building ruby EventRouter gem. It starts with an example to demonstrate the problem. Then presents how EventRouter can help solve the problem in a clean easy way.
Imagine we are building GitHub Pull Request (PR) page where you can update the PR description, title and state. We are also going to keep track of all of these updates on the PR activity timeline, and make sure we notify the reviewers of any change. For demonstration purposes, the update action might look like:
We first persist the user values into the database. And that is essentially what this action is responsible for. Then, we are performing multiple actions one after another. However, the update action itself should not be responsible for the side effects of a pull request being updated.
Lets inspect what those actions are doing. They are acting upon the fact that a pull request got updated. Each has a different responsibility and work to do. Which means they should be isolated in terms of execution. If one failed, the rest should not. We also need to retry each action independently and take the necessary steps on failure.
One way to refactor this, is to emit a single domain event PullRequestUpdated and we create one consumer per each use case. Lets do a bit of refactoring…
PullRequestUpdated event class can be defined as:
We moved the logic to another class and that approach has couple of benefits:
The new class has only one responsibility, which is to deliver the event to specific set of handlers.
Our controller now is smaller and easier to test.
Easy to test the PullRequestUpdated class independently.
The event logic is now reusable, so if we want to emit the same event when a commit is submitted, well you know what to call.
The controller still needs to wait for the event to be delivered to its destinations, right? But for that specific case, we want to return the response back once the update is finished. So lets create a new publish_async method and use it in the controller instead. We can use a background processing library like Sidekiq but to keep it simple lets use Ruby Thread:
Since the event is processed in background, the previous approach avoids having multiple points of failure while processing the update action.
EventRouter helps you build a similar flow. It supports multiple delivery adapters out of the box and it is also easy to extend and add new delivery backends, e.g RabbitMQ. The controller stays the same and the event can be changed to:
Publish events in sync or async way.
Deliver events to their destinations using sync or async adapters.
Build events with arbitrary arguments.
Using Oj serializer you can maintain the arguments structure.
Enrich events with extra payload before they are delivered to their destinations.
More to come so make sure to check the repo Wiki for more features and details.
Constructive feedback is always welcome. Hope it helps! 👋