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.