For the complete documentation index, see llms.txt. This page is also available as Markdown.

Pipeline Validation and Diagnostics

Brighter can validate your pipeline configuration at startup, catching common mistakes before any messages are sent or consumed. It can also log a diagnostic report showing exactly how your pipelines, publications, and subscriptions are wired. Both features are opt-in and configured via extension methods on IBrighterBuilder.

Quick Start

Add .ValidatePipelines() and .DescribePipelines() to your Brighter configuration chain:

using Paramore.Brighter.Extensions.DependencyInjection;

builder.Services.AddBrighter(options =>
    {
        options.HandlerLifetime = ServiceLifetime.Scoped;
    })
    .AutoFromAssemblies([typeof(MyHandler).Assembly])
    .ValidatePipelines()
    .DescribePipelines();

When the application starts:

  • ValidatePipelines checks your configuration against a set of rules. If any errors are found, it throws a PipelineValidationException with all errors listed, preventing the application from starting.

  • DescribePipelines logs a structured report of your configured pipelines to ILogger — a summary at Information level and full detail at Debug level.

These two methods are independent — you can enable one without the other.

What Gets Checked

Validation checks scale automatically to your configuration. If you only call AddBrighter(), only handler pipeline checks run. If you also call AddProducers() or AddConsumers(), the corresponding checks are included automatically.

Handler Pipeline Checks (AddBrighter)

These checks apply to all Brighter applications, including those that only use the command processor without messaging.

Rule
Severity
What It Checks

Handler type visibility

Error

Handler class must be public. Brighter only discovers public handler types — a non-public handler will silently not be found by the pipeline builder.

Sync/async attribute consistency

Error

Async handlers (IHandleRequestsAsync<T>) must use async attributes (e.g. RejectMessageOnErrorAsyncAttribute). Sync handlers must use sync attributes. A mismatch will throw a ConfigurationException at pipeline build time.

Backstop attribute ordering

Warning

Backstop error-handling attributes (RejectMessageOnError, DeferMessageOnError, DontAckOnError) should be at the outermost position (lowest step number). If a backstop has a higher step number than a resilience pipeline attribute, it will never execute on failure.

Example error messages:

Producer Checks (AddProducers)

These checks apply when you configure outgoing messages with AddProducers().

Rule
Severity
What It Checks

Publication RequestType set

Error

Every Publication must have a RequestType set. A null RequestType causes Post() / Deposit() to throw ConfigurationException at runtime.

Publication RequestType implements IRequest

Error

The RequestType must implement IRequest. A type that doesn't implement IRequest will fail at runtime.

Example error messages:

Consumer Checks (AddConsumers)

These checks apply when you configure incoming messages with AddConsumers().

Rule
Severity
What It Checks

Pump/handler type match

Error

A Reactor (sync) subscription must have a sync handler (IHandleRequests<T>). A Proactor (async) subscription must have an async handler (IHandleRequestsAsync<T>).

Handler registered

Error

Every subscription must have at least one handler registered for its RequestType. Without a handler, messages will be received but cannot be dispatched.

RequestType subtype

Warning

The subscription's RequestType should implement ICommand or IEvent, not just IRequest. Implementing only IRequest is unusual and may indicate misconfiguration.

Example error messages:

Diagnostic Report

The DescribePipelines() method logs a structured report showing how your pipelines are wired. This helps you confirm that handlers, mappers, and transforms are resolved as you expect.

Summary (Information Level)

At Information log level, a single summary line is logged:

The summary includes counts only for the configuration paths you use. If you don't call AddProducers(), publications won't appear in the summary.

Full Detail (Debug Level)

At Debug log level, the report shows the full wiring for each configuration path. Here is an example with all three paths configured:

Key things to look for in the report:

  • Pipeline chain: Verify that backstop attributes come first (lowest step number) and the handler is last.

  • Mapper resolution: Check whether a custom or default mapper is used. If you expected a custom mapper but see (default), your mapper may not be registered correctly.

  • Transforms: Verify that outgoing transforms (WrapWith) appear on publications and incoming transforms (UnwrapWith) are configured where expected.

Configuration

Enabling Validation and Diagnostics

Both methods are called on the IBrighterBuilder returned by AddBrighter():

You can enable either one independently:

Controlling Error Behavior

By default, validation errors throw a PipelineValidationException and prevent the application from starting. You can change this to log errors without stopping startup:

When throwOnError is false:

  • Validation errors are logged at LogLevel.Error

  • Validation warnings are logged at LogLevel.Warning

  • The application continues starting

When throwOnError is true (the default):

  • Validation errors throw PipelineValidationException (which extends ConfigurationException)

  • The exception message lists all errors found

  • Validation warnings are still logged at LogLevel.Warning

Warnings never prevent startup regardless of the throwOnError setting.

Conditional Enablement

Both methods accept an enabled parameter that controls whether the feature is registered at all. This lets you gate validation on environment or configuration without removing the method call:

When enabled is false, no services are registered and there is zero overhead at startup.

How Validation Scales to Your Configuration

You don't need to configure which checks to run — validation automatically detects what you've configured:

Configuration
What Gets Validated

AddBrighter() only

Handler pipeline checks

AddBrighter() + AddProducers()

Handler pipeline + producer checks

AddBrighter() + AddConsumers()

Handler pipeline + consumer checks

AddBrighter() + AddProducers() + AddConsumers()

All checks

When AddConsumers() is used, validation is deferred to the ServiceActivatorHostedService so that it runs before the dispatcher starts receiving messages.

Common Mistakes and Fixes

Async Handler with Sync Attributes

An async handler must use async versions of pipeline attributes.

Before (error):

After (fixed):

Backstop After Resilience Pipeline

The backstop attribute should have a lower step number than the resilience pipeline so it wraps the entire pipeline and catches any exceptions.

Before (warning):

After (fixed):

In Brighter, lower step numbers are outer wrappers. The backstop needs to be outermost so it catches exceptions from the resilience pipeline and any handlers inside it.

Reactor Subscription with Async Handler

A Reactor subscription uses synchronous message pumping and requires a sync handler. If your handler is async, use Proactor instead.

Before (error):

After (fixed):

Missing Handler for Subscription

Every subscription needs at least one handler registered for its RequestType. If you use AutoFromAssemblies(), ensure the handler's assembly is included.

Before (error):

After (fixed):

Further Reading

Last updated

Was this helpful?