Maintainable and Scalable Systems with Rails Engines
We recently realized that this site (the one you’re reading right now) was getting a little too large to remain sustainable. We noticed that large parts of the app provided completely separate fuctionality and didn’t really need to communicate with each other. Maintaining these separate parts as a single monolithic application was starting to become painful so we decided to break it up using Rails Engines.
- Gaslight.co (static pages, newsletter signups, contact forms)
- BlogApp engine (posts, tags, comments)
- TrainingApp engine (courses, chapters, instructors, registrations)
A Rails Engine is a stand-alone Rails app that can be mounted by another Rails app. This is acheived largely through namespacing. Controllers and models in a Rails Engine are defined within modules of the engine’s namespace. Simlarly, assets specific to the engine are accessed in namespaced directories.
Rails provides a generator for creating engines just like it does for creating a standard Rails app. Rather than
rails new myapp, run
rails plugin new myapp. This will create an application skeleton similar to a standard Rails app that implements the namespacing described above.
Is an engine right for me?
Engines aren’t the answer for everything, and building a rails app with engines will increase development time. The benefits are that you can maintain pieces separately. Each engine can be run in isolation having it’s own layout, assets, and test suite. Furthermore, architecting an app with engines forces you to really think through your dependencies and makes you isolate things from the get go.
Gems. Engines define dependancies with a .gemspec, not a Gemfile, meaning your gems need to be available on a gem server like RubyGems and can’t be loaded directly from GitHub. You can always vendor a gem locally, but thats kind of a hassle. You also need to be careful to use similar versions of dependancies between engines. If you’re using haml 3 in one engine, use that version everywhere.
Shared assets. Sharing assets and partials between engines is hard. I’ve tried to various solutions, but ended up stubbing out the dependent assets within each dummy app. For instance, the newsletter signup form in the sidebar (over there →) gets pulled in from the main application as
shared/_newsletter.erb. In the dummy app of our blog engine, we have a blank template under
shared/_newletter.erbso it doesn’t raise a missing partial error when we run the blog in isolation.
Migrations. Scoped migrations are great and make it clear which tables go with which engine. Running
rake blog_app:install:migrationsis lame, and can lead to some nasty bugs when migrations get out of sync. Before you start with engines, read Pivotals Post and keep your migrations separate.
Reusable pieces. Now that our blog and training apps are engines, it’s easy to pull them out and move them around. Because engines are just gems, they can be run inside any rails app. Running a additional instance of our blog is as easy as putting a line in a Gemfile.
Separate Tests. As apps grow, test suites can become unwieldy and take longer to run. With engines, test suites are broken apart and run faster. Because engines are isolated, you can be assured that changes to an engine wont affect other parts of your app.
Quick development. It’s easier for developers understand a single engine rather than needing to understand the entire app. Rampup time is shorter.
If you feel the pain of dealing with a bloated architecture, chances are you’ll have places where engines make sense. If you’re comfortable with Rails, Engines are just a short mental leap. It may be somewhat painful to break out your first engine, but understanding engines means In the end, you’ll have a much more maintainable and scalable systems.