Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Value Object

Pattern

A reusable solution you can apply to your work.

A value object is an object defined entirely by its attributes, with no identity of its own. Two value objects with the same data are the same thing.

“When you care only about the attributes of an element of the model, classify it as a value object. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the value object as immutable.” — Eric Evans, Domain-Driven Design

Understand This First

  • Entity – entities are defined by identity; value objects are defined by content. Understanding one requires understanding the other.
  • Domain Model – the domain model decides which concepts are entities and which are value objects.

Context

You have a domain model with entities that carry identity through change. But not every concept in the model needs identity. A shipping address, a monetary amount, a date range, a color — these things are defined by what they contain, not by who they are. There’s no meaningful difference between two instances of “$47.00.” They aren’t two different forty-seven dollars; they’re the same value encountered twice.

This operates at the architectural level, alongside Entity. The decision to model something as a value object rather than an entity changes how you store it, compare it, and pass it around. It also changes what an agent can safely do with it: value objects can be copied, shared, and replaced freely because they carry no identity that needs protecting.

Problem

How do you model concepts that matter to the domain but don’t need their own identity, without cluttering the system with unnecessary tracking, keys, and lifecycle management?

A team building a food delivery app stores restaurant addresses in their own table with auto-incrementing IDs. When a restaurant moves, they update the address row. When the same physical address appears for two different restaurants in the same building, the system creates two rows with the same street, city, and zip but different IDs. Nothing in the business ever asks “show me all the things that happened to address #4827.”

The address IDs serve no purpose, but they cost something: every query that touches addresses joins through a foreign key, the database accumulates orphaned address rows when restaurants close, and the agent generating new features has to decide whether to create a new address record or reuse an existing one. That question shouldn’t exist.

The problem isn’t the address table. The problem is treating a value as if it were an entity.

Forces

  • Some domain concepts have no meaningful identity. Two instances of “10 kilograms” aren’t two different ten-kilogram objects; they’re interchangeable. Giving them identity adds complexity with no benefit.
  • Mutable objects with shared references create aliasing bugs. If two orders share a reference to the same Address object and you change one order’s address, the other order’s address changes too.
  • Agents default to the patterns they see most often. Most tutorial code models everything as a mutable class with an ID. Without explicit guidance, agents reproduce that pattern even when it’s wrong.
  • Simple data types (strings, integers) don’t express domain meaning. A price stored as a raw float loses the currency, and an address stored as a raw string loses the structure.

Solution

When a concept is defined entirely by its attributes, model it as a value object: a small, immutable object that compares by value rather than by reference.

Three properties define a value object:

  1. Equality by content. Two value objects with the same attributes are equal. A Money object with amount 47.00 and currency “USD” equals another Money object with the same fields. You compare them field by field, not by pointer or ID.

  2. Immutability. Once created, a value object doesn’t change. If you need a different amount, you create a new Money object. This eliminates aliasing bugs entirely: no shared reference can be changed out from under another holder because nothing changes.

  3. No identity. Value objects have no primary key, no UUID, no lifecycle. They exist as attributes of the entities that contain them. An Order entity has a shipping_address that is a value object. The address doesn’t have its own table with its own ID. It’s either embedded directly in the order’s row or stored in a way that doesn’t pretend it has a life of its own.

In practice, value objects do the heavy lifting for domain-specific types. Instead of passing raw primitives around your codebase, you wrap them in value objects that carry meaning and enforce rules. A Temperature value object knows its scale (Celsius or Fahrenheit) and refuses to be compared with a temperature in the wrong scale. A DateRange knows that its start must precede its end. The rules travel with the data.

For agentic workflows, name your value objects explicitly in the domain glossary. Tell the agent which concepts are values and which are entities. “Address is a value object. Do not give it an ID column. Embed it in the entity that owns it or store it as a composite of columns on that entity’s table. When comparing addresses, compare all fields.” Agents that know the distinction produce cleaner schemas and skip the unnecessary join tables.

How It Plays Out

A fintech team models Money as a value object with two fields: amount (a decimal) and currency (a three-letter ISO code). The object’s constructor rejects negative amounts and unknown currency codes. Its add() method throws if you try to add dollars to euros. Six months into the project, no one has written a currency-mismatch bug, because the type makes the mistake unrepresentable. When they direct an agent to add a multi-currency pricing feature, they pass the Money class definition in the prompt context. The agent generates code that converts currencies before adding, because the type’s constraints make the requirement visible.

A mapping startup stores geographic coordinates as a LatLng value object. Early on, a developer stored coordinates as two separate float columns and wrote helper functions to compute distances. The functions drifted: one used degrees, another used radians, and a third truncated to four decimal places for display but then fed the truncated values back into distance calculations. Wrapping the pair into a LatLng value object with a distance_to() method consolidated the logic. The object always stores full-precision radians internally and converts for display only on output. The scattered helper functions disappeared.

Prompt Guidance

“In this codebase, EmailAddress is a value object, not an entity. It validates format on construction and is immutable. Two EmailAddress instances with the same string are equal. Do not create an email_addresses table. Store the email as a column on the users table.”

Consequences

Value objects simplify the model. They eliminate unnecessary identity tracking, reduce join tables, and make comparison semantics obvious. Immutability removes an entire category of bugs — the ones where a shared reference changes unexpectedly. Domain rules embedded in value object constructors and methods catch mistakes at creation time rather than at use time.

The tradeoff is proliferation. A large domain model might produce dozens of value object types: Money, Address, DateRange, Temperature, Coordinate, PhoneNumber, Quantity. Each one needs a constructor, equality logic, and sometimes serialization support. In languages without first-class support for value types (like Java before records, or JavaScript), the boilerplate can feel heavy. Modern languages have closed much of this gap: Kotlin’s data class, Java’s record, Python’s @dataclass(frozen=True), and Swift’s struct all generate equality and immutability with minimal code.

There’s a judgment call at the boundary between entity and value object that shifts depending on context. In one system, a mailing address is a value object embedded in a customer record. In another (a postal logistics system that tracks delivery attempts per address), the same address concept is an entity with its own identity and history. The decision isn’t about what the thing is; it’s about what your system needs to do with it. If you need to track it over time, it’s an entity. If you need to describe something, it’s a value.

  • Contrasts with: Entity – entities are defined by identity; value objects are defined by content. The two complement each other in every domain model.
  • Uses / Depends on: Domain Model – the domain model identifies which concepts are entities and which are value objects.
  • Enables: Data Model – value objects shape storage decisions: embed in the owning entity’s table rather than creating separate tables with foreign keys.
  • Enables: Consistency – immutable value objects can’t be corrupted by concurrent modification.
  • Enables: Make Illegal States Unrepresentable – value objects that enforce rules in their constructors prevent invalid data from existing.
  • Refined by: Bounded Context – the same real-world concept may be a value object in one context and an entity in another.
  • Uses / Depends on: Ubiquitous Language – value objects carry domain vocabulary into the code, replacing raw primitives with named concepts.

Sources

  • Eric Evans introduced value objects as a core building block of domain-driven design in Domain-Driven Design: Tackling Complexity in the Heart of Software (2003), Chapter 5. The distinction between entities (defined by identity) and value objects (defined by attributes) is one of the book’s most practical contributions. The epigraph comes from his treatment there.
  • Martin Fowler formalized value object as a standalone pattern in Patterns of Enterprise Application Architecture (2002) and later expanded the treatment in his bliki entry “ValueObject” (2016), which clarified the distinction between reference objects and value objects and argued for immutability as the defining implementation choice.
  • Vaughn Vernon, in Implementing Domain-Driven Design (2013), provided the practical implementation guidance this article draws on: embedding value objects in entity tables, enforcing rules in constructors, and using the “does it need to be tracked over time?” test to distinguish values from entities.