Using events to decouple Rails applications

During the last years Rails developers have been looking for ways to move from monolithic to component-based applications. One of these is the use of Rails Engines. The basic idea is to move away from the default MVC Rails architecture where “fat Models” end up being morbid obese.

If you are interested to know more about Rails Engines I highly recommend you to watch these two talks by Stephan Hagemann at Mountain West RubyConf 2013 and RailsConf 2014.

In a few words we’re talking about code that doesn’t pertain to your application core business logic but lives around it. Since we recognize that they’re not part of the core we can extract them into Rails Engines.

A typical example can be to extract email notifications functionality from the core of the application and move it to an Engine.

Decoupling engines

The most exciting thing, of course, is not just moving code around. It allows you to create APIs that developers can rely upon to build functionality. By encapsulating a feature to its own engine we can debug better since we know where all the code related to certain functionality is.

Using observers

One way to react to the changes of our models in core is to use good old observers. Before we had ActiveModel::Observer for that! And we still have it of course. The only issue now is that our observer class name is something like EmailNotificator::UserObserver and Rails cannot do the magic of inferring the Model from the class name. That’s when the convenient observe method comes to help. Look at the code to better understand how it works.

In this Observer we’re subscribing to core User model changes. If we need to observe engine specific Models we can do the following instead:

Custom events

When our engine is waiting for a custom event to happen outside the engine itself, for instance waiting for an external service webhook, we need a way to subscribe to it. We can apply the pub/sub pattern using ActiveSupport::Notification. The idea is to trigger an ActiveSupport::Notification event from the core (or another engine) and then let our engine listen to it:

In the case of webhooks we’ll probably have a Controller inside an Engine that manages the incoming webhook. We can trigger the event inside the Controller’s action:

Then inside our engine code:

Now every time a webhook.received event is triggered our engine will execute the code in the block.

Conclusions

Thinking about components that communicate through events can help during the process of breaking monolithic apps into smaller components. It will make your code cleaner and easier to debug (git grep friendly). Give it a try!

 

enricostn

 

Leave a Reply

Your email address will not be published. Required fields are marked *