FlowMediator is a lightweight, opinionated mediator library for .NET 8 and .NET 9.
It focuses on explicit application flow, not generic messaging.
FlowMediator was built around a simple idea:
Not everything is a request.
Commands and queries represent intent.
Events represent facts that already happened.
FlowMediator enforces this distinction explicitly.
| Concept | Description |
|---|---|
| SendAsync | Commands & Queries (single handler, pipeline-enabled) |
| PublishAsync | Events (multiple handlers, side-effect oriented) |
| Pipeline | Applies only to SendAsync |
| Events | Never treated as requests |
dotnet add package FlowMediatorpublic record GetUserByIdQuery(int Id) : IRequest<UserDto>;
public class GetUserByIdQueryHandler
: IRequestHandler<GetUserByIdQuery, UserDto>
{
private readonly IUserRepository _repository;
public GetUserByIdQueryHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<UserDto> Handle(
GetUserByIdQuery request,
CancellationToken cancellationToken)
{
var user = await _repository.GetByIdAsync(request.Id);
return user ?? throw new Exception("User not found");
}
}services.AddFlowMediator(typeof(GetUserByIdQuery).Assembly);
services.AddScoped<IUserRepository, UserRepository>();
services.AddTransient(
typeof(IPipelineBehavior<,>),
typeof(LoggingBehavior<,>)
);var mediator = provider.GetRequiredService<IMediator>();
var user = await mediator.SendAsync(new GetUserByIdQuery(1));
Console.WriteLine(user.Name);public class UserCreatedEvent : IEvent
{
public Guid EventId { get; } = Guid.NewGuid();
public DateTime OccurredOn { get; } = DateTime.UtcNow;
}
await mediator.PublishAsync(new UserCreatedEvent());PublishAsync is not fire-and-forget and is not a message bus.
It runs handlers in-process, sequentially.
If one handler fails:
- execution stops
- remaining handlers are not invoked
- the exception propagates to the caller
This is intentional. FlowMediator is designed for in-process coordination, not reliable message delivery.
For fire-and-forget or guaranteed delivery, use Outbox pattern + a message broker.
public class UserCreatedEventHandler
: IEventHandler<UserCreatedEvent>
{
public Task Handle(
UserCreatedEvent @event,
CancellationToken cancellationToken)
{
Console.WriteLine("User created");
return Task.CompletedTask;
}
}Pipelines apply only to SendAsync.
They are ideal for:
- Logging
- Validation
- Transactions
Events are executed outside the pipeline.
- Clean application flow
- Explicit CQRS
- Domain-driven design
- Predictable execution
- FlowContext (CorrelationId, UserId, Metadata)
- Step-based execution model
- Observability and retry
- Command / Query specialization
FlowMediator focuses on application flow and execution semantics.
It does not provide:
- Authorization or authentication
- Security policies
- Exception handling strategies
- Infrastructure-level concerns
These responsibilities intentionally remain in the application layer.
FlowMediator is designed to be explicit and predictable,
not a full application framework.
Guarantees
SendAsyncis request/response: single handler, pipeline-enabled.PublishAsyncis event notification: multiple handlers, no response.
Non-Goals
- Durable messaging / exactly-once delivery guarantees
- Distributed transactions
- Automatic retries without idempotency guarantees
Contributions, bug reports, and feature requests are welcome.
Licensed under the MIT License.