Skip to content

Commit bfaa01d

Browse files
Draft: Rails apps have layers but no modules
1 parent 5ea69a8 commit bfaa01d

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
created_at: 2026-03-31 12:00:00 +0100
3+
author: Andrzej Krzywda
4+
tags: ['rails', 'ddd', 'architecture', 'bounded contexts']
5+
publish: false
6+
---
7+
8+
# Rails apps have layers but no modules
9+
10+
You can have 200 models and zero modules. That's the problem with typical Rails conventions. Rails supports layers - models, views, controllers. But layers are not modules. Within one layer - especially models - usually all is mixed together. There are no boundaries.
11+
12+
<!-- more -->
13+
14+
```ruby
15+
Order.first.user.invoices.last.line_items
16+
```
17+
18+
Such code is not so uncommon. It crosses 4 business boundaries. In just 1 line of code. All thanks to associations.
19+
20+
## The problem with associations
21+
22+
One of the first thing we teach in Rails is associations.
23+
24+
```ruby
25+
class Order < ApplicationRecord
26+
belongs_to :user
27+
end
28+
```
29+
30+
It's very readable, feels right. Allows us to call it like this:
31+
32+
```ruby
33+
Order.first.user
34+
```
35+
36+
Then we have the User class:
37+
38+
```ruby
39+
class User < ApplicationRecord
40+
has_many :orders
41+
has_many :invoices
42+
end
43+
```
44+
45+
and the Invoice class:
46+
47+
```ruby
48+
class Invoice < ApplicationRecord
49+
belongs_to :order
50+
has_many :line_items
51+
end
52+
```
53+
54+
and this is how we allow the original code:
55+
56+
```ruby
57+
Order.first.user.invoices.last.line_items
58+
```
59+
60+
This is how we boil the frog. One step at a time. One column at a time. One association at a time.
61+
62+
The result?
63+
A User class with 100 columns in the database.
64+
65+
## DRY and god models
66+
67+
There is a misconception about DRY - Don't Repeat Yourself. We have an existing User class. It feels right to just add things there.
68+
69+
No one was ever fired for adding a new column to the users table.
70+
71+
It feels like the User class is the right abstraction for DRY. Yet, it always ends as the god model.
72+
73+
## Service Objects don't help with modularisation
74+
75+
Many Rails teams believe that Service Objects are the solution. They are, but to a different problem.
76+
77+
Service objects help us when our controllers become too big. They are called from the controllers and they are the ones orchestrating ActiveRecord models. Often they handle transactions too.
78+
79+
What is good about them?
80+
81+
They are creating a boundary between the HTTP layer (controllers) and the domain layer.
82+
They also are a good solution to the transaction boundary.
83+
84+
Service objects are a new layer. We could now call it MVCS. Model View Controller Service. It's not bad. It does help with unit testing - it's easier to unit test a service object than a controller action.
85+
86+
Service objects do nothing about modularisation.
87+
88+
They don't create new boundaries. They don't help with composing modules.
89+
90+
Service objects are just another horizontal slice.
91+
92+
## Microservices
93+
94+
It's usually around this phase in the architecture - MVCS - when a decision is made.
95+
96+
We will go microservices.
97+
98+
Sometimes it comes from the team itself - what can be a stronger boundary than a network? The team hopes it will enforce a better design. Sometimes the decision comes from the new CTO, who was just brought in from another world, possibly Kotlin, Go, or TypeScript. Microservices bring them the hope to create new code in their favourite and familiar language.
99+
100+
Are microservices helping with the modularisation?
101+
Nope. They are just yet another horizontal layer. This time we add a layer behind a network call. We no longer have transactions, it's harder to run tests, the build takes longer. All for the benefit for having 3 new Go microservices and adding new layers of serialisation/deserialisation.
102+
103+
More layers, less performance, but still no modules.
104+
105+
## A bitter conclusion
106+
107+
Rails makes it easy to add code. It makes it impossible to isolate it.
108+
109+
200 models. Five layers. Zero modules. That's the default.
110+
111+
In 1972, Parnas wrote that a module hides a design decision from the rest of the system. Fifty years later, Rails apps hide nothing.
112+
113+
What does your User class hide?

0 commit comments

Comments
 (0)