Most domain models do not break down because the wrong code was written. They break down because the team never agreed on which concepts in the product have a life of their own and which ones are just descriptions. The choice between an entity and a value object is the most basic object-level modeling decision in Domain-Driven Design (DDD), and it shapes how the product handles change for years to come.
The previous post in this series covered the layered architecture and explained where business logic should live. The next question is what the domain layer is actually made of. The answer is a small set of DDD building blocks — objects, services, modules, aggregates, factories, repositories — and the most basic of these is the object, split into entity and value object.
Getting this distinction right reduces product complexity. Getting it wrong forces teams to argue about identity, history, and consistency every time a feature touches a shared concept. The good news is that the test is simple once the vocabulary is clear.
DDD Building Blocks: A Map of the Domain Model

DDD is not a single technique. It is a set of building blocks, each answering a different question about the domain.
- What exists in the domain?
- What has to stay consistent?
- Where should the rules live?
- How does the model evolve safely over time?
The core building blocks can be summarized in one table.
| Building block | What it represents | Core responsibility | Core question |
|---|---|---|---|
| Object | A meaningful concept in the domain | Carries domain meaning and behavior | “Is this how users actually think about it?” |
| Service | A business decision that spans multiple concepts | Enforces rules between objects | “This rule does not belong to a single object — where does it go?” |
| Module | A coherent part of the domain story | Groups related concepts together | “What kind of product problem does this part exist to solve?” |
| Aggregate | A consistency boundary | Keeps invariants intact together | “What must never be left half-updated?” |
| Factory | Creation logic for domain objects | Enforces the rules at the moment something comes into existence | “What conditions must hold for this to exist in the first place?” |
| Repository | Access to existing domain objects | Manages retrieval and persistence | “How do we handle this safely over time?” |
Building Blocks of Domain-Driven Design
Each block exists to answer a different kind of product question.
- Objects define what concepts the product contains and carry domain meaning.
- Services define how the team makes business decisions when a rule spans several concepts.
- Modules define how the domain is organized so the model stays understandable as it grows.
- Aggregates define where consistency must be enforced immediately and where it can be relaxed.
- Factories define what conditions must hold for something to exist in the first place.
- Repositories define how the system reaches domain objects and persists them without leaking storage concerns.
These building blocks tend to emerge naturally as the product starts to face harder questions about identity, change, consistency, and growth. When a feature looks simple but turns out to be unexpectedly expensive, the current model usually has a missing, overloaded, or misplaced responsibility.
This post focuses on the first building block — the object — because everything else builds on it.
What is an “Object” in DDD?
Before comparing entities and value objects, it helps to define what “object” means in DDD. The word is overloaded in software, and the DDD meaning is narrower than the one most developers carry over from object-oriented programming.
In DDD, an object is not a technical artifact or a programming construct. It is a way to represent a concept in the domain that the product needs to reason about. An object does three things:
- It carries meaning in the domain.
- It packages rules or constraints.
- It participates in business decisions.
What matters is not how the object is implemented but what role it plays in expressing the user’s reality as it relates to the product.
From this angle, DDD makes one further distinction. Some objects persist over time — these are entities. Other objects only describe a situation or a state — these are value objects. The split is not technical. It is about how the product handles change.
A simple analogy: a person and an address are both real, but they behave differently. “Min-Soo Kim” the person stays the same person after moving. “123 Yeoksam-dong, Gangnam-gu, Seoul” the address, once it changes, is simply a different address — not the “same address that was modified.” The first behaves like an entity. The second behaves like a value object.
Entity vs Value Object: Identity vs Description
The layered architecture explains where logic lives. The building blocks explain what that logic is made of. Most domain models start to wobble when the team fails to make one core distinction clear:
What should be modeled as an entity, and what should be modeled as a value object?
The answer turns on identity versus description. An entity is something the product treats as the same thing over time, even when its details change. A value object is something the product uses to describe a situation, with no need to track it across changes.
What an entity is: identity and continuity over time
An entity is something the product treats as the same thing over time, even when details change. What defines an entity is not how it looks at a particular moment but the fact that the system cares about its continuity.
In practice, an entity almost always has an ID. The important idea, though, is not the ID itself — it is the identity behind it.
Consider a B2B recruiting platform, used internally by hiring teams to manage applications and interviews. Two entities show up almost immediately in any recruiting domain:
- Candidate: a real person the product interacts with. Their profile can be updated, their contact details can change, and past interactions still belong to this same person.
- Application: a specific attempt by a candidate to apply for a role. It has a clear start and end, moves through stages (screening, interviewing, offer), and both its history and its current state matter independently.
Both are entities because the product has to refer back to them as the same thing after changes have happened. The Application entity can be visualized like this:
Core concepts (recruiting domain)
─────────────────────────────────
┌──────────────────────────┐
│ Candidate (Entity) │
└─────────────┬────────────┘
│ participates in
▼
┌────────────────────────────────────────────┐
│ Application (Entity) │
│--------------------------------------------│
│ Identifier: applicationId │
│ │
│ Fields: │
│ - candidateId: CandidateId │
│ - status: ApplicationStatus │
│ - interviewSlot: InterviewSlot | null │
│ │
│ Invariants: │
│ - State transitions must be valid │
│ - interviewSlot required when status is │
│ INTERVIEW │
│ - interviewSlot is null in other states │
└────────────────────────────────────────────┘
The point is not the stages themselves. The point is that identity stays constant while state evolves. If users expect the same object to still be there tomorrow, the team is most likely looking at an entity.
A useful analogy is a passport. The photo can change, the address can be updated, new visa stamps can be added — but as long as the passport number is the same, it is the same person’s same passport. The details keep shifting; the identity does not.
What a value object is: replaceable description and immutability
A value object is something the product uses to describe something, not to track it. The easiest way to tell them apart is to ask a simple question:
If this changes, do we still think of it as the same thing?
If the answer is no, it is most likely a value object.
Stay with the internal recruiting platform. Candidate and Application are entities. But InterviewSlot plays a very different role. It only describes when an interview is scheduled. The distinction becomes clear once we see how InterviewSlot relates to the other concepts in the domain:
Core concepts (recruiting domain)
─────────────────────────────────
┌──────────────────────────┐
│ Candidate (Entity) │
└─────────────┬────────────┘
│ participates in
▼
┌────────────────────────────────────────────┐
│ Application (Entity) │
│--------------------------------------------│
│ Identifier: applicationId │
│ │
│ Fields: │
│ - candidateId │
│ - status │
│ - interviewSlot │
│ │
│ Rules / invariants: │
│ - status follows allowed transitions │
│ - interviewSlot allowed only when status │
│ is INTERVIEW │
└─────────────┬──────────────────────────────┘
│ has
▼
┌──────────────────────────────────────────┐
│ InterviewSlot (Value Object) │
│------------------------------------------│
│ Fields: │
│ - startTime │
│ - endTime │
│ │
│ Invariants: │
│ - startTime < endTime │
│ │
│ Characteristics: │
│ - No identity │
│ - No lifecycle │
│ - Replaced, not updated │
└──────────────────────────────────────────┘
This diagram makes one thing visible: InterviewSlot exists only to describe a condition on the Application.
- It is not referenced independently.
- It is not tracked over time.
- It has no meaning beyond its values.
When the interview time changes, the product does not need to remember the previous slot. It just uses a new one. There is no notion that “this particular interview slot” survives across changes.
From a product perspective, a value object works well when:
- History is not important on its own.
- Past values do not influence future decisions.
- Equality is determined entirely by the attributes.
If the product later starts treating the interview slot as something the team books, reschedules, and tracks independently, that shift is a signal that the boundary is moving. At that point, the concept needs identity and continuity, and the team should start treating the interview slot as an entity. That transition is the subject of a later section.
Why Model Value Objects Separately

At first, it can look simpler to store start time and end time directly as fields on the Application entity:
┌────────────────────────────────────────────┐
│ Application (Entity) │
│--------------------------------------------│
│ Identifier: applicationId │
│ │
│ Fields: │
│ - candidateId │
│ - status │
│ - interviewStartTime │
│ - interviewEndTime │
│ │
│ Rules / invariants: │
│ - status follows allowed transitions │
│ - interview times allowed only when │
│ status is INTERVIEW │
└────────────────────────────────────────────┘
This works technically. But it blurs an important question:
Are we just storing timestamps, or are we modeling an interview slot?
The moment the product starts caring about “what counts as a valid slot,” a separate value object pays off. Modeling InterviewSlot as its own value object accomplishes several things at once:
┌──────────────────────────────────────────┐
│ InterviewSlot (Value Object) │
│------------------------------------------│
│ Fields: │
│ - startTime │
│ - endTime │
│ │
│ Invariants: │
│ - startTime < endTime │
│ │
│ Characteristics: │
│ - No identity │
│ - No lifecycle │
│ - Replaced, not updated │
└──────────────────────────────────────────┘
There are four reasons this matters.
1) It captures a domain concept explicitly
Domain experts and users do not say “start time and end time.” They say “interview slot.” When a term shows up consistently in product discussions, it earns a place in the model. This is the foundation of the ubiquitous language — the shared vocabulary that keeps the team, the model, and the product aligned.
The rule “start time must be before end time” is not a rule about the Application. It is a rule about a time range. Putting it inside InterviewSlot prevents that constraint from being scattered across other services or entities.
3) It becomes reusable
Once InterviewSlot exists, other parts of the domain can use it — for checking interviewer availability, detecting scheduling conflicts, and similar logic. Without it, each feature ends up writing its own version of “a valid time range,” each with slightly different assumptions.
4) Change becomes cheaper
When time-related rules evolve, having a single place to change them matters. Consolidating interview time logic into a value object limits the blast radius of any change.
A good test is to ask three questions about a candidate concept:
- Do domain experts use a specific term for this combination of fields?
- Are there invariants that tie these fields together?
- Does another part of the domain need the same concept?
If the answer to any of these is “yes,” a value object is probably appropriate — even if it is only used in one place to start.
When a Value Object Becomes an Entity: The Two-Sided Platform Example

Imagine the product evolves into a two-sided recruiting platform, where candidates and interviewers actively coordinate schedules. In this new model, the interview slot is no longer just selected and stored. It is managed.
The interview slot can now:
- be proposed by an interviewer
- be booked by a candidate
- be rescheduled multiple times
- be cancelled and tracked afterwards
Once the platform works this way, new questions become important:
- “Is this the same interview slot that was scheduled last week?”
- “Who changed it, and when?”
- “If this interview moves, do we need to notify both sides?”
These questions introduce something new: continuity. The moment the product has to refer back to the same interview slot over time, the concept has crossed a new boundary. It is no longer a description of a time. It is something the product tracks as the same thing over time.
At that point, the model changes:
Later stage (two-sided recruiting platform)
───────────────────────────────────────────
┌──────────────────────────────────────────┐
│ InterviewSlot (Entity) │
│------------------------------------------│
│ Identifier: interviewSlotId │
│ │
│ Fields: │
│ - startTime │
│ - endTime │
│ │
│ Rules / invariants: │
│ - Rescheduling follows allowed rules │
│ - Cancellations are recorded │
│ │
│ Characteristics: │
│ - Identity preserved │
│ - History matters │
│ - Changes are tracked │
└──────────────────────────────────────────┘
What actually changed was user expectation of the product. In the internal recruiting system, the interview time answered “When is the interview for this application?” In the two-sided platform, the interview slot answers “What is the current state of this specific scheduling agreement?”
The moment the product says “this particular interview” and cares about its past, the concept is no longer a value object. It is an entity.
This is also why the entity-vs-value-object choice is not a one-time decision. As the product changes, what is a description today can become an identity tomorrow.
Common Modeling Mistakes: Entity Overuse, Mutable Value Objects, Anemic Entities
Three modeling mistakes show up repeatedly, especially while a team is still learning the domain. They usually start as reasonable assumptions that stop being reasonable as the product grows.
1) Modeling everything as an entity
A common early decision is to model every concept as an entity “just in case.” This introduces identity and lifecycle in places where the product does not actually need them.
The result is more unnecessary IDs and persistence logic, more state to keep consistent, and more coordination cost for simple changes. Small feature changes take longer than expected, and the team keeps debating “exactly which one is it?” for concepts users do not care to distinguish.
2) Mutable value objects
Value objects exist to describe something, not to track it over time. When a value object starts mutating internally, it begins to behave like an entity. Identity becomes implicit but not explicit, history exists but is not modeled, and the boundary becomes unclear.
When this happens, one of two things is true: the concept actually needs identity, or there is another missing concept that should own the change.
3) Anemic entities
An anemic entity stores data but owns no behavior. In this kind of model, the entity has only fields, services enforce all the rules, and behavior lives far away from the concept it is supposed to describe.
This separation makes the model harder to reason about. Rules scatter, intent gets diluted across layers, and understanding the product requires reading multiple services. The entity becomes a data bag, and the domain logic loses its center of gravity.
A useful analogy is bad job design in a company. Giving every employee a unique employee number (entity ID) is fine; assigning unique IDs to every intern’s lunch order is overkill (everything as an entity). Rewriting a job description every week makes it unclear whose job it is (mutable value object). And giving an employee a title without any authority leaves them as a figurehead while their manager holds every decision (anemic entity).
Conclusion
The entity-vs-value-object distinction sits at the base of the domain model because it answers two simple questions: does the product care about identity, or only about description? Entities are tracked over time and keep their identity through changes. Value objects describe a situation and are replaced when they change. Most modeling problems trace back to a concept that was treated as one kind of thing when it should have been the other.
This distinction also clarifies the limits of the object building block. Some rules belong to one object. But many rules — pricing, transfers, eligibility, scheduling — sit between objects and do not naturally fit inside any single one. That is where the next building block comes in. The next post in this series covers domain services: what they are, how they differ from application and infrastructure services, and how to keep business rules close to the concepts they describe.
Domain-Driven Design Series
(1) Domain-Driven Design as a Product Management Framework
(2) What is a Domain Model? Definition, Quality Criteria, and Examples
(3) DDD Layered Architecture: The Role of Each Layer in Domain-Driven Design
(4) Object in DDD: Building Blocks of the Domain Model
(5) Service in DDD: When Business Logic Doesn’t Belong to a Single Entity
(6) Modules and Bounded Contexts in DDD: Structuring Domains for Scale
(7) Aggregate Root Pattern in DDD: Consistency Boundaries Explained
(8)Factory Pattern in DDD: Controlling How Aggregates Come Into Existence
(9) Repository in DDD: Pattern, Principles, and the Domain Model Checklist
