Wrapping C# types with actual domain specific knowledge
While implementing the domain for this new feature our team was working on, I felt surprisingly uncomfortable with how we were using C# types to represent texts, two-valued logic, monetary values, etc. If we are going to represent domain specific knowledge, we should also encompass these properties; they have rules that exceed their primitive type’s constraints, after all.
Let’s make the case for a system that should only accept documents that have been registered starting from the year 2000, but can accept documents up to 10 years in advance (from the request). First, if we simply use DateTime, we’ll be delegating the responsibility of validity to some other part of our application, which will cause either a maintenance problem, or a validation problem. We can solve this by wrapping our DateTime in a custom record type:
public sealed record ValidDate
{
public DateTime Value { get; init; }
public ValidDate(DateTime value)
{
var minDate = new DateTime(2000, 1, 1, 0, 0, 0, kind: DateTimeKind.Utc);
var maxDate = DateTime.UtcNow.AddYears(10);
if (value < minDate || value >= maxDate)
{
throw new SomeCustomException("Give some meaningful reason for failing the instantiation");
}
Value = value;
}
}
For a more functional approach you could have a private constructor and a Create
method that returns OneOf<ValidDate, Error>
!