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

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.

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