Decode your own reviews"new("revert") is an anaemic model in disguise"
No narration yet
Module 8 · Lesson 513 min

"new("revert") is an anaemic model in disguise"

This is the anaemic domain model from Module 1, Lesson 4, caught in your own code, by Nick, on PR #272 (the revert-decision work). It's the origin of the D1 and D2 conventions, so this comment quite literally became the rule the team writes to.

Your design had the application layer query the database, distil the result into booleans (snapshotExists, hasActiveDecision), and pass those booleans into a ForRevertFlow(...) factory that built the aggregate with a literal "revert" identity. The conventions that came out of it, D1 and D2:

Walk through why this is the anaemic trap wearing a costume. The "aggregate" you built didn't hold its own state, it received a summary of state the caller had already computed. So it couldn't answer anything about itself; it could only validate the booleans it was handed. The behaviour and the state had been pulled out of the object and left in the application layer, exactly the "data bag plus a service that does the work" shape from Lesson 4. It looked like an aggregate (it had a factory, it had methods) but it was a stateless function in a trench coat.

The fix is the "load whole, then ask" instinct from the foundations lesson. Load the real aggregate from persistence, with the snapshot and decision state it needs, as itself. Then call aggregate.RevertDecision(revokedAt) and let it decide from its own loaded state. The application layer stops computing domain facts and goes back to pure orchestration: load, act, save.

Notice that on this one you did the rework and it became doctrine, your fix taught the team. That's the strong version of receiving review: not just complying, but internalising the principle so the next design starts right. The last lesson is Nisha and Nick on layering: where logic belongs, and why a retry ended up in the wrong place.

Practice

Try it yourself

Recall

Why the factory was anaemic

Reconstruct the smell from D1/D2.

You'd built the aggregate with a ForRevertFlow(...) factory that took pre-fetched booleans and used a literal "revert" identity. Why did Nick call that an anaemic domain model in disguise?

Quiz

What an aggregate id identifies

An aggregate root's identity should identify:

Recall

The fix, as an instinct

Turn D1 into a design-time reflex.

What's the correct shape, stated as a rule you can apply before writing the code?