You should be using Value Objects 💊
How do you ensure that two given components speak the same language? Do you have invalid values floating around? What do strings and integers tell about your domain?
📝 Java enums are a native example of value objects but they’re not adequate for an arbitrary amount of values. Typical value objects are email, password, locale, telephone number, money, IP address, URL, entity identifier, rating, file path, point, color, date, date range, distance.
Carry a value
The value object is a container and carrier of an arbitrary value:
Usually, a value object holds a single value, but it can hold more. For example, an RGBA color holds 4 integers, a 3D point holds three numbers. This improves API semantics:
Value objects allow passing by value rather than by reference. Conceptually speaking, value objects can be seen as literals like
"John Wick". Value objects are the cure to the primitive obsession anti-pattern; they enclose their real type as an implementation detail; for example, if you’re changing the identifier of the
User entity from integer to string, you wouldn’t have to change entities that use it or declarations like this:
findById(userId: UserIdentifier) // all the calls keep the same
Same value? Always equal
When you take a pill from a bottle, does it matter which one you pick? Two value objects carrying the same value(s) are always equal when compared, even if their references are different.
Email("email@example.com") will always equal
Email("firstname.lastname@example.org") regardless of their references:
Objects that are equal due to the value of their properties […] are called value objects. Value Object, Martin Fowler
Immutability is the most important characteristic of value objects. It's an essential property in functional programming. Value objects are native in languages like Clojure. In other languages, it pays to make the objects immutable, namely to prevent the aliasing bug. Once an
Immutable values can freely be passed around. There is no need to create copies. Even concurrent access is safe without modifications. Testing is simplified to creating different instances via the constructor and then asserting the returned results of properties and methods. Immutable objects also encourage side-effect free functions and those come with their own set of advantages. Value Objects, Florian Benz
No identity and no history
Value objects don’t make sense on their own. They exist in the context of an entity. You might create a new one to run a
findByEmail for example, but other than that, they exist only to serve as entity data.
While entities have a long-lived identity granted by their properties, the value object’s identity is solely given by the piece of data they carry. Since it does not evolve, it means it does not have a life of its own.
Entities live in continuum, so to speak. They have a history (even if we don’t store it) of what happened to them and how they changed during their lifetime. Value objects, at the same time, have a zero lifespan. We create and destroy them with ease […] we don’t store value objects separately. The only way for us to persist a value object is to attach it to an entity. Entity vs Value Object, Vladimir Khorikov
How do you know that you have valid telephones floating around your application? With value objects, you can prevent invalid value objects from existing at all. This is the best way to have a centralized validation rather than validating all over the place (e.g. in multiple handlers). By the way, you should self-validate entities as well.
By using and validating value objects, one does not only catch errors early on but also improves security. It’s also valuable information about the domain. Value Objects, Florian Benz
Can encapsulate logic
There’s nothing wrong with encapsulating a few bits of logic if they belong to the value object:
Another example is when you want to create operators around value objects. Let’s define the
plus of two points:
Primitive types don’t convey any meaningful domain knowledge. Value objects are domain-specific types that enrich your type system, enforcing and promoting domain terminology. Value objects help APIs and entities reveal their intent, acting as code self-documentation. Let’s see some examples.
Customer entity; most of its properties can be value objects:
Compare these signatures:
fun notifyClient(email: String)
fun notifyClient(recipient: Email)
The parameter of the first signature reminds me of the Hungarian notation, which is a bad pattern in a modern language. The second signature focuses on the parameter meaning — therefore its type is superfluous in the name.
Compare these example invocations — the second doesn’t allow any misunderstanding about the order of arguments:
They’re even more important in the functional programming paradigm because you’re mostly passing lambdas around. It also helps readability when resorting to generics:
val articleMappings: Map<String, String>
val articleMappings: Map<ArticleCategory, Region>
They make the domain explicit, e.g. by using
Moneyas a wrapper instead of just two fields of type BigDecimal and String […]. Readers of the code will understand it's about money and maintainers will be unable to pass something not representing money. In addition, the wrapper can contain validations and thus additional domain knowledge and also increase security. Value Objects, Florian Benz
For flexibility reasons, and to avoid weird bugs, you might be more open regarding the input that you’re willing to receive. For example, you could automatically convert
" John.Doe@Gmail.com " to
Normalization is part of parsing, which is an I/O responsibility. However, value objects belong to the domain in the clean architecture. This is why self-normalization is a bonus/optional/it-depends.
⚠️ You may decide not to self-normalize value objects but you need to make sure you self-validate them. Otherwise, you may end up with a database full of repeated emails but with different letter casings.
A value object is an envelope carrying simple immutable data. It’s solely identified by that data whereas an entity has a regular identifier with multiple pieces of data. Both can have associated logic.
A primitive obsession in your app is almost as bad as a user form with text fields for everything or a database full of anything but strings: you lose meaning and allow wrong input. Value objects bring semantics to your APIs and entities. In the context of an app, I see value objects at the same level as primitive types. In some cases, they’ll make the compiler help you to reduce mistakes.
Given the low effort involved and its great benefits, I recommend to recognize value objects and create them from early in the project. You’ll have a warning sign the moment you do any kind of logic with literals (e.g. parsing, splitting, validation, conversion, operations).
Value objects are valuable. Value objects make sense in most languages, object-oriented or not. Time to search for “value objects in [your language]”.