1. Introduction
In modern backend systems, especially those built with Clean Architecture, the Mediator pattern plays a critical role in enforcing separation of concerns, reducing coupling, and keeping business logic isolated from controllers, infrastructure, and frameworks.
In .NET, this is often implemented using the MediatR library — but the ideas apply to any language or platform.
This article explains the pattern in a deeply technical way, with diagrams, use-cases, anti-patterns, and a full request pipeline breakdown.
2. The Problem the Mediator Pattern Solves
Without a mediator, controllers call services, which call other services, which call repositories, which reference other components.
This leads to:
- Tight coupling between layers
- Difficult unit testing because dependencies multiply
- Implicit flows (logic scattered across multiple services)
- Hard-to-maintain orchestration code inside controllers or services
Controller → ServiceA → ServiceB → RepoA → RepoB
Changes ripple everywhere.
The Mediator pattern fixes this.
3. What the Mediator Pattern Does
Instead of components calling each other directly, they send messages (commands, queries, events) to a Mediator, which routes the message to a single handler.
Controller → Mediator → Handler → Repository
This enforces one action = one handler.
Benefits:
- Predictable flow
- Zero direct dependencies between features
- Perfect for CQRS
- Enables pipeline behaviors (logging, validation, transactions)
- Clean, testable business logic
4. Mediator in Clean Architecture
In Clean Architecture:
- Controllers live in the outer layer
- Use cases live in the application layer
- Business rules must not depend on frameworks
The Mediator becomes the use case invoker.
Folder Structure Example

5. Command vs Query (CQRS)
The Mediator pattern naturally supports CQRS.
Commands:
- Perform an action
- Change state
- Should not return complex objects
Queries:
- Read-only
- Return data
- No side effects
Both go through the mediator.
6. Example: CreateOrder Command
Command
public record CreateOrderCommand(string CustomerId, decimal Amount) : IRequest<Guid>;
Handler
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, Guid>
{
private readonly IOrderRepository _repo;
public CreateOrderHandler(IOrderRepository repo)
{
_repo = repo;
}
public async Task<Guid> Handle(CreateOrderCommand request, CancellationToken ct)
{
var order = new Order(request.CustomerId, request.Amount);
await _repo.Save(order);
return order.Id;
}
}
Controller
[HttpPost]
public async Task<IActionResult> Create(CreateOrderCommand cmd)
{
var id = await _mediator.Send(cmd);
return Ok(id);
}
Notes:
- No service layer required
- No direct dependency on domain or repositories
- Business logic is fully contained in handler
7. Pipeline Behaviors (The Secret Weapon)
Pipeline behaviors allow cross-cutting concerns without polluting handlers.
Examples:
- Validation
- Logging
- Transaction/unit-of-work
- Caching
- Authorization
- Performance tracking
Logging Behavior Example
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
Console.WriteLine($"Handling {typeof(TRequest).Name}");
var response = await next();
Console.WriteLine($"Handled {typeof(TResponse).Name}");
return response;
}
}
Each request passes through the pipeline in order.
8. Why Mediator Fits Clean Architecture Perfectly
- Dependency Rule is preserved.
Controllers → Mediator → Application layer → Domain. - Use cases become explicit.
Every operation is represented by a Command or Query. - Testing becomes trivial.
You test each handler independently. - Easy to add cross-cutting logic.
Pipeline behaviors act like middleware at the use-case level.
9. Common Anti-Patterns (Important)
❌ Putting business logic in controllers
❌ “Service Layer” that duplicates handlers
❌ Handlers calling handlers
❌ God-handlers doing too much
❌ Mixing Commands and Queries
10. Real Use Cases in Large Systems
E-commerce
- PlaceOrder
- CancelOrder
- GetOrderDetails
- GetCustomerOrders
Banking
- TransferFunds
- GetTransactionHistory
Booking Systems
- BookTicket
- CheckAvailability
These systems scale cleaner when business operations are isolated as handlers.
11. Sequence Diagram (Text Version)
Controller → Mediator → CreateOrderHandler
Controller sends request → Mediator resolves handler → Handler processes → Returns OrderId → Mediator returns to Controller.
12. Summary
The Mediator pattern is a core building block for Clean Architecture because it:
- Makes use cases explicit
- Enforces clean boundaries
- Simplifies controllers
- Eliminates service coupling
- Enables powerful pipelines
- Works perfectly with CQRS
- Keeps business logic isolated and testable
If your application is growing, Mediator helps keep it modular, predictable, and scalable.