Entity Pattern
DDD Entity — objects defined by identity continuity, not attributes
Tags
Overview
Purpose
DDD Entity — objects defined by identity continuity, not attributes
Context
Domain models where certain concepts must be uniquely distinguishable and tracked through time, even when their descriptive attributes change. The system needs to maintain references to specific instances across transactions, sessions, and subsystem boundaries. Multiple parts of the system hold references to the same conceptual thing and must agree on which instance they mean. The domain has lifecycle concerns: creation, state transitions, archival, or deletion of individually trackable things.
Problem
How to model domain concepts that must be tracked as unique individuals
through time and state changes — where two instances with identical
attributes may nonetheless be fundamentally different things?
Forces:
(1) Some domain concepts require continuity of identity across all state
changes — the "thread of identity" must never break
(2) Multiple system components hold references to the same concept and must
agree on which instance they reference, even across process boundaries
(3) Equality by attributes is insufficient: two customers named "John Smith"
at the same address are different people with different accounts
(4) The entity has a lifecycle (created, active, suspended, closed) that
must be managed and that affects what operations are valid
(5) Identity management has real cost: generating unique IDs, maintaining
indices, ensuring uniqueness constraints, handling identity in
distributed systems
Solution
Model the concept as an Entity: an object defined primarily by its identity rather than its attributes. Assign each Entity a unique, immutable identifier at creation that persists throughout its entire lifecycle. Implement equality based on identity comparison, not attribute comparison. Strip the Entity class of all attributes that do not directly serve identity or lifecycle management — push descriptive characteristics into Value Objects held by the Entity.
The design principle: if you can meaningfully ask "which one?" when faced with two instances having identical attributes, the concept is an Entity. The identity is the only attribute that must never change.
Consequences
Benefits:
- Continuity of identity enables tracking through any number of state changes,
which is essential for audit trails, history, and regulatory compliance
- Clear equality semantics: identity comparison is unambiguous and fast
- Lifecycle management becomes explicit: creation, transitions, and deletion
are first-class concerns with defined rules
- Reference stability: other objects can hold stable references via identity
that survive attribute changes
- Separation of concerns: identity logic is isolated from descriptive attributes
(which become Value Objects)
Liabilities:
- Identity management overhead: unique ID generation, uniqueness constraints,
and identity resolution across distributed systems add complexity
- Mutable state: Entities are inherently mutable, requiring careful handling
of concurrency, transactions, and consistency
- Tendency to bloat: developers often add too many responsibilities to Entities
instead of extracting Value Objects and Services
- Persistence complexity: Entities require repositories, identity maps, and
change tracking — significantly more infrastructure than Value Objects
- Testing difficulty: mutable state means test setup must carefully construct
Entities in specific states, and assertions must account for identity vs value
Collaborations
Factory creates Entity instances, assigning Identity at construction and ensuring all creation invariants are satisfied. Entity manages its own state transitions and invariant enforcement throughout its lifecycle. Repository persists Entity state and reconstitutes Entities from storage using Identity for lookup. Other Entities and services reference this Entity by its Identity, never by holding direct object references across aggregate boundaries. When Entity state changes, Repository is responsible for detecting and persisting the changes. Identity remains constant through all of these interactions — it is the stable anchor.
Structure
Participants:
- Entity: Identity-bearing domain object, mutable state, lifecycle management
- Identity: Unique immutable identifier (natural or surrogate)
- Repository: Persistence and retrieval by identity
- Factory: Complex construction with invariant enforcement
Relationships:
- Entity has-a Identity (composition, immutable)
- Repository manages Entity (persistence lifecycle)
- Factory creates Entity (construction)
- Entity contains ValueObject (descriptive attributes)
- Entity references other Entities via Identity (association)
Structural invariants:
- Identity is assigned once at creation and never changes
- equals() and hashCode() are based ONLY on Identity
- One Repository per Aggregate Root (not per Entity)
- Entity attributes that are descriptive should be Value Objects
Implementation
Design guidelines:
1. Choose identity strategy early
- Natural keys (email, ISBN): use when domain provides guaranteed uniqueness
- Surrogate keys (UUID, sequence): use when no natural key exists or when
natural keys might change
- Go: type CustomerID string or type CustomerID uuid.UUID
- Prefer UUIDs for distributed systems (no coordination needed)
2. Make identity immutable and central to equality
- Go: unexported id field, ID() accessor, Equals(other) based on ID only
- Java: final id field, equals/hashCode on id only
- TypeScript: readonly id property, custom equals method
3. Keep Entity classes lean
- Push descriptive attributes into Value Objects
- Entity should hold identity + Value Objects + lifecycle state
- Domain operations that don't need identity belong in Services
4. Design lifecycle transitions explicitly
- Define valid states and transitions (state machine)
- Guard transitions with invariant checks
- Emit domain events on significant transitions
5. One Repository per Aggregate Root
- Internal Entities within an Aggregate are loaded/saved with the root
- Never create Repositories for non-root Entities
- Repository interface is defined in the domain layer
6. In SBX .framework, the entity.yml protocol realizes this pattern directly.
Every framework entity has a `name` (identity), `purpose` (intent), and
the three-map PD architecture where `entities` holds identity-bearing
children. The `entities` map uses containment semantics with cascade-delete,
mirroring the Entity pattern's lifecycle management within Aggregates.
Example
Consider a banking system where a customer opens an account. Over the next decade, every attribute of that account changes: the balance fluctuates daily, the interest rate adjusts quarterly, the account type upgrades from savings to premium, the mailing address changes when the customer moves, and even the customer's name changes after marriage. Despite every single attribute being different from the original, the system must recognize this as the SAME account. Tax records, transaction histories, and regulatory reports all reference this continuous identity. If the system treated accounts as Value Objects (equality by attributes), every attribute change would create a "different" account, severing the thread that connects a decade of financial history to one customer relationship. The account must be an Entity — identified by its account number, not by the current values of its fields.
Resolution
Applying the Entity pattern to the banking account: Account is modeled as an Entity with an immutable AccountNumber as its Identity. Balance, interest rate, account type, and address are Value Objects held by the Account Entity. When the customer moves, a new Address Value Object replaces the old one — the Account's identity is unaffected. A decade of transactions all reference the same AccountNumber. The Repository retrieves the Account by AccountNumber regardless of how many attributes have changed. Regulatory queries trace the full history through the stable identity thread. Two accounts with identical balances and types are correctly recognized as different accounts because they have different AccountNumbers.
Known Uses
- User/Account in virtually every authentication system — users are tracked by ID through name changes, email changes, and password resets
- Order processing in e-commerce (Amazon, Shopify) — orders have lifecycle (placed, paid, shipped, delivered) with stable order ID
- JPA/Hibernate @Entity annotation (Java) — maps Entity pattern directly to ORM with @Id for identity and managed lifecycle states
- Active Record (Rails) — every model instance has an id primary key, found by id, equality by id
- DDD reference implementations (Vernon IDDD, Evans Cargo Tracker) — canonical Entity pattern usage with Repositories and Factories
See Also
- value-object: Entity and Value Object are the two fundamental object classifications in DDD. An object is modeled as one or the other based on whether identity or attributes define it. Choosing incorrectly leads to either unnecessary complexity (Entity for values) or lost continuity (Value Object for identities).
- aggregate: Aggregates use Entities as their root and potentially as internal components. The Aggregate Root is always an Entity. The aggregate boundary defines the Entity's transactional and consistency scope.
- typed-map-composition: SBX entity.yml's `entities` map uses typed-map-composition to hold identity-bearing child entities with containment semantics — the structural realization of Entity-within-Aggregate composition.