Mediator Pattern
& MediatR
Kiến trúc Mediator giúp tách biệt logic, giảm coupling — triển khai dễ dàng với MediatR
Mediator Pattern là gì?
Mediator là một design pattern thuộc nhóm Behavioral, giúp giảm sự phụ thuộc trực tiếp giữa các đối tượng bằng cách đưa toàn bộ giao tiếp qua một trung gian (mediator). Thay vì component A gọi trực tiếp component B, cả hai chỉ giao tiếp qua mediator.
Direct Coupling
- Controller inject 5-10 services
- Thay đổi service → sửa controller
- Khó unit test (nhiều mock)
- Constructor phình to, khó đọc
- Vi phạm Single Responsibility
Mediator Pattern
- Controller chỉ inject
IMediator - Logic nằm trong Handler riêng biệt
- Dễ unit test từng handler
- Constructor gọn gàng, rõ ràng
- Mỗi handler = một responsibility
Cách hoạt động
Khi controller cần thực hiện một hành động, nó tạo một request object và gửi qua MediatR. MediatR tìm handler tương ứng, thực thi, và trả về kết quả:
MediatR Library
MediatR là thư viện .NET của Jimmy Bogard (tác giả AutoMapper), triển khai Mediator pattern với hai mô hình giao tiếp:
Request / Response
IRequest<TResponse>— request có responseIRequest— request không trả response- Gửi qua
mediator.Send(request) - Luôn có đúng một handler xử lý
Notification (Pub/Sub)
INotification— sự kiện phát điINotificationHandler<T>— subscriber- Gửi qua
mediator.Publish(notification) - Có thể có 0 đến nhiều handlers
Cài đặt & Thiết lập
Package Manager
Install-Package MediatR
.NET CLI
dotnet add package MediatR
Đăng ký MediatR vào DI container trong Program.cs:
using MediatR; var builder = WebApplication.CreateBuilder(args); // Đăng ký MediatR — scan tất cả handlers trong assembly builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly) ); // Nếu handlers nằm ở nhiều assembly khác nhau: builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies( typeof(Program).Assembly, typeof(ApplicationLayer).Assembly ) );
AddMediatR(typeof(Program).Assembly) đã bị deprecated. Luôn dùng overload với
Action<MediatRServiceConfiguration> như ví dụ trên.
Ví dụ cơ bản
Một ví dụ đơn giản: API lấy thông tin sản phẩm theo ID.
1. Định nghĩa Request
public record GetProductByIdQuery( int Id ) : IRequest<ProductDto>;
2. Tạo Handler
public class GetProductByIdHandler : IRequestHandler<GetProductByIdQuery, ProductDto> { private readonly AppDbContext _db; public GetProductByIdHandler(AppDbContext db) => _db = db; public async Task<ProductDto> Handle( GetProductByIdQuery request, CancellationToken cancellationToken) { var product = await _db.Products .FindAsync(new object[] { request.Id }, cancellationToken) ?? throw new NotFoundException("Product", request.Id); return new ProductDto(product.Id, product.Name, product.Price); } }
3. Gọi từ Controller
[ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly ISender _sender; public ProductsController(ISender sender) => _sender = sender; [HttpGet("{id:int}")] public async Task<IActionResult> GetById(int id) { var result = await _sender.Send(new GetProductByIdQuery(id)); return Ok(result); } }
ISender thay vì IMediator nếu controller chỉ cần Send(). Nguyên tắc
Interface Segregation — chỉ phụ thuộc vào interface nhỏ nhất cần thiết.
Lợi ích chính
Controller không biết handler nào xử lý request. Bạn có thể thay đổi, refactor, hoặc swap handler mà không cần sửa controller. Điều này đặc biệt hữu ích trong các dự án lớn có nhiều team cùng phát triển.
Mỗi handler chỉ xử lý đúng một use case. Thay vì một ProductService chứa 20 methods, bạn có 20 handlers nhỏ gọn, mỗi handler là một file riêng, dễ navigate và dễ hiểu.
Unit test một handler cực kỳ đơn giản: tạo instance handler, truyền dependencies (mock nếu cần), gọi
Handle(), assert kết quả. Không cần mock MediatR, không cần setup controller pipeline.
Pipeline Behaviors cho phép bạn thêm logging, validation, caching, authorization... vào tất cả requests mà không sửa bất kỳ handler nào. Giống middleware trong ASP.NET nhưng ở tầng application logic.
MediatR là nền tảng tự nhiên cho CQRS (Command Query Responsibility Segregation). Bạn tách Command (ghi) và Query (đọc) thành các request riêng biệt, mỗi loại có handler và pipeline behavior riêng.
Khám phá chi tiết
Chọn chủ đề bạn muốn tìm hiểu sâu hơn: