Prologue

In 2010, I started working with Ruby on Rails. Coming from the PHP environment, I was immediately amazed by the elegance of Ruby and how fast you can build working things with Rails. The framework took the burden of configuration off our shoulders, making it super easy and pleasant to save, validate, and pull data from the database.

16 years later, nothing really has changed. Ruby and Rails still evoke the same excitement about building a brand-new project. With the recent rise of AI, the time required to implement initial features has decreased dramatically.

The question is not whether Rails helps to build applications really fast. It helps, no doubt about it. The question is how to work with Rails as the business and team grow, with more and more features shipped every week and complexity rising.

Does Rails-way help the same way it did when you were taking off? The more experienced you are, the more important the answer is because you stop working with greenfield applications and start looking into complex legacy Rails-based systems. And often it hurts.

We were never told how to deal with complexity in a Rails app

Convention over configuration; this is the motto of Rails. Equipped with reasonable defaults, you can accelerate your app's development without worrying about the skeleton or initial choices. This is where Rails guides shine; they let you take off with elegance and a breath of excitement.

Chances are, if the project gets traction, the user base grows, and so does the codebase: more models, more code within the models, and more columns in the single database table. Queries become more complex; you hunt for N+1S and look for ways to speed up the test suite.

Cognitive complexity in Rails app

But Rails guides won’t show you how to deal with complexity. And if you continue working the same way you did when you were dealing with a few models, shipping will soon turn into hesitation, and hesitation will turn into avoidance.

Eventually, you will realize that velocity hasn't dropped because the team got worse; it has dropped because the cost of being wrong has become too high.

We also told ourselves that business is not something that software engineers deal with and understand

You probably know the telephone game, right? In most cases, the sentence at the beginning is totally different when the last person hears it. These days, in many cases, working on a larger Rails application reminds one of the telephone game, where each handoff is a lossy compression step.

Consider the following scenario:

The ticket is marked as completed, as we can tell when the account was canceled. After a few months, the marketing comes and asks: "How many users churned last month?". "What does it mean churned?" - the first thing that comes to the developer's mind. I believe you know the next parts of the story.

People use different vocabularies at different levels. The naming conventions in the code are different stories.

Abstraction is useful until it becomes insulation

A product owner is a useful abstraction of the customer when the developer needs to focus on shipping. It becomes insulation the moment the developer stops asking why, stops noticing when the ticket doesn’t quite make sense.

Quite often, the abstraction in the form of various Rails design patterns has eaten the underlying reality. Developers treat user.charge(amount) as a black box. Jira tickets become the source of truth.

Still, we do need abstractions. We need them, but in Rails applications, they too often become prosthetics rather than just tools.

Software and communication layers influence each other all the time

It’s a double-edged sword. It can increase clarity or complexity. Quoting the classic:

There are only two hard things in Computer Science: cache invalidation and naming things.

Phil Karlton

In the majority of Rails applications, it increases complexity, which is why developers often debate 100 different variations for naming and building service objects rather than focusing on business logic and a ubiquitous language. What’s worse, the variations come and go and usually don’t remain the same throughout the project lifecycle.

Rotation in the project happens, and most Rails developers have never worked with other languages before, so they don’t have experience dealing with complexity. They are trying their best, but the Rails guides dropped them off when it became difficult.

The golden cage of Rails-way

But how can you question the current reality if you don't know another? That was my first thought when I recently listened to a discussion about why it’s so hard for Rails developers to follow Domain-Driven Design.

Database tables with tens of columns, fat models, slow tests, developers treating Jira tickets as the source of truth, model callbacks, N+1 queries, multiple abstractions in the form of various design patterns - this is the reality for many bigger Rails applications. The bigger they get, the more painful it is to deal with that.

But we can outgrow things. We usually do. The Rails-way approach definitely helps us quickly get off the ground and accelerate development in the early stages, but as the app grows, it largely contributes to the application's complexity and moves us away from mapping code behavior to real-world business processes.

Farewell to Rails-way

Fortunately, I was lucky enough to work on multiple large Rails projects over the years, learned lots of hard lessons, and lost the battle with complexity enough times to start looking for a different perspective, a reality where growing a business does not reduce the developer's confidence in the changes being introduced.

As a result of this search, I decided to join Arkency to learn completely different ways of handling complexity in large Rails applications. It’s been a short moment, but I’m already amazed by how Domain Driven Design and an approach based on events change the rules of the game for the better.

The window in which I still remember how to follow Rails-way and learn to work with DDD is shrinking fast; I will soon lose the unique perspective I have now. This journal is an attempt to document the journey and help others go through the same way, just faster and less painfully.

I believe that if you are a Ruby engineer and you want to grow, the only way to do that is by working on more complex projects, and nothing better prepares you for that than truly understanding what you create and why. This is where the Rails-way usually no longer works. I’m about to learn how to change that. You can too.