I received an interesting question about validations and invariants that I thought I would share with you.
1. Preamble
Some time ago, I wrote an article about Always-Valid Domain Model. It’s an important article, so make sure you read it if you haven’t already.
One of the points I made in that article is that validation rules are the same concept as domain invariants.
Just to recap: an invariant is a condition that must be held true at all times. For example, a triangle is a concept that has 3 edges. The edges.Count == 3
condition is inherently true for all triangles.
Another important property of invariants is that they define the domain class: that class is what it is because of them. Therefore, you can’t possibly violate these invariants.
If you do, the domain class would simply cease being the thing you expect it to be; it’d become something else. For example, if you add the 4th edge to a triangle object, that object would become a square, not a triangle.
The presence of invariants is what necessitates the introduction of validation rules. Without invariants such as edges.Count == 3
, you wouldn’t need to validate input data from external clients.
The difference between validations and invariants, then, is just a matter of perspective. The same business rules are viewed as invariants by the domain model, and as validation rules by application services:
This difference leads to different treatment of violations of these business rules:
An invariant violation in the domain model is an exceptional situation and should be met with throwing an exception and stopping the current operation entirely (the fail fast principle).
On the other hand, there’s nothing exceptional in external input being incorrect. This is what your application services are for: they separate (filter) correct and incorrect requests. You shouldn’t throw exceptions in such cases and instead should use a Result
class.
2. The question
So here’s the question I received regarding the difference between validation rules and invariants (paraphrased):
You said invariants are the same as input validation. I think that is incorrect because validation rules can change whereas invariants can’t.
For example, the triangle has 2 invariants:
Having exactly 3 sides,
Each side being bigger than zero.
But let’s say in our domain model we also have another condition: the size of the sides should be bigger than 10 cm.
That validation rule (unlike the 2 invariants) can be changed or removed from our domain model. That’s why I think validation rules are different from invariants.
This is a good question. Indeed, intuitively, these two conditions just don’t feel the same: having 3 edges and requiring all edges to be bigger than 10 cm. One is essential for triangles, the other one clearly isn’t.
But that’s just because we are bringing our real-world experience into the realm of domain modeling.
Let’s step back for a second and discuss what domain modeling is all about. What is the purpose of modeling a problem domain (i.e building a domain model)?
Is it to build an as close approximation to the physical world as possible? Or make the model as realistic as possible?
It is not.
The purpose of domain modeling is to build a model that is useful for our particular problem space. Not for all possible problem spaces and definitely not for some generic problem in a vacuum. For our specific one.
Therefore, if our application needs all triangles to have bigger than 10 cm edges, if working with triangles of smaller sizes doesn’t help us achieve our goals, then those smaller triangles might just as well not exist for the purposes of our application.
In other words, if a concept isn’t useful for the model, you shouldn’t include it into that model at all.
Yes, triangles with smaller than 10 cm edges may exist in other domains, but in our particular one, they don’t. Same for squares, pentagons, and other figures (assuming that our application only works with triangles). Hence, such triangles aren’t really triangles from our model’s perspective.
Therefore, there’s no difference between these 2 conditions: having exactly 3 edges and all edges being bigger than 10 cm. They both are invariants that make up the concept of a triangle in our particular application.
Of course, requirements may change and the 10 cm condition may transform into 5 or 20 (or even become configurable), but that’s a regular process of refinement where you get better insight into the problem space as the project progresses.
It doesn’t mean the original 10 cm condition wasn’t an invariant. It was. Just as the new condition now.
For more information about how to handle such a change of invariants, check out this article: How to Strengthen Requirements for Pre-existing Data
--Vlad
https://enterprisecraftsmanship.com
Enjoy this message? Here are more things you might like:
Workshops — I offer a 2-day workshop for organizations on Domain-Driven Design and Unit Testing. Reply to this email to discuss.
Unit Testing Principles, Patterns and Practices — A book for people who already have some experience with unit testing and want to bring their skills to the next level.
Learn more »
My Pluralsight courses — The topics include Unit Testing, Domain-Driven Design, and more.
Learn more »