---
title: Entities
source: 'https://academia.sh/en/courses/domain-driven-design/entities'
course: 'Domain-Driven Design'
language: en
updated: '2026-08-23T07:01:19+00:00'
license: 'CC BY-SA 4.0'
---

# Entities

The selection criterion for objects defined by identity: counting how many separate instances twelve shipments reduce to under value equality, measuring how many invoice reads come out correct, working out how many keys point to the same identity after a reweighing correction, and comparing what the two equality definitions answer to the domain's two questions.

In the previous lesson's unified version, a shipment was a flat record carrying three fields,
and two shipments could only be considered the same if their fields were equal. That is not
true in the domain. The same shipment's weight changes with a reweighing correction, and the
shipment is still the same shipment; two separate shipments can have every field equal and
still be two shipments.

The object that carries this distinction is called an **entity**: an object that stays the
same even as its fields change, and whose equality is therefore defined not by its fields but
by an **identity**. The definition was given in the Introduction to Object-Oriented
Programming course; the work here is not the definition but the **selection criterion** —
which domain concept gets an identity, and what is lost when it does not.

## Two Questions

The criterion reduces to two questions. First: is this thing still the same thing after its
fields change? Second: do two instances whose fields are all equal have to be tracked
separately?

In the shipping-fee library, the answer to both is yes. A customer sends five parcels of the
same weight to the same zone on the same day; each of the five gets its own invoice. When the
same parcel is weighed, its declared weight is corrected; the invoice keeps being issued to
that parcel.

## Two Models

The first version defines a shipment by its fields. Equality is a key produced by combining
the fields.

```sh
mkdir -p value identity
```

```js
// value/shipment.mjs — shipment without identity: equality is equality of fields
export const shipment = (customer, weight, zone) => ({ customer, weight, zone });
export const key = (s) => `${s.customer}|${s.weight}|${s.zone}`;
export const equals = (a, b) => key(a) === key(b);
export const correctWeight = (s, newWeight) => ({ ...s, weight: newWeight });
```

The second version gives the shipment a number. Equality looks only at that number; the
weight correction changes the object but does not touch its identity.

```js
// identity/shipment.mjs — entity: equality is equality of the shipment number
export class Shipment {
  constructor(shipmentNo, customer, weight, zone) {
    this.shipmentNo = shipmentNo;
    this.customer = customer;
    this.weight = weight;
    this.zone = zone;
  }
  key() { return this.shipmentNo; }
  sameAs(o) { return o instanceof Shipment && o.shipmentNo === this.shipmentNo; }
  correctWeight(newWeight) { this.weight = newWeight; return this; }
}
```

## Measurement

The measure's input is twelve shipments and the invoice cut for each one. Five shipments
belong to the same customer, the same weight, and the same zone; two belong to a second
customer, and two are repeated among a third customer's heavy parcels.

```js
// records.mjs — 12 shipments in the pricing context and the invoice cut for each
export const RECORDS = [
  ["G-1041", "ARC", 2.4, "34", "F-9001"],
  ["G-1042", "ARC", 2.4, "34", "F-9002"],
  ["G-1043", "ARC", 2.4, "34", "F-9003"],
  ["G-1044", "ARC", 2.4, "34", "F-9004"],
  ["G-1045", "ARC", 2.4, "34", "F-9005"],
  ["G-1046", "ARC", 5, "34", "F-9006"],
  ["G-1047", "BKM", 0.8, "06", "F-9007"],
  ["G-1048", "BKM", 0.8, "06", "F-9008"],
  ["G-1049", "BKM", 12, "65", "F-9009"],
  ["G-1050", "CTS", 0.8, "06", "F-9010"],
  ["G-1051", "CTS", 18, "65", "F-9011"],
  ["G-1052", "CTS", 18, "65", "F-9012"],
];
```

For each version, the invoice table is built with the shipment's key in that version. Then
the first shipment's weight is corrected from 2.4 to 3.1 kilograms and the table is read
again.

```js
// identity-measure.mjs — how many instances do 12 shipments reduce to, how many invoice reads are correct
import { RECORDS } from "./records.mjs";
import * as value from "./value/shipment.mjs";
import { Shipment } from "./identity/shipment.mjs";

function measure(label, build, key, correct) {
  const objects = RECORDS.map(([no, c, w, z]) => build(no, c, w, z));
  const table = new Map();
  objects.forEach((o, i) => table.set(key(o), RECORDS[i][4]));
  const right = objects.filter((o, i) => table.get(key(o)) === RECORDS[i][4]).length;
  console.log(`${label}: 12 shipments -> ${table.size} separate instances`);
  console.log(`  invoice reads correct ${right}/12`);

  const old = key(objects[0]);
  const updated = correct(objects[0], 3.1);
  table.set(key(updated), table.get(old));
  objects[0] = updated;
  const rightAfter = objects.filter((o, i) => table.get(key(o)) === RECORDS[i][4]).length;
  const pointer = [...table.keys()].filter((k) => k === old || k === key(updated)).length;
  console.log(`  after the reweighing correction: table entries ${table.size}, ` +
    `keys pointing to G-1041 ${pointer}`);
  console.log(`  invoice reads correct after the correction ${rightAfter}/12`);
}

measure("value", (no, c, w, z) => value.shipment(c, w, z), value.key,
  (s, w) => value.correctWeight(s, w));
measure("identity", (no, c, w, z) => new Shipment(no, c, w, z), (s) => s.key(),
  (s, w) => s.correctWeight(w));

const [first, second] = [value.shipment("ARC", 2.4, "34"), value.shipment("ARC", 2.4, "34")];
const [s1, s2] = [new Shipment("G-1041", "ARC", 2.4, "34"), new Shipment("G-1042", "ARC", 2.4, "34")];
console.log(`are two separate shipments equal -> value ${value.equals(first, second)}, ` +
  `identity ${s1.sameAs(s2)}`);
const s1b = new Shipment("G-1041", "ARC", 3.1, "34");
console.log(`is the corrected shipment the same -> value ` +
  `${value.equals(first, value.correctWeight(first, 3.1))}, identity ${s1.sameAs(s1b)}`);
```

```sh
node identity-measure.mjs
```

```
value: 12 shipments -> 6 separate instances
  invoice reads correct 6/12
  after the reweighing correction: table entries 7, keys pointing to G-1041 2
  invoice reads correct after the correction 6/12
identity: 12 shipments -> 12 separate instances
  invoice reads correct 12/12
  after the reweighing correction: table entries 12, keys pointing to G-1041 1
  invoice reads correct after the correction 12/12
are two separate shipments equal -> value true, identity false
is the corrected shipment the same -> value false, identity true
```

The first number gives the size of the loss: under value equality, twelve shipments reduce
to six instances. Six shipments collapse onto another shipment's key, so half of the invoice
reads come out wrong — six shipments show the invoice that was not cut for them.

The second number shows the reverse corruption. The weight correction produces a new key in
the value version; the old key cannot be dropped, because other shipments still depend on it.
As a result, the table grows to seven entries, and a single shipment ends up shown under two
keys. In the identity version, the same correction does not change the table's size: the key
`G-1041` stays put, and all twelve reads stay correct.

The last two lines reduce the criterion to a single sentence. The domain's first question is
whether two shipments are the same, and the answer is no; value equality answers yes. The
second question is whether the corrected shipment is still the same shipment, and the answer
is yes; value equality answers no. Of the two definitions, one answers both of the domain's
questions wrong, and the other answers both right.

## Where Identity Comes From

The only requirement identity carries is that it does not change over the object's lifetime
and is not repeated in any other object. If the domain already has a ready-made identity, that
one is used: a shipment number, a contract number, a tax ID. If not, one is generated, and the
rule for generating it is a decision for the application, not the domain.

The measure breaks when a field from the domain is used as if it were identity. In the pricing
context, the combination of customer and zone often produces a single shipment; "often" is not
an identity criterion, and the measurement above shows the cost of that assumption. For the
same reason, correctable fields such as weight, postal code, and declared value cannot be part
of an identity.

Identity's second consequence is uniqueness: if two instances share the same identity exist in
memory at once, which one is correct becomes unclear. This problem was addressed in the
Identity Map lesson of the Design Patterns course; the entity decision is that pattern's
precondition, because without an identity to preserve uniqueness, there is no key for the map
either.

## When Identity Is Not Needed

If the criterion's two questions both come back no, the object is not an entity. The library
has a clear example of this: a zone coefficient. There is no distinction between one 1.35
coefficient and another 1.35 coefficient, and the coefficient has no life story. The same is
true of weight, monetary amount, and discount rate.

Giving these concepts identity corrupts the measure in the opposite direction. Once a
coefficient is assigned a number, equality comparison looks at that identity, and two values
that are equal in the domain no longer come out equal in the code. Identity produces a gain
where it belongs; where it does not, it adds a rule to every comparison.

## Summary

- An entity is an object that stays the same as its fields change and whose equality is
  defined by an identity; the selection criterion is two questions — does it stay the same
  when its fields change, and do two instances with equal fields have to be tracked
  separately.
- Under value equality, twelve shipments reduced to six instances; 6 of the invoice reads
  came out wrong.
- The reweighing correction grew the value version's table to seven entries and left one
  shipment shown under two keys; the identity version's table stayed at twelve entries, with
  all twelve reads correct.
- Value equality answered both of the domain's questions wrong; identity equality answered
  both right.
- Identity has to be a name that does not change over the object's lifetime and is not
  repeated; correctable fields cannot be part of identity.
- If both questions come back no, identity is not needed; giving identity to concepts such as
  coefficients, weights, and rates adds an unnecessary rule to every comparison.

## Next Step

This lesson's last section counted the concepts that do not need identity, but still kept
them as bare numbers and strings: weight is a `number`, zone is a `string`. That leaves the
code free to add a weight to a volume, compare a kilogram with a gram, and multiply a cent by
a unit of currency. The next lesson turns these concepts into their own types, counts how
many places the equality rule is written by hand, and measures how many cases a unit mix-up
produces a silently wrong amount.
