The Integrity of Simple Values
Domain Modeling Made Functional — by Scott Wlaschin (57 / 125)
In the earlier discussion on modeling simple values, we saw that they should not be represented by string or int but by domain-focused types such as WidgetCode or UnitQuantity.
But we shouldn’t stop there, because it’s very rare to have an unbounded integer or string in a real-world domain. Almost always, these values are constrained in some way:
- An OrderQuantity might be represented by a signed integer, but it’s very unlikely that the business wants it to be negative, or four billion.
- A CustomerName may be represented by a string, but that doesn’t mean that it should contain tab characters or line feeds.
In our domain, we’ve seen some of these constrained types already. WidgetCode strings had to start with a specific letter, and UnitQuantity had to be between 1 and 1000. Here’s how we’ve defined them so far, with a comment for the constraint.
type WidgetCode = WidgetCode of string // starting with "W" then 4 digits
type UnitQuantity = UnitQuantity of int // between 1 and 1000
type KilogramQuantity = KilogramQuantity of decimal // between 0.05 and 100.00
Rather than having the user of these types read the comments, we want to ensure that values of these types cannot be created unless they satisfy the constraints. Thereafter, because the data is immutable, the inner value never needs to be checked again. You can confidently use a WidgetCode or a UnitQuantity everywhere without ever needing to do any kind of defensive coding.
Sounds great. So how do we ensure that the constraints are enforced?
Answer: The same way we would in any programming language — make the constructor private and have a separate function that creates valid values and rejects invalid values, returning an error instead. In FP communities, this is sometimes called the smart constructor approach. Here’s an example of this approach applied to UnitQuantity:
type UnitQuantity = private UnitQuantity of int
// ^ private constructor
So now a UnitQuantity value can’t be created from outside the containing module due to the private constructor. However, if we…