You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: posts/2026-03-31-rails-apps-have-layers-but-no-modules.md
+12-35Lines changed: 12 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,11 +15,11 @@ You can have 200 models and zero modules. That's the problem with typical Rails
15
15
Order.first.user.invoices.last.line_items
16
16
```
17
17
18
-
Such code is not so uncommon. It crosses **4 business boundaries**. In just 1 line of code. All thanks to associations.
18
+
Such code is not so uncommon. It crosses **4 business boundaries**. In just 1 line of code. All thanks to associations.
19
19
20
20
## The problem with associations
21
21
22
-
One of the first thing we teach in Rails is associations.
22
+
One of the first thing we teach in Rails is associations.
23
23
24
24
```ruby
25
25
classOrder < ApplicationRecord
@@ -57,57 +57,34 @@ and this is how we allow the original code:
57
57
Order.first.user.invoices.last.line_items
58
58
```
59
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.
60
+
This is how we boil the frog. One step at a time. One column at a time. One association at a time. The result? A User class with **100 columns** in the database.
64
61
65
62
## DRY and god models
66
63
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.
64
+
There is a misconception about DRY - Don't Repeat Yourself. We have an existing User class. It feels right to just add things there. **No one was ever fired for adding a new column to the users table.** It feels like the User class is the right abstraction for DRY. Yet, it always ends as the god model.
72
65
73
66
## Service Objects don't help with modularisation
74
67
75
-
Many Rails teams believe that Service Objects are the solution. They are, but to a different problem.
68
+
Many Rails teams believe that Service Objects are the solution. They are, but to a different problem.
76
69
77
70
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
71
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.
72
+
What is good about them? They are creating a boundary between the HTTP layer (controllers) and the domain layer. They also are a good solution to the transaction boundary.
85
73
86
-
**Service objects do nothing about modularisation.**
74
+
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.
87
75
88
-
They don't create new boundaries. They don't help with composing modules.
89
-
90
-
Service objects are just **another horizontal slice**.
76
+
**Service objects do nothing about modularisation.** They don't create new boundaries. They don't help with composing modules. Service objects are just **another horizontal slice**.
91
77
92
78
## Microservices
93
79
94
-
It's usually around this phase in the architecture - MVCS - when a decision is made.
95
-
96
-
We will go microservices.
80
+
It's usually around this phase in the architecture - MVCS - when a decision is made. We will go microservices.
97
81
98
82
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. Microservices bring the hope of starting fresh — new language, new design, better boundaries. But the boundaries **still aren't modules**.
99
83
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.**
84
+
Are microservices helping with the modularisation? 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 of having 3 new Go microservices and adding new layers of serialisation/deserialisation. **More layers, less performance, but still no modules.**
104
85
105
86
## A bitter conclusion
106
87
107
-
Rails makes it easy to add code. It doesn't make it easy 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.
88
+
Rails makes it easy to add code. It doesn't make it easy to **isolate it**. 200 models. Five layers. Zero modules. That's the default.
112
89
113
-
What does your User class hide?
90
+
In 1972, Parnas wrote that a module hides a design decision from the rest of the system. Fifty years later, Rails apps hide nothing. What does your User class hide?
0 commit comments