Component Based Architecture
A component contains reusable business functionality that can be referenced and used by other components. Components can associate with one another by setting an explicit dependency. The collection of components and their dependencies can be diagrammed in a directed acyclic graph, a graph with no cycles. Meaning, no circular dependencies. For those of you experienced with React JS, you are already familiar with component based architecture.
Modular Approach in Rails
Applying this pattern in Rails can be done with mountable engines. Your main application, often referred to as your host, will contain a folder we call engines. You can name the folder what ever you want.
components is another good name. I prefer
engines as we typically also generate a
gems folder to differentiate between the two but unimportant for the scope of this post.
Nested within the engines folder will live these mountable engines we call components. It is made available to the host application just like any other gem, via the gemfile.
Creating an Engine
Considering a real world example for an app that needs to support generating reports: Lets create a
First step is to create the mountable engine. We do so by using the rails plugin new generator. The generator accepts a variety of options that you may want to use. See the full list by running
rails plugin --help.
root $ rails plugin new engines/reports --mountable -d postgresql
The generator should have created a mini application in your engines/reports folder. It also should have modified your host application’s gemfile by appending:
gem 'reports', path: 'engines/reports'
I usually remove that line and add the engines dynamically like so:
Dir.glob(File.expand_path('../engines/*', __FILE__)).each do |path| gem File.basename(path), path: path end
Go ahead and try to bundle from your host.
root $ bundle install You have one or more invalid gemspecs that need to be fixed. The gemspec is not valid. Please fix this gemspec. The validation error was '"FIXME" or "TODO" is not a description'
Even though we are creating a local gem we still need to complete the TODO’s in the gemspec. Modify host/engines/reports/reports.gemspec and ensure you can bundle from your host directory.
Create Our First Model
Now that we have an engine, we can go ahead create a model. If you recall, we used the
--mountable option when creating the engine. That isolated the engine and name-spaced everything with
Reports, including the database tables.
Navigate to the reports directory and create a
root $ cd ./engines/reports root/engines/reports $ rails g model Report name data:text
Our new migration file exists within our reports engine db/migrate folder. Think of this migration file as the source of truth. The migration files are managed within the engine although they will also need to exist in the host application. We must copy our migration over to the host application. This is done by the railties install migration command.
root $ rails railties:install:migrations Copied migration 20190811131059_create_reports_reports.reports.rb from reports
You should notice a few things:
- The report table is name-spaced by reports. Table name is
- The original
CreateReportsReportsmigration file exists in the reports engine
- A copy of
CreateReportsReportslives in the host application
- The original and copied migration file versions (time-stamps) are different
It is safe to rerun the railties install migration command. It will only copy over new migrations.
From you host, migrate the database.
root $ rails db:migrate
It is time to add some business logic to support generating a report. Add a file named
# root/engines/reports/lib/reports/generator.rb module Reports class Generator def self.run(name:) # Some meaningful code here Report.create(name: name) end end end
And modify our
reports.rb file to require the
require 'reports/engine' require 'reports/generator' ...
Now, you can generate reports
Reports::Generator.run(name: 'Sample Report')
This post was written to introduce Component Based Architecture with Rails and walk you through creating your first engine. I encourage you to read more to learn about the benefits and drawbacks. Understanding both is important before adapting any architecture and using it in production applications.
I have used this technique with great success and believe many code bases would benefit from this modular monolith.
If you are interested in learning more about applying Component Based Architecture to your Rails application then you might find some of the following links useful.
- Rails Guide - Getting Started with Engines
- Book - Component-Based Rails by Stephan Hagemann
- Blog Post - The Modular Monolith: Rails Architecture by Dan Manges
- Talk - Get started with Component-based Rails applications! by Stephan Hagemann, 2015
- Sample Application - Rails engines example by Task Rabbit
- How to add Rspec to a rails engine
Found this useful? Know how it can be improved? Get in touch and share your thoughts at