- Streamlined Communication: Centralizes interactions between components for clarity.
- Decoupling: Reduces tight coupling, enhancing maintainability.
- MediatR Integration: A robust tool for implementing the Mediator Pattern in C#.
- Layered Architecture: Promotes separation of concerns for scalable systems.
- Reusability: Enables components to be reused across contexts.
Table of Contents
- What is the Mediator Pattern?
- Problems Addressed by the Mediator Pattern
- Implementing the Mediator Pattern in C# with MediatR
- Sample Code and Explanation
- Layered Architecture Explained
- Conclusion
- FAQ
What is the Mediator Pattern?
The Mediator Pattern is a behavioral design pattern that promotes loose coupling by centralizing communication between objects. Instead of objects interacting directly, they communicate through a mediator, which encapsulates interaction logic. This reduces dependencies, making systems easier to maintain and extend.
Problems Addressed by the Mediator Pattern
- Tight Coupling: Direct object interactions create complex, interdependent codebases. The Mediator Pattern eliminates this by routing communication through a single point.
- Complex Maintenance: Centralized communication simplifies debugging and updating interaction logic.
- Scalability Issues: Decoupled components are easier to modify or replace, supporting system growth.
- Reusability: Independent components can be reused in different contexts without modification.
Implementing the Mediator Pattern in C# with MediatR
In .NET, MediatR is a lightweight library that simplifies the Mediator Pattern, often used with Command Query Responsibility Segregation (CQRS). It enables clean separation of concerns by handling requests (commands or queries) through mediators and their handlers.
Steps to Use MediatR
- Install MediatR: Add the
MediatR
andMediatR.Extensions.Microsoft.DependencyInjection
packages via NuGet. - Define Requests and Handlers: Create request classes (commands or queries) and their corresponding handlers to process them.
- Configure Dependency Injection: Register MediatR services in your application’s dependency injection container.
- Dispatch Requests: Use the
IMediator
interface to send requests from controllers or services.
Sample Code and Explanation
Below is a practical example of using MediatR in a C# application to handle a user registration process.
Sample Code
// 1. Install MediatR packages
// Run in your project:
// dotnet add package MediatR
// dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
// 2. Define a Command (Request)
public class RegisterUserCommand : IRequest<User>
{
public string Username { get; set; }
public string Email { get; set; }
}
// 3. Define the Command Handler
public class RegisterUserCommandHandler : IRequestHandler<RegisterUserCommand, User>
{
public Task<User> Handle(RegisterUserCommand request, CancellationToken cancellationToken)
{
// Simulate user registration logic (e.g., save to database)
var user = new User
{
Id = Guid.NewGuid(),
Username = request.Username,
Email = request.Email
};
Console.WriteLine($"User {user.Username} registered with email {user.Email}");
return Task.FromResult(user);
}
}
// 4. User Model
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
// 5. Program Setup and Execution
public class Program
{
public static async Task Main()
{
// Configure Dependency Injection
var services = new ServiceCollection();
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
var serviceProvider = services.BuildServiceProvider();
// Resolve IMediator
var mediator = serviceProvider.GetService<IMediator>();
// Create and send a command
var command = new RegisterUserCommand
{
Username = "john_doe",
Email = "john@example.com"
};
var registeredUser = await mediator.Send(command);
Console.WriteLine($"Registered User ID: {registeredUser.Id}");
}
}
Explanation
- Command Definition:
RegisterUserCommand
represents the action (registering a user) and implementsIRequest<User>
, indicating it returns aUser
object. - Handler Logic:
RegisterUserCommandHandler
processes the command, simulating user registration. In a real application, this might involve database operations. - Dependency Injection: MediatR is registered in the DI container, allowing
IMediator
to route requests to the correct handler. - Request Dispatch: The
IMediator.Send
method sends the command to its handler, keeping the calling code decoupled from the handler’s implementation.
Layered Architecture Explained
A layered architecture organizes a .NET application into distinct layers, each with specific responsibilities, enhancing maintainability and scalability.
Layer | Description | Typical Contents |
---|---|---|
Domain Layer | Holds core business logic, entities, and domain services. | Entities, value objects, domain events |
Application Layer | Orchestrates business use cases, mediates between domain and external layers. | MediatR commands/queries, handlers, application services |
Infrastructure Layer | Manages technical concerns like database access and external integrations. | Repositories, EF Core contexts, API clients |
Presentation Layer | Handles client interactions, exposing endpoints or UI. | Controllers, Razor pages, minimal APIs |
MediatR in Layered Architecture
- Domain Layer: Contains pure business logic, unaware of MediatR.
- Application Layer: Hosts MediatR commands, queries, and handlers, orchestrating business logic.
- Infrastructure Layer: Provides services (e.g., repositories) used by handlers.
- Presentation Layer: Sends requests via
IMediator
, typically from controllers.
Request Flow Example
- A client sends a REST request to the presentation layer (e.g., a POST to
/api/users
). - The controller creates a command (e.g.,
RegisterUserCommand
) and dispatches it viaIMediator
. - MediatR routes the command to its handler in the application layer.
- The handler collaborates with domain entities and infrastructure services (e.g., a repository).
- The result is returned to the controller and sent to the client.
Conclusion
The Mediator Pattern, implemented via MediatR in C#, simplifies application architecture by decoupling components and centralizing communication. This leads to cleaner, more maintainable, and scalable codebases, especially when combined with CQRS and layered architecture. By adopting these practices, developers can build robust .NET applications that are easier to extend and test.
FAQ
- What is the Mediator Pattern? A behavioral pattern that centralizes communication between objects, reducing direct dependencies.
- How does MediatR improve application architecture? It decouples request handling, supports CQRS, and integrates seamlessly with dependency injection.
- Can the Mediator Pattern be used in other languages? Yes, it’s language-agnostic and widely used in languages like Java, Python, and JavaScript.
- What are real-world applications of the Mediator Pattern? It’s used in chat applications, event-driven systems, and microservices to manage complex interactions.
Connect with me on LinkedIn or check out my GitHub for more examples and discussions on software architecture!