Entities and continuity of identity
Entities and continuity of identity#
TL;DR#
An entity is a domain object defined by a thread of continuous identity, not by the values of its attributes — two entities with identical attributes at a given moment are still different things, and one entity whose attributes all change over time is still the same thing, as long as its identity persists.
The idea#
Some things in a domain matter because of what they are (their attributes) — two ten-dollar bills are interchangeable, and nobody cares which specific one you hand over. Other things matter because of which one they are, independent of their current attributes — a specific bank account is not interchangeable with another account that happens to have the same balance right now, because tomorrow their balances will diverge and each has its own history.
Entities are the second kind. A Customer named "Alex Rivera" who changes their email address, moves house, and gets married (changing their last name) is still the same Customer throughout — every attribute can change, and identity persists. Conversely, two different Customer records that happen to have identical name, email, and address at this instant are still two different customers if they arose from two different signups — attribute equality does not imply sameness.
This distinction matters enormously for how you write equality checks, how you design database keys, and how you reason about concurrent modification: identity is the thing you must never lose track of, and it's the thing most bugs around "which record did I actually just update" trace back to getting wrong.
How it works#
Defining an identity operation, not relying on attribute equality#
An entity needs an explicit notion of "sameness" that's independent of attributes. In practice this is a stable identifier — often a surrogate key, sometimes a genuinely meaningful business identifier (a Social Security number, an ISBN, an account number) as long as it's guaranteed stable and unique for the life of the entity.
Worked example — a Customer entity:
class Customer:
def __init__(self, customer_id: CustomerId, name: str, email: str):
self.id = customer_id # the identity
self.name = name # attributes; can change freely
self.email = email
def __eq__(self, other):
return isinstance(other, Customer) and self.id == other.id
def __hash__(self):
return hash(self.id)
Equality is defined only on id. Two Customer objects with the same name and email but different ids are unequal; two Customer objects with the same id but different names (perhaps one is a stale in-memory copy) are equal. This is the opposite of how a value object (ddd-evans/05) defines equality, and mixing the two up is one of the most common modeling mistakes teams make early in a DDD effort.
Choosing a good identity#
Not every candidate identifier is safe. A Customer's email address looks like a natural identifier, but if the business allows email changes, using email-as-identity means that changing your email would, mechanically, make you "a different customer" to every downstream system that keyed off it — a chain of subtle bugs. The book's guidance: prefer an identifier the domain itself considers immutable and unique for the life of the entity, generated by the system when necessary (a UUID, a sequence-generated ID) rather than borrowed from an attribute that might legitimately change.
Worked example — a bad identity choice:
A hotel booking system used (room_number, date) as the "identity" of a Reservation. This seemed reasonable until the business introduced the ability to move a reservation to a different room without cancelling and rebooking it — at which point the "identity" changed out from under every piece of code holding a reference to that reservation, silently breaking anything that compared old and new state by that composite key. The fix was to introduce a proper generated ReservationId, decoupled from the room and date attributes entirely.
Continuity across state transitions#
An entity's whole reason for existing as a distinct concept is that it can undergo a history of state transitions while remaining "the same thing." A Order entity moves through Draft -> Submitted -> Shipped -> Delivered; at every step it's still the same order, referenced by the same OrderId, even though almost every other attribute (status, shipping date, tracking number) has changed. Code that treats each state as effectively "a new object" (e.g., creating a brand-new row with a new primary key every time status changes, instead of updating the existing row) breaks the entity's fundamental promise of continuity — you lose the ability to answer "what is the full history of this one order" without reconstructing identity from fragile heuristics.
Entities in collections and comparisons#
Because entity equality depends on identity, not attributes, comparing entities for business purposes ("has this order changed since I last loaded it?") requires comparing individual attributes explicitly, or comparing a version/timestamp — not relying on ==, which only tells you "is this the same conceptual thing," never "has anything about it changed." This distinction feeds directly into how repositories (ddd-evans/10) detect and persist changes, and into how aggregates (ddd-evans/08) manage internal entities that should only be reachable through the aggregate root's own identity.
Pros#
- Matches how the business actually thinks about many core domain concepts — a specific order, a specific patient, a specific bank account are not interchangeable with lookalikes.
- Makes state transitions and audit history coherent: you can trace one entity's full lifecycle because its identity never changes underneath it.
- Prevents an entire class of subtle bugs caused by conflating "looks the same" with "is the same" — critical in concurrent systems where two threads might load two in-memory copies of the same logical entity.
Cons#
- Choosing a durable, correct identity is genuinely hard and easy to get wrong early (see the
(room_number, date)example) — mistakes here are expensive to fix later because identifiers tend to propagate into foreign keys, URLs, and external integrations. - Entities require more careful equality and hashing implementation than plain data classes; getting
__eq__/__hash__(or language equivalents) wrong causes bugs that are hard to trace because they only manifest in collections or comparisons. - Overusing entity semantics for things that are actually values (see
ddd-evans/05) adds unnecessary identity-tracking machinery and mutable state where an immutable value would have been simpler and safer.
Alternatives#
- Value objects (
ddd-evans/05) — the natural counterpart: use these for anything defined entirely by its attributes, with no need to track a specific instance's history (money amounts, addresses, date ranges). - Anemic records with primary keys but no identity discipline — common in ORM-first designs, where a numeric database ID exists but the code never enforces identity-based equality or protects the identifier from being reassigned; this looks like an entity but doesn't reliably behave like one.
- Event-sourced identity — instead of an entity's current-state row, model it as a stream of events keyed by a stable ID, and derive current state by replaying; this preserves the same identity discipline but changes how state and history are stored (a more advanced technique, mentioned in
implementing-ddd, not covered in depth by the original book).
When to use it#
Model something as an entity whenever the business needs to track a specific instance through changes over time and distinguish it from other instances that might currently look identical — customers, orders, accounts, patients, shipments, employees.
When NOT to use it#
If two instances with identical attributes are genuinely interchangeable and nobody in the domain would ever ask "yes, but which one specifically" — money, a color, a date range, a phone number — model it as a value object instead (ddd-evans/05); giving something identity it doesn't need adds mutable state and tracking overhead for no domain benefit.
Key takeaways / mental model#
Ask: "if I have two of these that currently look identical, does the business care which one is which?" If yes, it's an entity, and identity is the one property you must protect above all others. If no, it's a value, and you should be defining equality by attributes, not by tracking an instance.
Self-check questions#
- Why is email a risky choice of identity for a
Customerentity, even though it's usually unique at any given moment? - Explain the difference between
__eq__on an entity and__eq__on a value object, and why mixing them up causes bugs. - In the hotel reservation example, what specific bug would
(room_number, date)-as-identity cause once room changes were allowed? Trace it through. - Give a real example from a codebase you know of something modeled as an entity that should have been a value object, or vice versa.
References#
- Domain-Driven Design: Tackling Complexity in the Heart of Software (Eric Evans), Chapter 5: "A Model Expressed in Software" (Entities section).