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

Leave a Comment

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

Suggested Article

Scroll to Top