Value Objects as Embedded Entities

2021 01 01 head

This post discusses the value object pattern, and the factory pattern, which are tactical patterns in the domain-driven design Domain-Driven Design approach.

Value objects represent typed values that have no conceptual identity in your domain. They can help you write better codes that are less error-prone, more performant, and more expressive.

Value objects define the second kind of domain objects besides entities. Their main characteristic is immutability:

Attributes of a value object never change.

A second characteristic is that they do not have external identifiers. Unlike an entity, two value objects with the exact same properties are considered equal.

A third one is they should be self-validating. A value object shall verify the validity of its attributes when being created. If any of its attributes are invalid, the object should not be created and an error or exception should be raised.

Immutable value objects are defined as record in Java. Use the constructor to validate your objects. Throw an IllegalArgumentException if a validation error occurs.

Value Objects do have attributes and methods as entities. Attributes of value objects are immutable. Methods of value objects can only be queries without side effects. Operations never command a change to the internal state of a value object. We can pass value objects to clients without worrying they change them.

Many objects can be modeled as value objects instead of entities because they are defined through their attributes. These objects measure, quantify, or describe things in the domain model. Because they are so easy to handle, we should model domain abstractions as value objects as often as possible.

Addresses, Phone Numbers, Email Addresses

Some value objects are part of any commercial application domain. Postal addresses, phone numbers, and email addresses are canonical value objects. Other candidates are bank account IBAN numbers, job titles, job descriptions, company legal status.

This means that these objects do not have an object identifier and should not be stored in a separate table if stored in an SQL database. Value objects shall be embedded objects and be part of an object such as a person.

A person is an entity. It should have an external identifier and has a postal address, multiple phone numbers, and one or more email addresses.

Check your domain model and validate these assumptions.

  • Postal addresses, phone numbers, email addresses are value objects and have no external or internal identifiers.

  • They are always owned by other value objects or more often by entities such as people, companies, and delivery forms.

  • They are immutable objects.

  • Business processes can replace such an object with a new instance without impeding other domain instances.

  • Programmed in modern Java, these value objects should be records to guaranty immutability at the language level. As a bonus, you get the hashCode and equals methods for free.

A Java record class declaration is very compact. Often it is a one-liner.

Invoice Lines

Invoice lines are lines containing details in an invoice. Invoice lines are always value objects belonging to exactly one invoice.

To complicate matter, invoice lines have different types, such as a regular line with a specific article, quantity, unit price, and computed price. Often VAT aspects must be considered and computed for the overall invoice. An invoice line can also be a subtotal for a set of regular invoice lines.

VAT definition and computation is often a murky domain. The VAT value can be dependent on the article, the company selling it and the client buying it. When public administration defines a domain and associated business rules, the world often becomes quite complicated and ambiguous. The VAT administrative laws in Switzerland are more than 2000 pages of an illegible text.

The complete definition of this business domain is not part of this article.

The following design challenges exist

  • Serialization libraries such as JSON Jackson library cannot handle invoice lines with multiple Java class types. A workable approach is described and implemented with the open source component Gleam. The solution is to define selectors to instantiate the correct Java class.

  • Invoices are complex objects and ideal candidates for a document-oriented persistent approach. Invoices should not be stored in a relation-based persistence store, meaning an SQL database. To store them in a table, you have to model the various types of lines and add a technical identifier to each invoice line instance.

Factories

The factory pattern in Domain Driven Design DDD can be seen as a super pattern for the Gang of Four (GoF) creation patterns. Factories are concerned with creating new entities and value objects. They also validate the invariants for the newly created objects. We can place a factory on the entity or value object itself or on an independent object.

Java record constructs support validation of properties as part of the constructor. Invalid objects cannot be constructed if the constructor validation detects a violation. This mechanism ensures only valid instances exist in the domain.

Factories that are declared on the same object they create are either factory methods or prototype methods. The factory method creates a completely new object from the method parameters. The prototype method uses an existing instance to derive a new object.

The prototype method is supported with the keyword with in C#. The JEPs for deconstruction and construction of objects in a switch statement could provide a similar approach in the future for Java. The current version of Java JDK 16 does not support this feature.

When the creation logic is complex or has dependencies that are unnecessary for the created object. It is best to create a separate factory. This factory could provide multiple ways to create new instances.

Value Objects in Persistent Store

We encourage experimenting with the MicroStream approach for small projects footnote:[The Eclipse foundation took over the MicroStream library in 2023 under the new name Eclipse Store. The effort to persist a Java object graph is tiny.

You can always move to a no SQL solution when your application is successful and time comes to harden it. Another standard but cumbersome approach is to move to JPA.

Related concepts are discussed in our blog series