Rails Parts

1st of October, 2018
rails

When we restarted developing an application from scratch, I stumbled upon a great way to split the files in your Rails app into different parts.

Our Pain

For over 4 years, we have been building a Rails app for helping people finding work. Recently, we decided to restructure our application and stumbled upon a great way to split the files in your Rails app into different parts.

Just a few months ago, we started making new concepts for the core component of our application. We sat back and thought if this could finally be the opportunity we have been waiting for – to start a new architecture. We had learned so much about our business and what our customers needed. Our team had matured and developers were more experienced. And we were eager to employ the concepts we made up to reduce technical debt further.

Break it up

We wanted to start the redesign on the right foot. We decided to focus on simplicity, encapsulation and re-usability. So I started a quest to find a nice way to break our app into nice little components. The obvious candidates were:

Microservices Amazon does it, so we should do too, right? Well, except the app we are building is not Amazon. In the past, whenever I tried to make small individual services, the deployment and testing quickly became an issue. Also, getting interfaces right and evolving them is hard. You may end up in dependency hell.

For us, microservices were not an option. So, we decided to stick with a monolith. But – still encapsulated and nicely split up, so multiple teams can work on it.

Engines In our existing app we had a few engines to separate components. But we were frustrated with them: They come with some boilerplate code. It is not obvious how to make tests and migrations work as if they were part of the same app. And yes, you can make it all work, but we did not find a satisfying way for us.

Others There are a lots more options to segregate your logic like putting stuff in gems or creating app/services to provide re-usable components. I continued my search…

The Parts Concept

To this day, I still don’t know how I found it, but there it was:

# documentation from: https://api.rubyonrails.org/classes/Rails/Engine.html
class MyEngine < Rails::Engine
  # ...
  paths["app/controllers"]     # => ["app/controllers"]
  paths["app/models"]          # => ["app/models"]
  paths["app/views"]           # => ["app/views"]
  # ...
end

A way to split your app in components. I was excited.

I created the following folder structure1, moved a bunch of files around2, added two test controllers and removed the app folder (!):

├── bin
├── config
├── ...
└── parts
    ├── banana
    │   ├── controllers
    │   ├── models
    │   └── views
    └── cheese
        ├── controllers
        ├── models
        └── views

Now the only thing left to do, was to configure Rails so it knows where to find everything. To configure the new layout, I changed the application.rb to:

module TestProject
  class Application < Rails::Application
    # ...
    paths['app/controllers'] = ['parts/banana/controllers', 'parts/cheese/controllers']
    paths['app/models']      = ['parts/banana/models'     , 'parts/cheese/models']
    paths['app/views']       = ['parts/banana/views'      , 'parts/cheese/views']
    # ...
  end
end

After a quick server restart, it had worked! The controllers in banana responded as well as the one in cheese. All models worked and the views were found. There was no funny business such as custom require magic or monkey patching. No. A fully supported, standard way to change and add new sub-folders in Rails. I asked myself: “Why had I never seen this before?” Turns out my colleagues would asking the same question soon.

Encouraged by my initial success, I wanted to know if this would also work for migrations and tests. Yes it does. You can put locales, specs (see spec_helper.rb & .rspec), migrations and assets into individual parts – even routes and seeds (using a simple SeedManager). After a few hours the project’s file structure looked like this1:

└── parts
    ├── core
    │   ├── assets
    │   ├── controllers
    │   ├── helpers
    │   ├── jobs
    │   ├── locales
    │   ├── mailers
    │   ├── migrate
    │   ├── models
    │   ├── policies
    │   ├── services
    │   ├── spec
    │   ├── views
    │   ├── routes.rb
    │   └── seeds.rb
    └── banana
        ├── controllers
        ├── migrate
        ├── models
        ├── policies
        ├── services
        ├── spec
        ├── views
        └── routes.rb
Example: Core may be used by everyone
Example: Core may be used by everyone

The core would keep all the base classes like ApplicationController and ApplicationRecord. Later, we decided that the core part contains common classes needed by other parts (e.g. Users). We made up the rule that no part may use another part, with the exception that all parts may use the core-part. This way, we should be able to change one part without breaking another3.

After we started developing we had to do our homework: How do we slice our application into different parts? It quickly became clear, that we needed to draw the lines around business areas & responsibilities. We were tempted to do parts like backend or services. While backend would have ended up being a huge pile of everything, services is a natural folder in each part.

Assessment

We are working with parts for a few months now. It is a simple way to divide the contents of your app up. It is:

There are a some things to mind though:

All in all, the parts concept works great. I am sure we will learn much more about the pros and cons in upcoming sprints. Please feel free to check this repository out and drop us a email if you have questions.

Have fun Tom

P.S.: If you want to work with parts first hand, we are looking for new members on our dev team. Send me an email.

  1. I am deliberately leaving out a lot of files and folders to maintain a better overview for the reader. For the nitty-gritty details, please see this demo repository. 2

  2. I had to move the ApplicationController, ApplicationRecord, ApplicationHelper, assets, and the main layout.

  3. If there is interdependencies between two parts, we either go via a clear interface in core or define a facade.