Given our requirement of supporting arbitrary metadata on our validation results, how should we modify the semantic types we’ve already created?

I’m going to follow the example of the Exception type, which includes the oft-overlooked Data property. This is a dictionary of named values, allowing an arbitrary set of additional properties to be included with the exception.

We could simply add a read/write dictionary to our ValidationResult base class, but this runs into some messy problems.

  • At this point our validation semantic types are immutable classes. The addition of a mutable metadata dictionary would be a somewhat odd choice. It seems desirable to choose a design that retains immutability.

  • Two of our ValidationResult implementations currently have transient semantics. One success is much like any other, and combining two aggregates results in a single combined aggregate. Retaining these characteristics is also desirable.

  • Dictionary use often ends up with lots of magic strings, leading to brittle code that fails in non-obvious ways. Finding a way that avoids this brittleness while still allowing easy consumption would be good.

What sort of API would we like our consumers to experience?

As a minimum, we want to allow one or more name-value pairs to be easily included. Reusing one of our examples from the start of our series, we could use method chaining as an approach to add metadata:

private ValidationResult ValidateFullName()
    => Validation.ErrorWhen(
        string.IsNullOrEmpty(FullName),
        "Mandatory property 'FullName' not supplied.")
        .With("Property", nameof(FullName))
        .With("ErrorType", "Mandatory");

The With() method follows the usual pattern for immutable types, returning a new instance with the requested modification.

We still have magic strings in this API, but we can resolve that by adding some supporting constants and extension methods:

private ValidationResult ValidateFullName()
    => Validation.ErrorWhen(
        string.IsNullOrEmpty(FullName),
        "Mandatory property 'FullName' not supplied.")
        .WithProperty(nameof(FullName))
        .WithErrorType("Mandatory");

While this isn’t too bad to read, it does end up being quite repetitive. The .WithProperty(nameof(FullName)) ends up being repeated for every validation check we do for the FullName property. Similarly, .WithErrorType("Mandatory") gets repeated for every validation check we do for a mandatory field.

What if we could extract those metadata values out as member variables that we reused wherever needed by passing them in with our validation checks?

private static ValidationMetadata _fullNameProperty = ...;
private static ValidationMetadata _mandatoryProperty = ...;

private ValidationResult ValidateFullName()
    => Validation.ErrorWhen(
        string.IsNullOrEmpty(FullName),
        "Mandatory property 'FullName' not supplied.",
        _fullNameProperty,
        _mandatoryProperty);

What we’ve done is to define reusable metadata values that we can mix in with our validation results as required. This simplifies the common case, while still allowing for custom metadata where that makes sense.

Next time, we’ll look at what’s needed to make this API work.

Prior post in this series:
Validation Metadata
Next post in this series:
Capturing Validation Metadata

Comments

blog comments powered by Disqus