Avoid primitive obsession and build your own context

Primitive obsession is a known code smell that describes the usage of primitives for representing domain values. For example:

class User(
val name: String,
val age: Int
) {
init {
require(name.length in (2..25))
require(age in (0..110))
}
}

In this class both the user’s name and age are being represented by a string and an int respectively.

How else could we represent a name and an age?
Well, the primitives are the correct ones but not for direct usage. We can build value objects upon them and encapsulate both the meaning of the value and the business logic:

class User(
val name: Name,
val age: Age
)
class Name private constructor(
val value: String
) {
companion object {
fun of(value: String): Name {
require(value.length in (2..25))
return Name(value)
}
}
}
class Age private constructor(
val value: Int
) {
companion object {
fun of(value: Int): Age {
require(value in (0..110))
return Age(value)
}
}
}

So what? You just wrapped a primitive and moved the check.
True but now we have:

1. A reusable object

When the time comes and we need to have a new named entity we just use the class above and we can be sure that the business logic will follow along and be concise in the entire project.

2. Always valid instances

Whenever we see an instance of name or age we are certain that the instance holds a valid value. This means that an entity that consists from those value objects can only create valid instances and this means that the code that uses those entities (and value objects) does not need to have unnecessary checks. Cleaner code.

3. Code that scales more easily

Lets say that our business logic changes and we need to support users with invalid names too but without the need to deal with the name itself.
Having a value object can help implement the change easily. We just change the Name class. All other code remains the same:

sealed class Name constructor(
val value: String
) {
object UnknownName : Name("")
class KnownName(value: String) : Name(value)
companion object {
fun of(value: String): Name {
return if (value.length in (2..25))
KnownName(value) else
UnknownName
}
}
}
4. Code that is more robust

Lets say that our entities have the notion of an id and that after a few years the underline value needs to change from an integer to a long.
By having a value object to represent the Id all changes will take place in the outer layers of our architecture where we load/fetch ids from databases/network and create the id instances. The rest of project will remain untouched, especially the domain layer that holds our business logic.
If we had chosen the path of having an integer property in every entity then all of our entities, and the code that uses them, would need to change too.

5. Code that is more readable and reveals its usage

I’ve written a couple of posts in the past that showcase both the readability aspect and the revelation one.

6. A blueprint of our domain

When we open our project and see files like Invoice, Price, Quantity, Amount, Currency we get an immediate feel of what this project/package deals with and what are its building blocks.
We consume information that we would otherwise need to dig inside each file to find out.

7. A context

The final and most important part of having value objects is that now we can complete our entities and build a context for our domain. A common language that we can use to communicate with other engineers and stake holders in general.
Primitives are essential and they are the building blocks of a language but not of our business. The rest of company does not build its workflows upon integers and strings. It uses the businesses’ building blocks like age, name etc. It is vital that we do the same too in order to keep our code base in sync with the business.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s