This post is part of my Dev rules of thumb series. In them, I leave you with a poster with a few rules I follow for each concept, which you can print out and scatter around your office, and a brief explanation of what that pattern is about. You can also find a PDF of the poster at the bottom of this post, for better quality printing. However, please remember, a dev rule of thumb is a Principle with broad application that is not intended to be strictly accurate or reliable for every situation.
Value objects are sometimes confused with DTOs, however, while DTOs are simple data containers with no significant logic, Value Objects are a “thing”, they represent a cohesive concept; they can wrap multiple attributes and the logic associated with them to fully encapsulate that single concept.
For example, a “Time” class will contain the data that represents the time, but it may also contain logic to return the total number of seconds it represents or a method to give us the time in a specific format.
The data they contain is relatively simple, only scalars and other value objects, but the data graph should not be big, they should not be carrying around entities inside.
Value Objects in a domain might be entities in another domain, and vice versa. If our application is a watch, the Time class is likely part of the domain, however, if our application is an arithmetic calculator, it’s unlikely that Time is part of the domain. Other examples of Value Objects might be Money, Currency, Point, Height, and Color.
It is a good practice to make immutable classes, as much as possible. Value Objects are no exception. The reason is that several places in the codebase can hold references to an object, and they might inadvertently change the object data, introducing bugs.
Suppose we have an entity representing a user, which contains a Date object representing when the user registered in our application. If we want to send an email to that user two weeks later, we can get that Date object, add 15 days and have that date, right? Well, if the Date object is mutable and we add 15 days and then persist the object (as it’s done automatically by ORMs), we have effectively changed the registration date of that user. On the other hand, if the object is immutable, when we add those 15 days, instead of changing its internal data, it will return a new Date object with the intended date: The original object, representing the registration date, stays intact.
Unlike a DTO, the Value Objects mean something, they are not just a container of data, so they have internal rules to decide what is a valid or invalid state. These types of objects are never in an invalid state, they must be validated at creation time and, since they are immutable, they will never change into an invalid state.
Another interesting characteristic of these types of classes, is that, although they might be persisted (ie. to an RDBMS), they are not a specific “thing” in our domain, they don’t have a history, they don’t have an identity (an ID property). For example, if I have an application to manage bike rentals, the bikes will have a history specific to each of them, this means they have an identity, and to access them in a database we will likely need an ID property. However, the Tire class used to represent the amount and size of the tires that each bike uses, don’t have an identity, we don’t care about the specific history of those tires.
Another characteristic worth noting is that when comparing value objects, they will represent the same thing when their internal values are the same, not when their memory reference is the same nor when their ID is the same (they don’t even have an ID). So, when we have two point instances P1(1,3) and P2(5,6) they are clearly different, and when we have another two instances P3(7,8) and P4(7,8) their comparison should say their are the same since they represent the same point, despite being two different instances.
Here’s an example of a Value Object in PHP, representing a point.
The relevant part in the example is the
equals methods existing (logic associated to the concept) and returning a new Point instead of changing the current one (immutability). The
__toString method is there just for convenience of the example, a VO doesn’t need to have such method, in fact, it doesn’t even necessarily need getters.