Whetstone.
DDD from zero, in a language you knowEntities versus value objects
Module 1 · Lesson 313 min

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.

Practice

Try it yourself

Recall

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?

Reveal answer

"If two of these have identical attributes, are they the same thing, or two different things I need to tell apart?" If they're the same (two £50 amounts are just £50), it's a value object, defined entirely by its values. If they're different and you need to track each one across changes (two people both named Alex are different people), it's an entity, defined by identity.

Quiz

Classify each concept

Which of these is most clearly an entity (not a value object)?

  1. AA customer's bank account, tracked over time as its balance changes
  2. BA money amount of exactly £4,200 in GBP
  3. CAn email address such as name@example.com
  4. DA postal address with a street, city, and postcode
Show answer

Correct answer: A — A customer's bank account, tracked over time as its balance changes

A bank account has identity: it's the same account even as its balance changes, and two accounts with the same balance are still two different accounts. That's an entity. Money, an email, and an address are value objects: fully defined by their values, and two with the same value are interchangeable.

Do

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.

  • Write a TS class Money { constructor(readonly amount: number, readonly currency: string) {} }. Ask the litmus question: are two Moneys with the same amount and currency the same thing? (Yes.) So it's a value object.
  • Write a TS class Account { id: string; balance: number }. Ask again: are two accounts with the same balance the same account? (No, the id differs.) So it's an entity.
  • Notice the tell: the value object had NO id and you never wanted one; the entity NEEDED an id to answer 'same or different'.
  • Confirm you can now look at any class and predict which kind it is from whether identity matters
Done whenYou classified Money as a value object and Account as an entity using only the identity question, and you can see that the presence of a meaningful id is the tell.
Recall

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?

Reveal answer

No. A database table needs a primary key for storage reasons, but that surrogate id is a persistence concern, not a domain identity. If the note is really addressed through its flight (two notes with the same flight-key and body are the same note), it's a value object whose row happens to need a PK. Don't let the ORM's need for a key trick you into modelling domain identity that isn't there.

Sign in to track your progress →