NashTech Blog

Table of Contents

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

  1. Dependency Rule is preserved.
    Controllers → Mediator → Application layer → Domain.
  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 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.

Picture of Tien Nguyen Ngoc

Tien Nguyen Ngoc

Suggested Article

Scroll to Top