5 architecture anti-patterns and solutions for large Rails apps

As your Ruby/Rails codebase grows, it’s important to take note of patterns that are or are not working. As we move into 2014 here at Reverb, with a year of experience in building and maintaining a large marketplace at breakneck velocity, here are our learnings.

These tips assume you are growing a sizeable Rails codebase with a domain layer living outside of Rails. If you need help getting started on that, I highly recommend these resources for building domain driven applications.

Service objects with many responsibilities

Avoid naming your PORO classes NounService such as UserService, ProductService, etc. These invite large growing classes with many responsibilities, instead use Use Case classes that do only one thing. For example, Reverb::Accounts::ForgotPassword, or Reverb::Orders::FinalizeCheckout. On a related note, find methods that are used in only one use case and move them there. Don’t bloat your ActiveRecord models with methods that are specific to a Use Case, instead create POROs to break Use Cases into simpler parts.

Un-namespaced classes in the domain layer

Our domain layer, living in app/reverb now has more than 236 classes. It would be a nightmare if they were all in one flat directory, yet that’s how they started. These days, we group things by responsibilities such as app/reverb/checkout and app/reverb/offers. As with service class naming, we try to keep namespaces based on domain concepts such as checkout rather than the Railsy/Nouny approach of naming things after models.

Including modules to share functionality

We believe that sharing code via mixins is largely a smell. It definitely has its uses, but it should not be reached for as an everyday pattern. The reason is it makes it very difficult to find where your methods are coming from and to cleanly read a class definition and see its public API. Mixins with public methods increase surface area of your objects, leading to further coupling in the users of your object. Mixins may be appropriate in cases where they are the only way to extend an object, such as is the case often times with ActiveRecord. Instead of mixins, delegate to collaborator objects explicitly.

Any kind of dynamic method invocation

Sends, especially with interpolated method names are impossible to grep for and refactor. Think thrice before doing this. Likewise, we are proud to say that in approximately 25k lines of application code, we have zero usages of method_missing. In my past experience, using this technique only invites headache for maintenance, and is only appropriate when crafting very tightly specified DSLs, that are usually best left to external gems.

Learn how we enforce this in our codebase with a failing spec.

Rails Helpers

Do Not Use. Rails helpers create globally accessible methods. There are valid uses but they should be limited to customizing the framework itself. A good example is something like button_to which enhances link_to with specific button css classes. A bad example would be anything domain specific like link_to_user. Instead move methods into decorators. We use Draper, but any type of delegator may be used to implement decorators.

Do you agree with these approaches? We’d love to hear from you. Also, we’re hiring. If you’re a software developer with a passion for product ownership, small teams, lean startups, and are a musician/gearhead, Reverb.com is literally your dream job. Find out more on our jobs page.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s