Value Objects to the rescue!
Value objects represent typed values, that have no conceptual identity, in your domain. They can help you write better code that is less error-prone, more performant and more expressive. Wow!
Value objects are one of the building blocks introduced in the book Domain-Driven Design (also known as “the blue book”), written by Eric Evans. DDD is a software approach that provides a framework meant to tackle complex domains. DDD adds a lot of overhead and is only really useful if you are dealing with a very complex domain. Fortunately, we can use value objects all the time, even when not practicing DDD.
In DDD, value objects differ from entities by lacking the concept of identity. We do not care who they are but rather what they are. They are defined by their attributes and should be immutable.
In his book, Eric Evans gives the following example for a value object:
When a child is drawing, he cares about the color of the marker he chooses, and he may care about the sharpness of the tip. But if there are two markers of the same color and shape, he probably won’t care which one he uses. If a marker is lost and replaced by another of the same color from a new pack, he can resume his work unconcerned about the switch.
Value objects have three main characteristics:
1. Value Equality
Value objects are defined by their attributes. They are equal if their attributes are equal. A value object differs from an entity in that it does not have a concept of identity.
For example, if we consider a
Duration as a value object, then a duration of 60 seconds would be the same as a duration of one minute since the underlying value is the same.
This characteristic can be easier to implement in some languages than in others. In Java, we need to redefine the
hashCode methods. Indeed, by default, Java tests equality against the objects’ references. Another option is to use an annotation processing tool such as Lombok and then use the @Value annotation. I personally prefer using Kotlin. With it, you can simply define a data class.
Once created, a value object should always be equal. The only way to change its value is by full replacement. What this means, in code, is to create a new instance with the new value.
We will see later how immutability allows us to prevent errors from occurring and also how it can help optimise performance.
When implementing a value object, we need to make sure that we remove all setters and that getters return immutable objects or copies to guarantee that nobody can change those values from the outside. In Java, we can use the
final keyword and in Kotlin we can use the
A value object must verify the validity of its attributes when being created. If any of its attributes are invalid, then the object should not be created and an error or exception should be raised. For instance, if we have a concept of
Age it wouldn’t make sense to create an instance of age with a negative value.
In Java, we can throw an
IllegalArgumentException or any other custom
Exception that we have created.
Example of a value object
Reduce Primitive Obsession
Primitive Obsession is a code smell that is easy to create. It’s when we use primitives for storing data or as parameters. For instance, we use a string for an email address, a number for a weight or a number for a duration. It usually happens because it seems like too much effort to create a new class just for storing that one attribute. Before you know it, everything you use is now a primitive! 😱
One common mistake when using primitives is using the wrong unit. For instance, let’s say we have the following field:
private float distance;
Perhaps this distance is in inches, or maybe it is in meters, we don’t know. Of course, we could always name it
distanceInMeters. However, this requires developer attention to make sure they’re using the same unit because the compiler will accept anything that is a
float. Whenever you rely on developer attention, a mistake will happen eventually.
Now let’s imagine we need a distance in inches somewhere else in the code. We will need to convert this distance before using it. Where should this conversion code be? In a utility class? A static method? Maybe inline? What if we remove that conversion code before using the distance? The code will still compile so we will be using the wrong unit, whoops! Making such a mistake can be dramatic and can cost a company a lot of money. 💸
We can mitigate this risk by using a
Distance value object instead:
In this case, we would just store a
Distance and no longer have to care what unit it was created in. This class now also contains all the conversion logic and we can easily get the distance in any unit we want thanks to the getters.
Another error that can occur is mixing up parameters. For instance, let’s take the following method:
void sendEmail(String email, String subject, String body);
It’s very easy to mix up the parameters and call it this way:
sendEmail("Some subject", "Some content", "email@example.com");
This seems like a silly mistake to make, but it can happen with a little lack of attention. Unfortunately, the compiler will not help you here.
What happens if you want to be able to send multipart content? Or if you want to send the email to multiple recipients? You need to overload your method for each new case.
By using value objects instead of primitives, we would have the following:
sendEmail(EmailAddress recipient, Subject subject, Content content);
It would be impossible to invert parameters as the compiler will catch the errors. We still need to overload the method if we want to be able to send the email to multiple recipients though. This can be fixed by going one step further:
As you can see, this seems much simpler to read than the first method. It also has another benefit, flexibility.
Continuing off the previous example, now that we have an
Another example would be using a
CustomerId instead of a primitive. We could either use strings or numbers internally and it wouldn’t matter to the outside world. We could also change the internal representation around if we need to use a different type of database with minimal impact on the existing code. This would be much more difficult if we used primitives directly.
The only way for a value object to exist is to be valid. Once that object exists it can no longer change. Thanks to these characteristics of self-validation and immutability, we can stop wondering if we need to validate a parameter or not.
If we reuse the
sendEmail example, we would have to make sure the email address is valid. Where should we do that? In the method itself perhaps? Why not. But what if we need to use an email elsewhere? Also what if someone already validated the email before calling the
sendEmail method? We would be duplicating validation code all over the place! What if someone decides to change the value of the email in the meantime? Then we need to validate it again.
I’m sure you can see the pattern here. There is no sensible place where to put the validation code and we have no guarantee that we are dealing with a valid parameter.
However, with the value object, we are guaranteed that it is valid otherwise it would simply not exist. The validation code, which is defined by the domain, also resides where it should, inside the object itself. Only that object should know if it is valid or not.
Instead of having the same group of attributes in separate classes, we can instead create a shared value object. One example of this would be having an
Address value object that could be shared by
Offices instead of having duplicated address fields in each class. Re-using value objects amongst entities or other value objects is a good way to reduce duplication.
Easier to read
Using value objects, we don’t have to guess what variables truly are. Instead of having a
List<String> we could instead have a
List<PhoneNumber>. We no longer have to worry about internal representations but think about domain concepts instead. The code is a lot more expressive.
Furthermore, value objects centralize related domain logic making it easier to find and change.
Value objects are immutable and can, therefore, be used in parallelized code without risk. If many objects are using the same value object with the same value, we can use the flyweight pattern by sharing the same instance across all objects. Indeed, we only care about their attributes, not which instance we are using.
Because value objects are immutable, they can easily be used in caches or hash maps. Indeed, their hash code will not change over time and are therefore safe to use.
Despite all the benefits listed above, I would advise against wrapping every single primitive with a value object. Creating too many classes can bloat the codebase. In some cases, having to recreate new instances too often can have a performance hit as well.
Immutable objects don’t interface too well with databases in Java. You have to either relax the immutability constraints or use a separate data transfer object and convert from one to the other. It’s not ideal as it adds yet more classes to the mix.
Value objects are a valuable tool to have at our disposable. They allow us to remove some of that primitive obsession in us and their characteristics bring along a flurry of benefits. They are not without drawbacks so we need to be careful to not overuse them. You will need to use your proper judgment. I personally use value objects if there is any validation or domain logic associated with a field or group of fields or if there is any possibility of ambiguity when using primitives instead. I also use them to represent concepts that exist in the domain.
Thanks for reading! 🤓
If you are still looking for some more, here is a list of books I recommend: