When Rails-way does not work anymore?
I recently published an article on why I decided to explore an alternative way of writing Rails apps compared to the Rails-way, and what the Rails-way really is when we look at how people and 37signals write applications. In this article, I will discuss bold signs that it's time to let go of writing Rails apps the old way and start exploring alternative paths.
First of all, you can’t say that Rails-way works for some types of apps, like Fintech or E-commerce, and not for others from the beginning. This is the totally wrong angle to look at it. Rails-way is great for all types of apps up to some point.
High-level and organizational signs
You work on a Rails application, the business is growing, so is your application and the team. During the process, you may observe the following situations:
- Developers lose their confidence in introducing changes - it’s taking longer from passing PR to code review to the deployment, and the reason is not that your developers have gotten worse over time.
- The AI adoption among developers is not as rapid as you expected - the benefits are obvious. Still, the developers do not seem to be taking advantage of it, and the pace of introducing new changes has not increased much over the last few months.
- It’s hard to split developers into smaller teams - you would like to manage smaller teams focused on certain aspects of the app, but it’s hard to do the split logically, or teams are getting into code conflicts over and over again.
- Onboarding takes weeks, not days - you may have the documentation in place and other people to help, but even senior developers are not able to ship their first PR within the first few days.
- The business can’t get straight answers - you struggle with explaining to the business how the application and their own rules are working.
This won’t happen overnight; those signs will accumulate over time and will visibly decrease your ability to evolve applications as the business grows.
Low-level and technical signs
Technical debt will significantly affect the team's efficiency, regardless of team members' experience or the business direction. You may not incur the debt directly by going for shortcuts or agreeing to temporary solutions, but it will still grow indirectly. Here are the symptoms:
- The more features you ship, the more you deal with database performance issues - developers deal with multiple N+1 issues or attempt to refactor complex JOINs more often.
- The test suite becomes a tax - slow and flaky, consuming the CI minutes and making the whole cycle of delivery slower.
- Simple changes have an unknowable blast radius - you may change just one field in a model, but it affects so many other places that you are not able to predict the consequences and places where the test should be conducted.
- Business rules are duplicated across multiple places, making answering questions and making changes harder; refactoring rarely happens despite the feature having been tested.
Unfortunately, I have also recently realized that many developers do not consider those symptoms to be wrong; they treat them as an integral part of working on any Rails application.
Why Rails-way does not work anymore
The core idea of Rails was always to ship quickly and make coding a pleasure and a fun experience. You'll agree it does it better than any other solution in any technology currently out there. However, the speed and fun usually end when we meet real complexity: a business with many departments and tons of edge cases. At that moment, the double-edged sword can hurt us really badly.
A model good for everything is good for nothing
According to Rails-way, models are a first-class place for business logic. They serve as a DB table mapping, an object with business behavior, a validation container, and lifecycle events emitter via callbacks. Most likely, in many apps you will also find two God objects: User (or some variation of it) and the model that represents the business domain; for the events app, it will be Event, for the blogging app it will be Article, and so on.
They also often mix the entity state with the process state. A common and well-known example is using enums, for example, in Order - it can be paid, delivered, or aborted.
With the model conventions, you can ship very quickly; the interface is easily accessible, and it’s clear how to extend it. Going through multiple contexts is an advantage, but it becomes a curse as complexity grows. You are not only dealing with performance issues but also with a loss of confidence in introducing changes. One change in the model’s method can affect multiple places in the app that you are not even aware of.
When standard conventions are not enough, you invent your own
As soon as Rails conventions do not cover your needs, you start inventing your own conventions. This is the reason why we have endless discussions about the shape of the service object. Rails Guides do not share any mental model for dealing with the real complexity. Concerns seem to help a little: they help organize models and eliminate duplication, but in many cases, they contribute to even tighter coupling between models that handle completely different business departments or processes.
This is why I called Rails-way the Rails in the wild: it’s just people trying to handle complexity on their own, based on their experiences and ideas, because they have outgrown the widely promoted approach to building Rails apps. Working for years on big legacy applications, I saw that many times.
The disconnection from the business core
The marketing around Rails feels disconnected from the business, building its own abstraction. No wonder the naming is hard if you are so disconnected from the business domain where the business is primarily operating. Yes, Rails is optimized for happiness and helps to accelerate with AI agents thanks to conversation over configuration. No, we are not just CRUD monkeys hired to turn ideas into working concepts quickly.
To build complex and enterprise applications, it’s crucial to truly understand the business, build software to solve its problems, map the code to real-world processes, and share the common understanding between all stakeholders; these might sound like big words, but once you hit complexity, this is the mental model for being a software engineer you need to make it right. Rails-way does not help with that.
Last but not least
Rails-way works very well for thousands of apps and may serve your application as well. Although what I have seen through the last 16 years and keep seeing up to this day, when the complexity is growing, at some point you have to make a decision: you either try to grow your own version of Rails-way or switch to a domain-driven version of Rails that follows a slightly different approach and provides battle-tested patterns for handling complexity. It’s worth noting that following any of these paths won’t make you abandon Rails-way completely; this is not the point of inventing something completely new.
The latter is the approach I’m currently exploring and moving towards, both in mindset and in how I write Rails applications. In the next article, I will demonstrate an alternative approach to the Rails way that is designed to handle complexity without reducing the team’s confidence in making changes.