TL;DR: Domain Primitives are a stand-alone low-risk way to incrementally fortify your application, without the risk and complexity of introducing concepts like DDD, algebraic data types or immutability.
I like type safety and using the type system to enforce application rules, because it offloads work and mental capacity from the developer to the compiler. I like domain primitives because they are one of the simplest ways of doing just that: making the compiler work for you.
Domain primitives are often explained within the context of Domain-Driven Development (DDD), entities and value objects. But they are actually useful all on their own. I will not explain them at length here, because others have done that well here or here and of course in the book “Secure by Design” which I regrettably have not read yet as of November 2020. Rather I will write down my perspective on their benefits. In later posts, I will develop some coding patterns and helpers to conveniently work with them in C#.
Within this post I understand domain primitives to be types that
- encapsulate the simplest domain concepts, mostly values
- are named in domain language
- enforce validity at creation time, i. e. if an instance of a domain primitive exists, it is valid
They are an antidote to primitive obsession and its negative consequences. If the domain contains the concept “registry number”, say a 9 character alpha-numeric string, you do NOT use string
as the type of registry number variables and arguments, but you create a type RegistryNumber
that enforces the length of 9 and the character set of [0-9, A-Z] at creation time. You then use the RegistryNumber
type everywhere a “registry number” occurs within your domain code.
This will drive argument checking out of your domain code and into the adjacent layers:
The domain method signatures demand a RegistryNumber
. The caller is forced by the compiler to provide one. If the caller is the UI layer, it needs to create a RegistryNumber
from whatever user input it has. If that fails, the domain method cannot even be called. It is then the UI layer’s responsibility to deal with this case of invalid input. Since the existence of an instance of RegistryNumber
alone means that it is valid, the domain code does not need to check its validity again.
This makes the code self-documenting and forces developers to validate inputs and handle the error cases of invalid data.
Domain primitives make the compiler do half the work for you. They enable you to not only enforce but keep information about the value’s co-domain. A registry number as per the domain is not any old string and using RegistryNumber
lets you keep track of that throughout your application.
Because domain primitives are only concerned with the validity of single values (and not e. g. consistency rules) they are non-intrusive:
- they are independent of the application architecture as a whole; layered, onion, big ball of mud, it does not matter
- they can be easily used with frameworks and tools used outside the domain, such as ORMs, validators, mappers, converters, serializers, view models, bindings
- they can be used from the get-go or introduced later, one at a time, partially or in full
They can even simplify things whenever frameworks and libraries scope something by type, e. g. display templates in ASP.NET MVC or data templates in WPF.
I can think but of one precondition on using them easily. It is a precondition in general for the approach of using the type system to enforce domain rules: the definition of validity must be stable over time. By that I do not mean that the validity rules must not change at all. They can and will change as domain knowledge is accumulated. But they must be independent of the data to which they are applied.
In one of my previous projects, for example, the precondition was not met. Almost all domain rules depended on an external specification subject to (breaking) changes potentially every couple of months. Data from a year ago would be subject to different rules than current data, including some co-domains of values. Under such circumstances, it may not be beneficial to model the rules via the type system.
For most applications, though, domain primitives are a great way to write secure, self-documenting code.
As great as domain primitives are, they do not come for free, at least not in C# (they are almost free in a lot of functional languages through algebraic data types and immutability, though). Throughout the next posts I will develop code and design patterns to make working with domain primitives in C# as easy as possible, including their usage with an ORM for persistence.
Pingback: Gotcha with C# 9 Record inheritance and ToString() – Sven Hübner IT
Pingback: Domain Primitives I: easily declaring domain primitives – Sven Hübner IT