Mediator Pattern in Clean Architecture

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top