Showing posts with label Mediator Design Pattern. Show all posts
Showing posts with label Mediator Design Pattern. Show all posts

Wednesday, July 29, 2026

Mediator Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 4.5 – Mediator Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐⭐☆ Intermediate to Advanced
Prerequisites: C#, OOP, Interfaces, Dependency Injection, SOLID Principles, ASP.NET Core Web API, LINQ


Table of Contents

  1. Introduction

  2. What is the Mediator Design Pattern?

  3. Why Do We Need the Mediator Pattern?

  4. The Problem with Direct Object Communication

  5. Mediator Pattern Solution

  6. Mediator vs Direct Communication

  7. Mediator Pattern Structure

  8. UML Class Diagram

  9. Components of the Mediator Pattern

  10. Complete C# Console Application

  11. ASP.NET Core Implementation

  12. Dependency Injection with Mediator

  13. Request/Response Communication

  14. Command and Query Handlers

  15. Mediator and CQRS

  16. Banking Example

  17. E-Commerce Example

  18. Pipeline Behaviors

  19. Validation

  20. Logging

  21. Authorization

  22. Transaction Handling

  23. MediatR-Style Architecture

  24. Real-World Enterprise Scenarios

  25. Advantages

  26. Disadvantages

  27. Best Practices

  28. Common Mistakes

  29. Mediator vs Observer

  30. Mediator vs Facade

  31. Mediator vs Command

  32. Interview Questions

  33. Key Takeaways

  34. Conclusion

  35. Coming Up Next


1. Introduction

In a small application, objects can communicate directly with each other.

For example:

OrderService
     ↓
PaymentService
     ↓
InventoryService
     ↓
NotificationService

At first, this seems straightforward.

But as the application grows, the number of relationships between objects can increase dramatically.

A large enterprise application might contain:

  • Order Service

  • Payment Service

  • Inventory Service

  • Customer Service

  • Notification Service

  • Shipping Service

  • Audit Service

  • Fraud Detection Service

  • Reporting Service

If every service communicates directly with every other service, the application can become difficult to maintain.

The Mediator Design Pattern provides a solution.

Instead of:

Object A → Object B
Object A → Object C
Object B → Object D
Object C → Object D

we can introduce a central communication component:

              Mediator
             /   |   \
            /    |    \
           ↓     ↓     ↓
        Object Object Object

The objects communicate through the mediator rather than directly with one another.


2. What is the Mediator Design Pattern?

Definition

The Mediator Design Pattern is a behavioral design pattern that defines an object responsible for encapsulating how a group of objects interact.

In simple terms:

Mediator centralizes communication between objects so that they do not need to know about each other directly.

Instead of this:

CustomerService → OrderService
OrderService → PaymentService
PaymentService → NotificationService

we can use:

CustomerService
      ↓
    Mediator
      ↓
OrderService
PaymentService
NotificationService

This reduces direct dependencies between participating objects.


3. Why Do We Need the Mediator Pattern?

Imagine an e-commerce checkout process.

We have:

Order
Payment
Inventory
Shipping
Notification
Audit

Without a mediator, the checkout service could become tightly coupled:

CheckoutService
   |
   +---- PaymentService
   |
   +---- InventoryService
   |
   +---- ShippingService
   |
   +---- NotificationService
   |
   +---- AuditService

As more functionality is introduced, the service becomes increasingly complex.

For example:

CheckoutService
      |
      +-- PaymentService
      +-- InventoryService
      +-- ShippingService
      +-- NotificationService
      +-- AuditService
      +-- FraudService
      +-- LoyaltyService

This violates the spirit of several SOLID principles, particularly the Single Responsibility Principle and dependency-management goals.

The Mediator Pattern helps centralize the coordination.


4. The Problem with Direct Object Communication

Consider:

public class OrderService
{
    private readonly PaymentService _paymentService;
    private readonly InventoryService _inventoryService;
    private readonly NotificationService _notificationService;

    public OrderService(
        PaymentService paymentService,
        InventoryService inventoryService,
        NotificationService notificationService)
    {
        _paymentService = paymentService;
        _inventoryService = inventoryService;
        _notificationService = notificationService;
    }
}

This class directly knows about several other services.

Now imagine that each of those services also knows about several others.

We can eventually get:

        A
      / | \
     B  C  D
    / \ | / \
   E   F G   H

This is sometimes called communication complexity or dependency explosion.

The more objects know about one another, the harder it becomes to:

  • Test

  • Modify

  • Reuse

  • Understand

  • Maintain


5. Mediator Pattern Solution

The Mediator Pattern introduces a central object.

                  +----------------+
                  |    Mediator    |
                  +-------+--------+
                          |
        +-----------------+----------------+
        |                 |                |
        ↓                 ↓                ↓
   OrderService    PaymentService   InventoryService

Now the participating components don't need direct knowledge of each other.

The mediator coordinates communication.


6. Mediator vs Direct Communication

Without Mediator

OrderService
    ↓
PaymentService

OrderService
    ↓
InventoryService

OrderService
    ↓
NotificationService

The order service has multiple dependencies.


With Mediator

OrderService
      ↓
   Mediator
   /   |   \
  ↓    ↓    ↓
Payment Inventory Notification

The communication becomes centralized.


7. Mediator Pattern Structure

The classic pattern consists of:

Client
   ↓
Mediator
   ↓
Colleagues

The participating objects are commonly called Colleagues.

For example:

                 Mediator
                    |
        +-----------+-----------+
        |           |           |
        ↓           ↓           ↓
    Colleague   Colleague   Colleague

8. UML Class Diagram

A traditional UML representation looks like this:

                  +----------------------+
                  |   <<interface>>      |
                  |       Mediator       |
                  +----------------------+
                  | + Notify(...)        |
                  +----------^-----------+
                             |
                             |
                  +----------------------+
                  |  ConcreteMediator    |
                  +----------------------+
                  | - colleagueA         |
                  | - colleagueB         |
                  +----------------------+
                  | + Notify(...)        |
                  +----+------------+----+
                       |            |
                       ↓            ↓
              +------------+   +------------+
              | ColleagueA |   | ColleagueB |
              +------------+   +------------+
              | - mediator |   | - mediator |
              +------------+   +------------+

The mediator knows the participating colleagues.

The colleagues know the mediator.

They don't need to know each other directly.


9. Components of the Mediator Pattern

1. Mediator

Defines communication between components.

Example:

public interface IMediator
{
    void Notify(object sender, string eventName);
}

2. Concrete Mediator

Implements the coordination logic.

Example:

public class ConcreteMediator : IMediator
{
    // Coordination logic
}

3. Colleagues

Objects that communicate through the mediator.

Examples:

OrderService
PaymentService
InventoryService
NotificationService

4. Client

Creates or uses the mediator and participating objects.


10. Complete C# Console Application

Let's create a simple banking example.

Suppose we have:

Account
Payment
Notification

Instead of allowing these classes to communicate directly, we'll use a mediator.


Step 1 – Mediator Interface

public interface IBankMediator
{
    void Notify(object sender, string eventName);
}

11. Concrete Mediator

public class BankMediator : IBankMediator
{
    public AccountService? AccountService { get; set; }

    public NotificationService? NotificationService { get; set; }

    public void Notify(object sender, string eventName)
    {
        if (eventName == "WithdrawalCompleted")
        {
            NotificationService?.SendNotification(
                "Withdrawal completed successfully.");
        }

        if (eventName == "DepositCompleted")
        {
            NotificationService?.SendNotification(
                "Deposit completed successfully.");
        }
    }
}

The mediator decides what should happen when an event occurs.


12. Account Service

public class AccountService
{
    private readonly IBankMediator _mediator;

    public AccountService(IBankMediator mediator)
    {
        _mediator = mediator;
    }

    public void Withdraw(decimal amount)
    {
        Console.WriteLine(
            $"Withdrawal of {amount:C} completed.");

        _mediator.Notify(
            this,
            "WithdrawalCompleted");
    }

    public void Deposit(decimal amount)
    {
        Console.WriteLine(
            $"Deposit of {amount:C} completed.");

        _mediator.Notify(
            this,
            "DepositCompleted");
    }
}

Notice something important.

AccountService doesn't directly depend on:

NotificationService

It only knows:

IBankMediator

13. Notification Service

public class NotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine(
            $"Notification: {message}");
    }
}

14. Client Code

IBankMediator mediator = new BankMediator();

var bankMediator = (BankMediator)mediator;

var notificationService =
    new NotificationService();

var accountService =
    new AccountService(mediator);

bankMediator.AccountService = accountService;

bankMediator.NotificationService =
    notificationService;

accountService.Deposit(1000);

accountService.Withdraw(250);

Output:

Deposit of $1,000.00 completed.
Notification: Deposit completed successfully.

Withdrawal of $250.00 completed.
Notification: Withdrawal completed successfully.

The communication flow is:

AccountService
      ↓
IBankMediator
      ↓
BankMediator
      ↓
NotificationService

15. Improving the Example

The previous example demonstrates the basic pattern.

However, production applications should avoid unnecessary casting and mutable mediator properties.

A cleaner approach is to inject dependencies into the concrete mediator.

For example:

public class BankMediator : IBankMediator
{
    private readonly NotificationService _notificationService;

    public BankMediator(
        NotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void Notify(
        object sender,
        string eventName)
    {
        switch (eventName)
        {
            case "WithdrawalCompleted":
                _notificationService.SendNotification(
                    "Withdrawal completed successfully.");
                break;

            case "DepositCompleted":
                _notificationService.SendNotification(
                    "Deposit completed successfully.");
                break;
        }
    }
}

This version is easier to test and works better with dependency injection.


16. ASP.NET Core Implementation

Now let's implement a more practical version.

Suppose we have an order-processing API.

We want to coordinate:

Order
Payment
Inventory
Notification

17. Project Structure

A possible architecture:

MyShop
│
├── Controllers
│   └── OrdersController.cs
│
├── Mediator
│   ├── IOrderMediator.cs
│   └── OrderMediator.cs
│
├── Services
│   ├── OrderService.cs
│   ├── PaymentService.cs
│   ├── InventoryService.cs
│   └── NotificationService.cs
│
├── Models
│   └── Order.cs
│
└── Program.cs

18. Order Model

public class Order
{
    public int Id { get; set; }

    public int CustomerId { get; set; }

    public decimal Amount { get; set; }
}

19. Mediator Interface

public interface IOrderMediator
{
    Task<bool> PlaceOrderAsync(Order order);
}

20. Payment Service

public interface IPaymentService
{
    Task<bool> ProcessPaymentAsync(
        decimal amount);
}

Implementation:

public class PaymentService : IPaymentService
{
    public Task<bool> ProcessPaymentAsync(
        decimal amount)
    {
        Console.WriteLine(
            $"Payment processed: {amount:C}");

        return Task.FromResult(true);
    }
}

21. Inventory Service

public interface IInventoryService
{
    Task<bool> ReserveInventoryAsync(
        int orderId);
}

Implementation:

public class InventoryService : IInventoryService
{
    public Task<bool> ReserveInventoryAsync(
        int orderId)
    {
        Console.WriteLine(
            $"Inventory reserved for order {orderId}");

        return Task.FromResult(true);
    }
}

22. Notification Service

public interface INotificationService
{
    Task SendOrderConfirmationAsync(
        int orderId);
}

Implementation:

public class NotificationService :
    INotificationService
{
    public Task SendOrderConfirmationAsync(
        int orderId)
    {
        Console.WriteLine(
            $"Confirmation sent for order {orderId}");

        return Task.CompletedTask;
    }
}

23. Concrete Mediator

public class OrderMediator : IOrderMediator
{
    private readonly IPaymentService _paymentService;
    private readonly IInventoryService _inventoryService;
    private readonly INotificationService _notificationService;

    public OrderMediator(
        IPaymentService paymentService,
        IInventoryService inventoryService,
        INotificationService notificationService)
    {
        _paymentService = paymentService;
        _inventoryService = inventoryService;
        _notificationService = notificationService;
    }

    public async Task<bool> PlaceOrderAsync(
        Order order)
    {
        var paymentSuccessful =
            await _paymentService
                .ProcessPaymentAsync(order.Amount);

        if (!paymentSuccessful)
        {
            return false;
        }

        var inventoryReserved =
            await _inventoryService
                .ReserveInventoryAsync(order.Id);

        if (!inventoryReserved)
        {
            return false;
        }

        await _notificationService
            .SendOrderConfirmationAsync(order.Id);

        return true;
    }
}

The mediator coordinates the workflow.


24. Orders Controller

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly IOrderMediator _mediator;

    public OrdersController(
        IOrderMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> PlaceOrder(
        Order order)
    {
        var result =
            await _mediator.PlaceOrderAsync(order);

        if (!result)
        {
            return BadRequest(
                "Unable to place order.");
        }

        return Ok(
            new
            {
                Message = "Order placed successfully."
            });
    }
}

The controller now has a very simple responsibility.

HTTP Request
     ↓
Controller
     ↓
Mediator
     ↓
Payment
Inventory
Notification

25. Register Services with Dependency Injection

In Program.cs:

builder.Services.AddScoped<IOrderMediator,
                           OrderMediator>();

builder.Services.AddScoped<IPaymentService,
                           PaymentService>();

builder.Services.AddScoped<IInventoryService,
                           InventoryService>();

builder.Services.AddScoped<INotificationService,
                           NotificationService>();

This allows ASP.NET Core's built-in dependency injection container to construct the object graph.


26. Request/Response Communication

A common modern implementation of the Mediator Pattern uses a request and handler.

For example:

PlaceOrderRequest
       ↓
PlaceOrderHandler
       ↓
Order Processing
       ↓
PlaceOrderResponse

Conceptually:

Controller
    ↓
Send(Request)
    ↓
Mediator
    ↓
Handler
    ↓
Service / Repository
    ↓
Response

This is extremely common in CQRS-style architectures.


27. Command and Handler Architecture

Consider:

public record CreateOrderCommand(
    int CustomerId,
    decimal Amount);

A handler processes the command:

public class CreateOrderHandler
{
    public async Task<int> Handle(
        CreateOrderCommand command)
    {
        // Create order
        // Save to database
        // Return ID

        return 1001;
    }
}

The controller does not need to know how the command is processed.

It simply sends the request to the appropriate handler.


28. Mediator and CQRS

Mediator and CQRS are different concepts, but they work extremely well together.

CQRS

CQRS stands for:

Command Query Responsibility Segregation

It separates:

Commands

from:

Queries

Commands change state.

Queries retrieve data.

For example:

CreateOrderCommand
UpdateCustomerCommand
CancelOrderCommand

and:

GetOrderByIdQuery
GetCustomerQuery
GetOrderHistoryQuery

Mediator can route each request to the appropriate handler.


29. CQRS + Mediator Architecture

A typical architecture:

                    Controller
                        |
                        ↓
                    Mediator
                        |
             +----------+----------+
             |                     |
             ↓                     ↓
         Command                 Query
             |                     |
             ↓                     ↓
       CommandHandler         QueryHandler
             |                     |
             ↓                     ↓
       Write Database         Read Database

This provides a clean separation between application requests.


30. Example: Command

public record CreateOrderCommand(
    int CustomerId,
    decimal Amount);

Handler:

public class CreateOrderHandler
{
    public async Task<int> Handle(
        CreateOrderCommand command)
    {
        Console.WriteLine(
            $"Creating order for customer {command.CustomerId}");

        // Save order

        return 1001;
    }
}

31. Example: Query

public record GetOrderQuery(int OrderId);

Handler:

public class GetOrderHandler
{
    public async Task<Order?> Handle(
        GetOrderQuery query)
    {
        // Read order from database

        return new Order
        {
            Id = query.OrderId
        };
    }
}

The application now separates:

Commands → Change State
Queries  → Read State

32. Pipeline Behaviors

One of the most powerful ideas in mediator-based architecture is the pipeline.

A request can flow through multiple behaviors:

Request
   ↓
Logging
   ↓
Validation
   ↓
Authorization
   ↓
Transaction
   ↓
Handler
   ↓
Response

This is similar to ASP.NET Core middleware.


33. Logging Pipeline

Instead of adding logging manually to every handler:

Console.WriteLine("Starting handler");

we can centralize logging.

Conceptually:

Request
   ↓
Logging Behavior
   ↓
Handler

This provides consistent logging.


34. Validation Pipeline

A validation behavior can inspect requests before the handler runs.

CreateOrderCommand
       ↓
Validation
       ↓
Valid?
  /       \
No         Yes
↓           ↓
Error     Handler

For example:

Amount > 0
CustomerId > 0
Order items exist

If validation fails, the handler doesn't need to execute.


35. Authorization Pipeline

Authorization can also be performed before the handler.

Request
   ↓
Authentication
   ↓
Authorization
   ↓
Handler

For example:

User
 ↓
Has Permission?
 ↓
Yes → Handler
No  → Access Denied

This keeps authorization-related concerns out of business handlers where practical.


36. Transaction Pipeline

For commands that modify multiple records:

Request
   ↓
Begin Transaction
   ↓
Handler
   ↓
Save Changes
   ↓
Commit

If something fails:

Exception
   ↓
Rollback

This can be implemented as a pipeline behavior where the transaction boundary is appropriate.


37. MediatR-Style Architecture

A popular .NET approach has historically been the MediatR library, which follows mediator-style request/handler concepts.

A typical structure looks like:

Application
│
├── Commands
│   └── CreateOrder
│       ├── CreateOrderCommand
│       └── CreateOrderHandler
│
├── Queries
│   └── GetOrder
│       ├── GetOrderQuery
│       └── GetOrderHandler
│
└── Behaviors
    ├── LoggingBehavior
    ├── ValidationBehavior
    └── TransactionBehavior

The important architectural lesson is not the library itself.

The important concept is:

One request can be routed to one focused handler, while cross-cutting concerns can be handled in a pipeline.


38. Banking Example

Consider a banking application.

We may have:

TransferMoneyCommand
DepositMoneyCommand
WithdrawMoneyCommand
GetAccountBalanceQuery
GetTransactionHistoryQuery

A mediator can route each request.

                 Mediator
                    |
      +-------------+-------------+
      |             |             |
      ↓             ↓             ↓
TransferHandler DepositHandler WithdrawHandler
      |
      ↓
Account / Transaction Services

For a money transfer:

Transfer Request
      ↓
Validation
      ↓
Authorization
      ↓
Transaction
      ↓
Debit Account
      ↓
Credit Account
      ↓
Audit
      ↓
Commit

The controller remains simple.


39. E-Commerce Example

Suppose a customer places an order.

CreateOrderCommand
       ↓
Mediator
       ↓
CreateOrderHandler
       |
       +── Validate Customer
       |
       +── Check Inventory
       |
       +── Calculate Total
       |
       +── Process Payment
       |
       +── Create Order
       |
       +── Publish Event

Each responsibility can be delegated to appropriate application/domain services.

The handler coordinates the use case rather than becoming a giant business object itself.


40. Real-World Enterprise Scenarios

Mediator-style architecture can be useful for:

Banking

  • Fund transfers

  • Account operations

  • Loan applications

  • Payment processing

E-Commerce

  • Create order

  • Cancel order

  • Process payment

  • Update inventory

Healthcare

  • Patient registration

  • Appointment scheduling

  • Claims processing

Insurance

  • Claim submission

  • Policy updates

  • Premium calculation

Logistics

  • Shipment creation

  • Delivery scheduling

  • Tracking updates

HR

  • Employee onboarding

  • Leave requests

  • Payroll operations


41. Advantages of Mediator Pattern

1. Reduces Coupling

Objects don't need direct references to one another.


2. Centralizes Communication

Communication rules can be located in the mediator or request-handling pipeline.


3. Improves Testability

Individual handlers/services can be tested independently.


4. Supports Single Responsibility

Handlers can focus on one request/use case.


5. Works Well with CQRS

Mediator-style request routing is a natural fit for:

Commands
Queries
Handlers

6. Centralizes Cross-Cutting Concerns

Pipeline behaviors can handle:

Logging
Validation
Authorization
Transactions
Performance Monitoring

7. Cleaner Controllers

Controllers can become thin:

HTTP
 ↓
Request
 ↓
Mediator
 ↓
Handler

42. Disadvantages of Mediator Pattern

1. Mediator Can Become Too Large

If all business logic is placed into one mediator class, it becomes a God Object.

Bad design:

Mediator
 ├── Orders
 ├── Payments
 ├── Inventory
 ├── Customers
 ├── Reports
 ├── Shipping
 └── Everything Else

The mediator should coordinate, not contain the entire business domain.


2. Additional Abstraction

For a small application, mediator infrastructure can be unnecessary.


3. Debugging Can Be Less Direct

Instead of:

Controller → Service

the flow can become:

Controller
 → Mediator
 → Pipeline
 → Handler
 → Service
 → Repository

This requires developers to understand the architecture.


4. Overuse Can Create Excessive Classes

A very small operation may not justify:

Request
Handler
Validator
Behavior
Response

Use the architecture where it adds value.


43. Best Practices

1. Keep Handlers Focused

A handler should represent a specific use case.

For example:

CreateOrderHandler
CancelOrderHandler
GetOrderHandler

2. Keep Business Logic in Appropriate Layers

Don't turn the mediator into a business-logic container.

Use:

Handler
 ↓
Domain/Application Services
 ↓
Repository

where appropriate.


3. Use Dependency Injection

ASP.NET Core DI works naturally with mediator-based architectures.


4. Keep Controllers Thin

Controllers should primarily handle:

  • HTTP concerns

  • Model binding

  • Authentication context

  • Response mapping

Business workflows should not become controller code.


5. Use Pipeline Behaviors for Cross-Cutting Concerns

Good candidates include:

Logging
Validation
Authorization
Transactions
Performance measurement

6. Don't Introduce a Mediator Everywhere

For simple applications:

Controller → Service

may be perfectly appropriate.

Architecture should solve a real problem rather than introduce complexity for its own sake.


7. Keep Commands and Queries Clear

Use meaningful names:

CreateOrderCommand
CancelOrderCommand

GetOrderQuery
GetCustomerQuery

44. Common Mistakes

Mistake 1 – Putting Everything in the Mediator

The mediator should coordinate communication.

It should not become the entire application's business layer.


Mistake 2 – Creating One Giant Handler

For example:

OrderHandler

containing:

Create
Update
Cancel
Refund
Ship
Track
Return

Prefer focused handlers.


Mistake 3 – Confusing Mediator with CQRS

Mediator and CQRS are different.

Mediator
→ Communication / request routing

CQRS
→ Separation of commands and queries

They can be used together, but one does not mean the other.


Mistake 4 – Overusing Pipeline Behaviors

Not every operation needs ten layers of pipeline processing.

Use behaviors for genuine cross-cutting concerns.


Mistake 5 – Hiding Important Business Flow

If the architecture has too many abstractions, developers may struggle to understand the actual workflow.

Keep the request flow discoverable.


45. Mediator vs Observer

These patterns are often confused.

MediatorObserver
Centralizes communicationBroadcasts notifications
Usually coordinates interactionsOne-to-many relationship
Participants communicate through mediatorObservers subscribe to subject
Focuses on coordinationFocuses on event notification

Mediator

A → Mediator → B

Observer

        Subject
       /   |   \
      ↓    ↓    ↓
 Observer Observer Observer

46. Mediator vs Facade

Both can appear to centralize access, but they have different goals.

MediatorFacade
Coordinates communication between objectsSimplifies access to a subsystem
Objects communicate through mediatorClient communicates with facade
Focuses on collaborationFocuses on simplification
Behavioral patternStructural pattern

Facade:

Client
  ↓
Facade
  ↓
Subsystem

Mediator:

Object A
   ↓
Mediator
   ↓
Object B

47. Mediator vs Command

These patterns also work together.

Command

Encapsulates a request as an object.

CreateOrderCommand

Mediator

Routes the request to the appropriate handler.

Command
   ↓
Mediator
   ↓
Handler

Therefore, they are complementary rather than competing patterns.


48. Mediator vs Chain of Responsibility

The distinction is important.

Chain of Responsibility

A request passes through a chain:

Request
 ↓
Handler A
 ↓
Handler B
 ↓
Handler C

Mediator

Objects communicate through a central mediator:

Object A
    ↓
Mediator
    ↓
Object B

A mediator-based application can also contain pipeline behaviors that resemble a chain.


49. Interview Questions

Beginner

1. What is the Mediator Design Pattern?

It is a behavioral pattern that centralizes communication between objects so they don't need direct references to one another.


2. What problem does Mediator solve?

It reduces tightly coupled communication between multiple objects.


3. What type of design pattern is Mediator?

It is a Behavioral Design Pattern.


4. What are Colleagues?

They are the objects that communicate through the mediator.


5. What is the role of the Mediator?

It coordinates communication and interaction between participating objects.


Intermediate

6. What is the difference between Mediator and Facade?

Facade simplifies access to a subsystem.

Mediator coordinates communication between participating objects.


7. What is the difference between Mediator and Observer?

Mediator centralizes and coordinates communication.

Observer provides one-to-many notification.


8. Is Mediator related to CQRS?

They are separate concepts, but they are frequently used together.

Mediator can route commands and queries to their respective handlers.


9. What is a Handler?

A handler is responsible for processing a specific request, command, or query.


10. What is a pipeline behavior?

A pipeline behavior executes around request handling and is useful for cross-cutting concerns such as:

Logging
Validation
Authorization
Transactions
Performance monitoring

50. Advanced Interview Questions

11. Why should a mediator not contain all business logic?

Because it can become a God Object with too many responsibilities.

Business logic should remain in appropriate domain/application services and handlers.


12. How does Mediator improve testability?

Components can depend on abstractions and focused handlers can be tested independently.


13. Can Mediator be used without CQRS?

Yes.

Mediator is a communication pattern. CQRS is an architectural pattern for separating commands and queries.


14. Can CQRS be used without Mediator?

Yes.

CQRS doesn't require a mediator implementation.


15. How does Mediator work with dependency injection?

The mediator, handlers, services, repositories, and behaviors can be registered with ASP.NET Core's dependency injection container.


16. Why are pipeline behaviors useful?

They allow cross-cutting concerns to be applied consistently without duplicating code in every handler.


17. What is a common disadvantage of Mediator?

It can add unnecessary abstraction and make simple flows more complicated.


18. When should you avoid Mediator?

For small applications where direct service calls are clear and manageable.


19. How does Mediator relate to Clean Architecture?

Mediator-style request/handler architecture can fit naturally into the application layer of Clean Architecture.

For example:

API
 ↓
Application
 ↓
Domain
 ↓
Infrastructure

Requests and handlers can live in the Application layer while infrastructure concerns remain separated.


20. What is the difference between a Mediator and a Service?

A service generally provides a business capability.

A mediator primarily coordinates communication or routes requests between components.


51. Practical Enterprise Architecture

A modern ASP.NET Core application may use:

                    Client
                      |
                      ↓
                ASP.NET Core API
                      |
                      ↓
                  Controller
                      |
                      ↓
                   Mediator
                      |
             +--------+--------+
             |                 |
             ↓                 ↓
          Command            Query
             |                 |
             ↓                 ↓
          Handler            Handler
             |                 |
             ↓                 ↓
       Domain Services    Read Services
             |                 |
             ↓                 ↓
       Repository / DB    Repository / DB

Cross-cutting concerns can surround the handler:

Request
   ↓
Logging
   ↓
Validation
   ↓
Authorization
   ↓
Transaction
   ↓
Handler
   ↓
Response

This architecture can provide a clean and maintainable application layer when used appropriately.


52. Complete Request Flow Example

Let's consider:

POST /api/orders

The request might flow through the application as:

HTTP Request
     ↓
OrdersController
     ↓
CreateOrderCommand
     ↓
Mediator
     ↓
Validation Behavior
     ↓
Authorization Behavior
     ↓
Transaction Behavior
     ↓
CreateOrderHandler
     ↓
OrderService
     ↓
Repository
     ↓
Database
     ↓
Response

This gives developers a predictable request-processing pipeline.


53. Key Takeaways

Remember these points:

1. Mediator is a Behavioral Design Pattern

It focuses on communication and collaboration between objects.

2. It reduces direct coupling

Instead of:

A → B
A → C
B → C

we use:

A → Mediator
B → Mediator
C → Mediator

3. Colleagues communicate through the mediator

They don't need direct knowledge of one another.

4. Mediator works well with ASP.NET Core

It can help structure application-level request handling.

5. Mediator and CQRS are different

Mediator handles communication/routing.

CQRS separates commands and queries.

6. Commands and Queries can have dedicated handlers

Command
 ↓
CommandHandler

Query
 ↓
QueryHandler

7. Pipeline behaviors handle cross-cutting concerns

Examples:

Logging
Validation
Authorization
Transactions
Performance

8. Don't overuse Mediator

For simple applications, a direct:

Controller → Service

architecture can be better.


Conclusion

The Mediator Design Pattern is an important behavioral pattern for designing loosely coupled applications.

Without Mediator, objects can become tightly connected:

A → B
A → C
B → D
C → D

As the application grows, this communication structure can become difficult to understand and maintain.

Mediator introduces a central communication point:

              Mediator
             /   |   \
            ↓    ↓    ↓
           A     B     C

In modern ASP.NET Core applications, mediator-style architecture is especially useful when combined with:

  • Dependency Injection

  • CQRS

  • Command/Query handlers

  • Validation pipelines

  • Logging pipelines

  • Authorization

  • Transaction management

  • Clean Architecture

A typical enterprise request flow can become:

Controller
    ↓
Mediator
    ↓
Pipeline Behaviors
    ↓
Handler
    ↓
Application/Domain Services
    ↓
Repository
    ↓
Database

However, the Mediator Pattern should not be treated as a mandatory architecture for every application.

For a small application, direct service calls may be simpler.

For a large enterprise application with many use cases and cross-cutting requirements, mediator-style request/handler architecture can significantly improve organization and separation of responsibilities.

The key idea is simple: Mediator reduces direct communication between objects by introducing a central coordinator, making complex interactions easier to organize, test, and maintain.


🚀 Coming Up Next: Part 4.6 – Memento Design Pattern

In the next article, we'll explore the Memento Design Pattern, including:

  • What is the Memento Pattern?

  • Why do we need it?

  • Originator, Memento, and Caretaker

  • Encapsulation and state preservation

  • UML Class Diagram

  • Complete C# Console Application

  • Undo/Redo implementation

  • ASP.NET Core implementation

  • Banking transaction rollback scenario

  • Document version history

  • Configuration snapshots

  • Database transaction concepts

  • State restoration

  • Real-world enterprise examples

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Memento vs Command

  • Memento vs Snapshot

  • Memento vs Prototype

  • Interview questions

The Memento Pattern will be particularly useful for understanding how applications can capture and restore an object's previous state while keeping the internal details of that state encapsulated.

Don't Copy

Protected by Copyscape Online Plagiarism Checker