Table of Contents

Tutorials

Skim Introduction and Services first. This page focuses on how you write domain and application code so failures surface cleanly on Result without hand-written try/catch around every dispatcher call.

Domain rules with DomainException

Keep invariants inside entities or domain services. When a rule fails, throw a DomainException.

1. Factories for domain exceptions

using CodeBlock.DevKit.Domain.Exceptions;

internal static class ProductDomainExceptions
{
    public static DomainException NameTooLong(int maxLength) =>
        new($"Product name cannot exceed {maxLength} characters.");
}

2. Entity enforcing the rule

public sealed class Product
{
    public const int MaxNameLength = 100;

    public string Name { get; private set; }

    public Product(string name) => Rename(name);

    public void Rename(string name)
    {
        if (name.Length > MaxNameLength)
            throw ProductDomainExceptions.NameTooLong(MaxNameLength);
        Name = name;
    }
}

3. Caller reading errors from the application service

The use case that calls new Product(...) does not catch the exception—the DevKit pipeline does. The Result returned by SendCommand (via your application service) carries the message in Errors:

var result = await _demoThingService.CreateDemoThing(input);

if (!result.IsSuccess)
{
    foreach (var error in result.Errors)
    {
        // Bind to UI, return API problem details, log, etc.
    }
}

The SaaS template follows the same flow for DemoThings: factories in DemoThingDomainExceptions.cs, rules in DemoThing.cs, and IDemoThingService / DemoThingService returning Result<CommandResult>.

Validation and ValidationException

Before a command handler runs, DevKit runs a validation behavior on BaseCommand instances. It combines:

  1. Data annotations on the command (and the same attributes on DTOs you validate similarly).
  2. FluentValidation rules for that command type from DI.

Failed rules are appended to INotificationService, then ValidationException is thrown so the handler does not run. Messages end up in Result.Errors like domain and application failures.

Data annotations — For example CreateDemoThingDto.cs uses [Required] and localized resources. The command CreateDemoThingRequest is decorated so MediatR receives a validated object.

FluentValidation — Inherit AbstractValidator<TCommand> for the same TCommand you send through the dispatcher:

using FluentValidation;

namespace CanBeYours.Application.UseCases.DemoThings.CreateDemoThing;

internal sealed class CreateDemoThingValidation : AbstractValidator<CreateDemoThingRequest>
{
    public CreateDemoThingValidation()
    {
        RuleFor(x => x.Name)
            .NotEqual(x => x.Description)
            .WithMessage("Name and description must not be identical.");
    }
}

See also