18 Aug 2026
There is a subtle but powerful difference between calling a service directly and routing a request through a mediator. When your API endpoint knows about your handler, you have coupling. When your endpoint sends a message to a bus and the bus finds the right handler, you have decoupling. The Mediator pattern is the glue that makes CQRS practical in a real application.
In Brand Website V3, API endpoints never reference handlers directly. An endpoint receives a request, adapts it into a command or query, and sends it through ICommandBus or IQueryBus. The bus dispatches to the correct handler based on the generic type parameter. The handler is resolved from DI. The endpoint does not care where the handler lives, what it does, or how it does it.
// Blog.Api/UpsertBlogPost/UpsertBlogPostEndpoint.cs
private static async Task<IResult> UpsertBlogPostAsync(
UpsertBlogPostRequest request, ICommandBus commandBus)
{
var command = request.Adapt<UpsertBlogPostCommand>();
var result = await commandBus.SendAsync(command, CancellationToken.None);
if (result.IsFailure) return Results.Problem(result.Error.Description);
return Results.NoContent();
}
Look at how thin this endpoint is. It adapts the request, sends the command, checks the result, and returns an HTTP response. There is no business logic here. There is no database access. There is not even a using statement for a service. The command bus is injected as a parameter, and the endpoint is done.
if (result.IsFailure) return Results.Problem(result.Error.Description);
return Results.NoContent();
var result = await commandBus.SendAsync(command, CancellationToken.None);
var command = request.Adapt<UpsertBlogPostCommand>();
HttpContext, no routing, no middleware.public UpsertBlogPostCommandHandler(IBlogDbContext dbContext)
The AugusteVN.CQRS bus exposes clean interfaces. ICommandBus for writes, IQueryBus for reads. They accept a request and return a Result or Result<T>. That is the entire contract.
// The bus interfaces
public interface ICommandBus
{
Task<Result> SendAsync(ICommand command, CancellationToken cancellationToken);
}
public interface IQueryBus
{
Task<Result<T>> SendAsync<T>(IQuery<T> query, CancellationToken cancellationToken);
}
This simplicity is the point. The bus is not doing anything magical. It is looking up the right handler in DI and calling HandleAsync. But that indirection buys you loose coupling, testability, and a clean separation between the web layer and the domain layer.
See the Mediator pattern implemented end-to-end in Brand Website V3. Every API endpoint routes through the command/query bus, keeping your web layer thin and your domain logic isolated. Learn more at kiss-code.com.
I continuously build, learn and experiment with innovative technology. Allow me to share what I learn, with you.