Primitive Obsession
Primitive Obsession (AKA StringlyTyped) means being obsessed with primitives. It is a Code Smell that degrades the quality of software.
There's a blog post here that describes it more fully; what follows is a summary.
Primitive Obsession is this:
What's wrong with that?
An int
likely cannot fully represent a customer ID. An int
can be negative or zero, but it's unlikely a customer ID can be. So, we have constraints on a customer ID. We can't represent or enforce those constraints on an int
.
So, we need some validation to ensure the constraints of a customer ID are met. Because it's in int
, we can't be sure if it's been checked beforehand, so we need to check it every time we use it. Because it's a primitive, someone might've changed the value, so even if we're 100% sure we've checked it before, it still might need checking again.
So far, we've used as an example, a customer ID of value 42. In C#, it may come as no surprise that this is true: " 42 == 42
" (I haven't checked that in JavaScript!). But in our domain, should 42 always equal 42? Probably not if you're comparing a Supplier ID of 42 to a Customer ID of 42! But primitives won't help you here (remember, 42 == 42
!):
But sometimes, we need to denote that a Value Object isn't valid or has not been set. We don't want anyone outside of the object doing this as it could be used accidentally. It's common to have Unspecified
instances, e.g.
We can do that with Instances, which is covered more in this tutorial. Instances allows new
to be used in the value object itself, which bypasses validation and normalization. More information on validation can be found in this tutorial.
The constructor is private
, so only this type can (deliberately) create invalid instances.
Now, when we use Age
, our validation becomes clearer:
Primitive Obsession can also help introduce bugs into your software. Take, for instance, the following method:
… and a caller calls it like this:
We've messed up the order of the parameters, but our compiler won't tell us. The best we can hope for is a failing unit test. However, given the contrived data often used in unit tests, it could be that the data will hide the problem by using the same ID for customer and supplier.
With Value Objects representing SupplierId
, CustomerId
, and Quantity
, the compiler can tell us if we mess up the order. These types make the domain code more understandable (more domain language and less C# types), and validation is as close to the data as possible; in this example, it likely means that all these types cannot be zero or negative.