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
Application/
Commands/
CreateOrder/
CreateOrderCommand.cs
CreateOrderHandler.cs
Queries/
GetOrder/
GetOrderQuery.cs
GetOrderHandler.cs
Behaviors/
LoggingBehavior.cs
ValidationBehavior.cs
Domain/
Infrastructure/
WebAPI/
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
Each goes through the mediator.
Each goes 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); }
Notice: – 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.
Example behaviors
· Validation
· Logging
· Transaction/unit-of-work
· Caching
· Authorization
· Performance tracking
Example Behavior: Logging
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
1. Dependency Rule is preserved
Controllers → Mediator → Application layer → Domain
Nothing depends inward.
2. Use cases become explicit
Every operation is represented by a Command or Query.
3. Testing becomes trivial
You test each handler independently.
4. Easy to add cross‑cutting logic
Pipeline behaviors act like middleware but at the use case level.
—