Validate a command or query
Check a command's or query's input in a FluentValidation validator beside it, and get every failure back as Errors before the handler runs.
What you build
One validator per command or query that needs input checks: an internal FluentValidation AbstractValidator<T> beside it in the Feature. Every failure of every validator comes back as one failed Result before the handler runs, each an Error of kind validation with its code and the property it names as its target. Nothing throws.
Shape here, business rules in the aggregate
A validator checks the shape of the input: present, long enough, in range. A rule that depends on state, such as a name already in use or a sale that has ended, belongs in the aggregate, through CheckRule, or in a unique index. See Business rules.
When to use it
For any command or query whose input can be wrong. A command or query with no validator has nothing checked in that step.
Steps
Write the validator beside the command
Check each member the handler relies on. A typed ID's constructor throws on an empty Guid, so check an id before the handler wraps it.
internal sealed class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(command => command.ProductId).NotEmpty();
RuleFor(command => command.CategoryId).NotEmpty();
RuleFor(command => command.Name).NotEmpty().MaximumLength(200);
}
}Give a failure your own code where a caller needs one
FluentValidation's error code becomes the Error's code. Set your own with WithErrorCode; Validation names the code a failure without one gets.
Validate a Page request the same way
A query that reads a Page checks its request against its declaration with AllowedBy, in the same validator step:
internal sealed class ListProductsQueryValidator : AbstractValidator<ListProductsQuery>
{
public ListProductsQueryValidator() =>
RuleFor(query => query.Page)
.Cascade(CascadeMode.Stop)
.NotNull()
.AllowedBy(ListProductsQuery.Paging);
}Register the Module's validators
The Module's Composition Root calls AddPouryaKitValidation(), which finds every concrete validator in the Module's assembly. An abstract base validator is skipped on purpose, and so is an open generic one: derive a concrete validator from it.
What the Kit guarantees
| Guarantee | Stated in |
|---|---|
| Every failure as one failed Result, with its code and target | Validation |
| A validator only reads | Code that only reads |
| What a failed validation answers over HTTP | Problem responses |