There’s a real, productive disagreement about how much structure a growing application needs. On one side, the “vanilla Rails is plenty” position (37signals / Jorge Manrubia): trust rich domain models and disciplined callbacks. On the other, the “sharp parts” position (Brandon Weaver): once you can’t trust the whole team to read the docs, push rules into mechanisms — database constraints, a single write door, value objects at boundaries. Both are right inside their assumptions. The interesting work is locating those assumptions.
The case for vanilla Rails
Manrubia’s argument isn’t “no abstractions” — it’s don’t reach for an extra layer before the object model has earned it. His strongest cards come from DDD itself, and they resonate with me:
Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping towards procedural programming. — Eric Evans, Domain-Driven Design
Don’t lean too heavily toward modeling a domain concept as a Service. Do so only if the circumstances fit… Using Services overzealously will usually result in the negative consequences of creating an Anemic Domain Model, where all the domain logic resides in Services rather than mostly spread across Entities and Value Objects. — Vaughn Vernon, Implementing DDD
The worry is concrete: reflexively extracting every operation into a service or
command object hollows out the entities until they’re just
data bags, and the service layer becomes the procedural dumping
ground DDD warns about. Manrubia’s tactics for keeping models rich without making
them monoliths are worth stealing regardless of where you land: concerns to
organise related behaviour, and composition — recording.incinerate reads as
an English-like API on the model but delegates the real work to a
Recording::Incineration collaborator. The model stays a facade, not a god
object. He also frames Rails’ implicit features (callbacks, [[globals-and-ambient-context|Current globals]])
as sharp knives: dangerous when abused, legitimate for secondary, orthogonal
concerns when wielded knowingly. Software, in his words, is a game of trade-offs,
not a search for the one pure architecture.
The case for the sharp parts
Weaver doesn’t dispute that disciplined callbacks can work — he disputes that discipline scales:
Enforcement that relies on developers reading docs is soft at best.
His objection is organisational, not aesthetic. At fifteen years, hundreds of engineers, dozens of teams of varying experience, “guidance on a page doesn’t hold.” A convention has a published bypass list; an invariant in the database doesn’t. So past some scale you replace culture with mechanism: constraints for hard rules, a single write door for behaviour, value objects so live records can’t leak, and lint/privacy rules that fail CI rather than a wiki page that asks nicely.
It’s a scale-and-team axis, not a right answer
The two positions barely contradict once you add the missing variable: who is going to write the next bypass, and can you trust them to have read the rule?
| Vanilla Rails leans right | Sharp parts leans right | |
|---|---|---|
| Team size & turnover | small, stable, cohesive | large, many teams, high turnover |
| Experience spread | uniformly senior | mixed |
| Codebase age | young-ish, one mental model | long-lived, many hands |
| Enforcement available | code review & culture suffice | needs mechanism (constraints, CI cops) |
| Cost that dominates | boilerplate of extra layers | debugging invisible cross-cutting writes |
Same engineering judgement, different inputs. A two-person product trying to find fit pays a real tax for command objects and structs it doesn’t need yet; a 500-engineer platform pays a worse tax for “everyone please remember to call the normaliser.”
My read
I hold both, and I don’t think that’s a contradiction. The DDD warnings are the
ones I’ve felt — I’ve seen anemic models where the entities were getter bags and
every rule lived in a FooService, and it was genuinely worse than rich objects
would have been. So I take Manrubia’s “don’t give up on the object too easily” as
the default posture. But I read Weaver as answering a different question: not
“where should this behaviour live?” but “what happens when someone bypasses
wherever it lives?” — and his answer (hard invariants belong in the data layer,
not in any object’s good intentions) is one the rich-model camp doesn’t really
contradict. My synthesis: rich entities and value objects for behaviour;
database constraints for the invariants that must never break; explicit objects
only where an operation genuinely spans aggregates or needs a guaranteed single
door — and let team scale, not fashion, decide how much of the last one you buy.
Related Concepts
- Anemic Domain Model — the failure mode the vanilla camp guards against
- Domain Services — legitimate uses vs. the procedural dumping ground
- Domain Model Purity
- Constraints over Conventions — the sharp-parts core claim
- Read Models and Boundaries — single ingress, value objects at the edge