Start here

Core concepts

Messages, handlers, buses, behaviors — the five ideas everything else in Relay builds on.

Five concepts carry the whole framework. Everything else — event sourcing, outbox, sagas, tenancy — is built out of these.

Message

A small immutable record that describes one operation. ICommand / ICommand<TResponse> for state changes, IQuery<TResponse> for reads. Messages carry only data; behavior lives in handlers. Domain events (DomainEvent) and integration events are messages too — facts, rather than requests.

Handler

The single class that owns one message’s behavior: ICommandHandler<TCommand, TResponse> or IQueryHandler<TQuery, TResponse>, with exactly one Handle method. Handlers are resolved from DI. One message, one handler — the RelayHandlerAnalyzer turns a missing handler (RELAY001) or a duplicate (RELAY002) into a compile-time diagnostic.

Bus

ICommandBus and IQueryBus are the typed entry points: Execute<TMessage, TResponse>(message, ct). Inject them anywhere — or skip the ceremony entirely and let [RelayHttpPost] + MapRelayEndpoints() generate the HTTP surface from your messages. IDomainEventBus and IIntegrationEventBus carry events, in-process and across services respectively.

Pipeline behavior

Cross-cutting concerns are classes that wrap handler execution, receiving the message and a next delegate. Authorization, validation, logging and the transaction are all just behaviors, ordered by priority, applied uniformly to every message. Add your own — rate limiting, audit, feature flags — once, and every current and future handler gets it.

Registration by scanning

AddRelay(assembly) discovers handlers, validators and behaviors at startup. There is no per-handler registration, and there are startup-time guarantees: notably, fail-closed authorization — a message with no declared authorization posture prevents the host from starting rather than shipping an open endpoint.

How a request flows

sequenceDiagram
    participant HTTP as HTTP endpoint
    participant Bus as ICommandBus
    participant Pipe as Pipeline
    participant H as Handler
    HTTP->>Bus: Execute(CreateProductCommand)
    Bus->>Pipe: authorization → validation → logging → transaction
    Pipe->>H: Handle(command)
    H-->>Pipe: Product
    Pipe-->>HTTP: 201 Created (ProblemDetails on failure)

Read the full treatment in article 001 — Getting Started, then meet the domain side in article 002 — Your First Aggregate.