Skip to content


No Primitives

Note: This post is part of the series Lessons (re)learned.

Here's a relatively well known code smell: Primitive Obsession. It is always very tempting — and some claim "quicker" — to use primitives instead of creating new types. When we do this, we're probably missing an opportunity to model a concept of the domain we're working with — or, at least, to raise the abstraction level a little bit.

Writing validation routines is usually a good indication that we might have missed a concept. We'll need to remember to always validate in every place we interact with that particular concept to ensure its correctness. If we have to remember, we're bound to forget.

It is very likely that our domain has rules around certain concepts, for example:

We can always compose them to raise the abstraction level even more and enrich our domain models. For example, an Account model could be raised as SourceAccount and DestinationAccount when dealing with bank account transfers.

Defining types also helps us to address certain types of connascence. Given the following example method:

save(productID string, name string)
// interface: save(string, string)

An honest mistake one can make is to invoke it with the order of the parameters reversed. If we raise the abstration level a bit and do:

save(id ProductID, name ProductName)
// interface: save(ProductID, ProductName)

Now we don't need to depend on knowing the internal variable names nor parameter order. This is especially beneficial on compiled languages or languages with type hints.

This subject also touches on the idea of making illegal states unrepresentable. I can't really recall where I first heard or read about it, but here's a couple of resources:

In DDD lingo, these models are known as Value Objects and are considered the building blocks of our domain. I've also seen these being called Micro Types or Tiny Types.