Entities versus value objects
The most important classification in DDD is also the simplest, once you have the test. Every concept in your model is either an entity or a value object, and the thing that decides it is identity.
Ask one question: if two of these had identical attributes, would they be the same thing, or two different things I need to tell apart?
- Two amounts of £50 are just £50. Interchangeable. There's nothing to "track." That's a value object: defined entirely by its values.
- Two people both named Alex Taylor, same date of birth, are still two different people. You need to tell them apart and follow each one across changes. That's an entity: defined by identity, not by its current attribute values.
Map this onto TypeScript you've written. A class Money { constructor(readonly amount: number, readonly currency: string) {} } is a value object: two Moneys with the same amount and currency are equal, and you never gave it an id because you never needed one. A class Account { id: string; balance: number } is an entity: the whole reason it has an id is that two accounts with the same balance are still two different accounts, and the account stays "the same account" as its balance changes. The presence of a meaningful id is the tell.
The practical consequence is how equality works, and it's a real difference:
- A value object is equal by value. Same components, equal object. You compare all its fields.
- An entity is equal by identity. Same
id, same entity, even if every other field differs. You compare ids and ignore the rest.
In TypeScript you rarely make this explicit, you just === two objects by reference and move on. In DDD you make it a deliberate design decision per type, because getting it wrong (value-comparing two entities, or manufacturing identity for a value) is a real bug.
Now the trap that catches people coming from an ORM, and it's the exact trap you'll see decoded in the final module against a real review comment. A row in a database table needs a primary key. So your notes table has an int id. It is tempting to conclude the note is therefore an entity with identity. It usually isn't.
Ask the litmus question about the note, not the row. If a note is addressed through its flight (two notes with the same flight-key and same body are the same note, there's nothing to track independently), then the note is a value object. The int id on the row is a surrogate key: a storage concern, a thing the database needs to have a primary key, not a domain identity. Letting the ORM's need for a PK convince you to model domain identity is importing a persistence artifact into your domain.
This distinction is the foundation for the next lesson (aggregates are a special kind of entity) and it's the single richest source of real feedback you got on Fusion. Nail the litmus test now and half the later material is just Fusion's specific way of expressing it.
Try it yourself
The identity litmus test
This one test decides entity versus value object every time. Have it cold.
What single question tells you whether a concept is an entity or a value object?
Classify each concept
Which of these is most clearly an entity (not a value object)?
Map a TS class to the right kind
Retrain the instinct on code you'd actually write. Do this in a scratch file or on paper.
Tick every step to confirm you did it.
The surrogate-id trap
The subtle mistake that trips up devs coming from an ORM.
A note in a database table has an `int id` primary key. Does that make it an entity?