Understanding Value Object

What it is, when to use it, and how to model it effectively.

Value Object is a tactical design pattern in Domain-Driven Design (DDD), as introduced in Eric Evans's Domain-Driven Design: Tackling Complexity in the Heart of Software. It was previously featured in Martin Fowler's Patterns of Enterprise Application Architecture.

Value Object represents a domain concept by encapsulating its values, protecting its invariants, and enforcing its business rules. This definition aligns with common tactical design patterns, but let's dive deeper into Value Objects specifically.

While it's often described as lacking identity, I offer a different perspective, which I explore further in Identity in software design.

Identity is about defining uniqueness and sameness. In software design, identity is relative, based on a specific criterion or Sortal that determines both.

For Value Objects, the Sortal is the content, with uniqueness defined by the content itself, and sameness determined by comparing that content through value equality. Since content defines uniqueness, Value Objects are inherently immutable.

This immutability makes them ideal for representing immutable domain concepts that quantify or describe others that may change. Value Objects often represent quantities, measurements, or observations as conceptual wholes, providing operations to manipulate and combine them. These operations are deterministic and produce Side-Effect-Free Behavior, ensuring referential transparency.

Thanks to this immutability, Value Objects also prevent aliasing issues, ensuring that multiple references to the same object do not lead to unintended side effects or state changes.

Value Objects enhance explicitness, preventing validation and duplication issues that often arise when modeling domain concepts using primitive types. They harness the type system to cohesively represent domain concepts, keeping validation and business logic centralized and avoiding the pitfalls of an anemic domain model.

To better understand what a Value Object is, when to use it, and how to model it, let's explore an example comparing implementations using primitives and Value Objects in a language without special constructs for Value Objects like TypeScript.

The listing below shows the Elevator and Load entities interact to manage the total weight of loads boarding into the elevator while ensuring that its capacity isn't exceeded:

Using primitives like number for weights introduces several issues:

  • Implicit meaning. The concept of weight is only implied by variable names like capacity and weight, leading to potential inconsistencies.
  • Implicit units. Weight is a quantity combining a value and a unit associated with it, but the unit is implied and unknown unless explicitly stated wherever it's used.
  • Inconsistent validation. Weight must be non-negative, but the primitive type does not enforce this invariant, requiring defensive programming in constructors and methods, which can lead to inconsistencies across the system.
  • Broken encapsulation. The `capacity` and `weight` attributes are public, meaning it is not possible to control access through behavior.
  • No enforcement of combination logic. Without encapsulation, the logic for combining weights through addition is not enforced by the type system, meaning there's no guarantee the weights will always be combined in a valid or meaningful way.

By contrast, implementing weight as a Value Object provides a more robust design. Since its identity is based on its content (a value and a unit), it naturally fits the immutable nature of concepts like weight, which quantifies load and elevator capacity.

The listing below shows a minimal implementation of the Weight Value Object and its usage by Load and Elevator.

Even this basic implementation offers clear benefits:

  • Explicit meaning. The concept of weight is explicitly modeled and reusable across the system.
  • Explicit units. Weight represents the conceptual whole of a quantity combining a value and a unit, enabling conversions between units and combinations of weights using different units.
  • Consistent validation. Invariants such as non-negative values are protected within the Weight class.
  • Strong encapsulation. The capacity and weight attributes are private, ensuring controlled access through behavior.
  • Enforced combination logic. The type system guarantees that weight combinations are valid and meaningful.

Although the Value Object approach may seem more complex, it efficiently addresses complexities overlooked when using primitive types, from ensuring domain explicitness to managing floating-point comparisons. More importantly, Software is a medium for storing executable knowledge, and using Value Objects helps make the code and tests valuable learning resources.

The simplicity and immutability of Value Objects make them easy to create, test, use, optimize, and maintain. When appropriate, prefer modeling domain concepts as Value Objects.

In summary, a Value Object:

  • Represents a domain concept by encapsulating its values, protecting its invariants, and enforcing its business rules.
  • Is identified by its content, with uniqueness defined by the content itself, and sameness determined by comparing that content through value equality.
  • Is inherently immutable.
  • Represents immutable domain concepts that quantify or describe others that may change.
  • Ensures referential transparency through deterministic operations that provide Side-Effect-Free Behavior.

For simplicity, I haven’t covered serialization and persistence of Value Objects, standard and tiny types, or discussed implementing interfaces like Comparable and Equatable, which are often used in Value Object implementations. These topics, along with other related considerations, will be explored in future articles.

References

  1. Evans, E. (2003). Domain-driven design: Tackling complexity in the heart of software. Addison-Wesley.
  2. Fowler, M. (2002). Patterns of enterprise application architecture. Addison-Wesley.
  3. Vernon, V. (2013). Implementing domain-driven design. Addison-Wesley Professional.
  4. Beck, K. (2002). Test driven development: By example. Addison-Wesley Professional.
  5. Fowler, M. (2015). Analysis patterns: Reusable object models. Addison-Wesley Professional.
  6. Millett, S., & Tune, N. (2015). Patterns, principles, and practices of domain-driven design. Wrox.
  7. Vernon, V. (2016). Domain-driven design distilled. Addison-Wesley Professional.

Subscribe to Adriano Di Giovanni

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe