Value Objects Like a Pro
How to build the perfect class for your domain.
Some time ago I was asked this automated question on a programming website:
Which variable type would you use for representing the age of a person?
[ ] Integer
[ ] Boolean
[ ] String
I smiled and left the website.
The correct answer was none of the three. To represent the age of a person, you should use an Age type.
“Is an integer not enough?“ — you may ask. No.
An integer has different properties and operations that age hasn’t.
- Does it make sense to add or subtract two ages? Maybe.
- Does it make sense to multiply or divide two ages. Don’t think so.
- Does it make sense to allow negative ages? Probably not.
The simple truth is, an Age is not an integer and therefore can’t be represented as such.
The bad practice of using primitive types to represent an object in a domain is so common that has even a name: primitive obsession.
But wait, I’m not finished with integers yet.
How do you make sure that an integer variable representing an age is initialized with a valid value? You need to explicitly check before assigning it, in every assignation occurrence in your code.
And how do you make sure that your integer value is not accidentally modified at a later time? You can’t, unless you are using a language that allows for immutable variables and you declare yours as such.
There are here enough points to say that primitives are not good enough for modeling objects in your domain.
You need a different atomic unit of information that satisfies the precision of your domain and its semantical nuances.
Search no more, since this unit already exists. It is called Value Object.
A Value Object represents a typed value in your domain. An Age, for example.
It has three fundamental characteristics: immutability, value equality and self validation.
The bad practice of using primitive types to represent an object in a domain is so common that has even a name: primitive obsession.
Immutability
A Value Object is immutable.
It means you can’t change its internal value(s) after creating it. No setters allowed.
After injecting one or more parameters into the constructor, there’s no way back. That object will remain the same no matter what until it gets disposed by the garbage collector.
Immutability brings two great advantages:
1) Hassle-free Sharing
You can share any Value Object by reference because, being immutable, it won’t be modified in another part of the code.
This dramatically lowers accidental complexity and cognitive load required to avoid introducing any bug.
This is gold especially when your code will run in a multi-threaded environment.
2) Improved Semantics
Combine immutability with another rule: do not add any getter by default to your Value Object.
Restrain — I repeat — restrain from adding any method just because “I may need it later”.
Your initial class should have a constructor and a bunch of private properties only.
This allows you to think about the transformation of your data at a later time. It means you can decide the semantics of your methods when you understand the exact use case(s) for your Value Object.
Doing so will improve your model by avoiding pointless interfaces and defining meaningful names and behavior for your Value Object.
Typically, this is how you manipulate a Value Object:
- you create a new instance via constructor or static method;
- you create another Value Object out of it;
- you extract internal data, converting it to some other type.
Imposed immutability forces you to express these cases in a language relevant to your domain and perfectly clear to any future reader of your code, yourself included.
Let’s see an example.
Value Equality
Imagine you and your friend pick one card each from two different poker decks and you want to understand if you have picked the same card. How do you do that?
What you would probably do is to check whether the two cards show the same number and are of the same suit. In other words, you check whether they have the same properties.
Imagine now that you exchange the two cards. You take the one from your friend, your friend yours. Has anything changed? No. You still have the same card and so your friend. Having the same properties, the two cards are indistinguishable one from the other.
Turns out the two cards are in fact Value Objects.
Two Value Objects with same internal values are considered equal. This means you can test for equality by checking if their internal values are all respectively equal.
Note to Java developers: please do not use that equals and hashCode nonsense.
Self Validation
A Value Object only accepts values which make sense in its context. This means you are not allowed to create a Value Object with invalid values.
A Value Object must check for the consistency of its values when they are injected into the constructor. If one of the values is invalid, a meaningful exception must be thrown.
This means no more ifs around the instantiation of an object. Every formal validation happens at construction time.
This enforced validation is also useful to express domain constraints in a meaningful and explicit way.
Let’s see an example.
Summary
In conclusion, do you want to build a Value Object which is robust, expressive and perfectly fits your domain use case?
This is the list you must always check it against:
- it is immutable and no setters defined;
- it reflects the semantics of the domain;
- it shows how information flows and is transformed during runtime;
- it hasn’t default or useless getter methods;
- it can be compared to other Value Objects of the same class by reading private properties directly.
If you follow this checklist your codebase will never look the same again.
Reading through it will be easier than even. It will feel great.
And all of this simply because of well crafted Value Objects.