Engine-iring Rails apps, by Vladimir Dementyev

Abstract

Rails applications tend to grow and turn into large monoliths–that's a natural evolution of a Rails app, isn't it?

What happens next is you starting looking for an architectural solution to keep the codebase maintainable. Microservices? If you brave enough...

Rails ecosystem already has a right tool for the job: engines. With the help of engines you can split your application into independent parts combined under the same root application–the same way rails gem combines all its parts, which are engines too, by the way.

Curios how to do that? Come to hear how we've engine-ified our Rails monolith and what difficulties we faced along the way.

Details

In the introduction I'd like to tell about the motivation behind the engined architecture:

  • what was wrong with the monolith (in our case)
  • why did we choose this approach (spoiler: we're not that big to handle microservices, and we were kind of inspired by the Shopify's components approach).

Then, a general overview of the architecture (how we split the app into engines, what layers do we have, how do we share the code (spoiler: we use local gems) and how it helps us to build new features (=why we're very happy about it and want to share our happiness with everyone).

The main part of the talk is starting with a quick explanation of what Engine is, including the examples from other gems (e.g., the most popular one–devise) and Rails itself. A note about isolation and isolated engines.

"Now, when we learned the basics, let's try to refactor our application using engines."

NOTE: "refactoring" is the key word here; we didn't build the app from scratch with engines, we "extracted" engines from the existing codebase.

There was a bunch of difficulties we had to deal with, and I want to talk about the solutions we found:

  • Dealing with models: extensions vs. dependency injection?
  • Migrations and DB tables: what if already have a database with millions of records, how to "connect" engine to it? How to make tables prefixes configurable within the engine?
  • Testing caveats: how to share factories and other test helpers? Factory names uniqueness; aliases
  • CI caveats (or "To run or not to run")
  • Decoupling engines, or how we introduced event sourcing the Rails way
  • Dealing with multiple (in-engine) frontend (Webpacker) apps
  • GraphQL API "stitching"
  • Cables vs. engine, or "Gimme more cables!"
  • ...and more.

Final thoughts (including my favorites "You can always extract engine into a separate app to have microservices architecture. Or you can't?" and "When it's too early to use engines").

To summarize, this talk:

  • Explains how Rails Engines work
  • Demonstrates how you can use some Rails parts (e.g., Webpacker, ActionCable) and other gems within engines
  • Shows how to build component-based Rails applications.

This talk is a missing guide to building engined Rails apps. It's more suitable for mid-, upper-level developers (=those taking architectural decisions).

Pitch

The talk is based on the personal experience of using engines as building blocks for the large Rails application.

It turned out that Rails Engines are not so easy to use out-of-the-box: many popular gems (including Rails frameworks) are not engine-ready, gluing engines together is something we should take care by ourselves.

While working on this project we've built many tools and pushed a bunch of patches to the existing projects, now using engines is much easier than the day we started applying this technique.

Along the way, we also cover some hidden gems of Rails (ActiveSupport hooks and notifications, custom generators) and other useful tools (e.g., event sourcing with RailsEventStore).

I want to show that Engines are ready to rock, and it's a great alternative to so popular microservices approach.

Edit proposal

Submissions

RailsConf 2019 - Rejected [Edit]

Add submission