PouryaKit
Concepts

Composition and startup checks

A Host refuses its own mistakes before it serves, when its container is built and when it starts, and each failure names its fix.

What it is

Every declaration refuses its own mistakes while the Host is composed. Then the Host's checks run before it serves:

  • a composition check checks the Host's whole composition when its container is built, before anything runs, such as a Module's declaration made without its Module;
  • a startup check checks what only the built application can answer, such as an endpoint it never mapped, when the Host starts and before any hosted service starts.

They explain most first-run errors. The composition and startup checks report every problem they find in one exception, a line per problem, and each line names its fix.

Read the whole message, and keep the container the Kit built

The message lists every problem the checks found, not only the first. And leave ConfigureContainer out of a Host of Modules: one called after AddPouryaKitModules replaces the container the checks were installed on, and the Host stops when it starts, naming the call.

When it matters

On every build and every start of a Host, in each environment: a Host built and never started runs Operations through its Gateways, so a guarantee the final registrations can answer is checked when the Host is built, not later. You meet them when you add a declaration and forget its other half, such as a permission without its check, or a subscription in a Module with no Unit of Work.

How it works

A Host's checks run at these moments:

  1. While the Host is composed: each declaration throws for its own mistake, such as a Module added twice or a name that is invalid.
  2. When the Host is built: the container is validated in every environment, and a problem it finds makes Build() throw an AggregateException. Then the composition checks run on the final registrations, and Build() throws one InvalidOperationException for every problem they find.
  3. When the Host starts: the startup checks run before any hosted service, and StartAsync throws one InvalidOperationException in the same shape.
  4. Once Wolverine has started: one last check of the queues, the only one at Wolverine's own moment.

AddPouryaKitModules and UsePouryaKitWolverine install the checks. A Host without Modules, such as the monolith template's, calls AddPouryaKitCompositionChecks() itself.

Test a check with a Host of the test's own that makes the mistake, and assert the exception at the moment its check runs. This test builds a Host that handles a command but registers no Unit of Work:

CompositionCheckTests.cs
[Fact]
public void A_features_command_in_a_host_without_a_unit_of_work_fails_the_host_when_built()
{
    var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder([
        "--environment",
        "Production",
    ]);
    builder.Services.AddPouryaKitCqrs(typeof(UnownedCommand).Assembly);
    builder.AddPouryaKitCompositionChecks();

    var thrown = Assert.Throws<InvalidOperationException>(() => builder.Build());

    Assert.Equal(
        $"The Host handles the command {typeof(UnownedCommand).FullName} but registers no Unit of Work, so the command would fail when it runs. Register the Unit of Work of a persistence package, for example with UsePouryaKitPostgres.",
        thrown.Message
    );
}

Every check

Host checks links the sections that list each check, at its moment, with its message.

Failure messages

Next steps

On this page