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 compositionrecording.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 rightSharp parts leans right
Team size & turnoversmall, stable, cohesivelarge, many teams, high turnover
Experience spreaduniformly seniormixed
Codebase ageyoung-ish, one mental modellong-lived, many hands
Enforcement availablecode review & culture sufficeneeds mechanism (constraints, CI cops)
Cost that dominatesboilerplate of extra layersdebugging 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.

Further reading