Работали ли вы с паттерном Mediator (например, с библиотекой MediatR)?

«Работали ли вы с паттерном Mediator (например, с библиотекой MediatR)?» — вопрос из категории Паттерны, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, применял паттерн Mediator, в основном через библиотеку MediatR в .NET-приложениях. Это мощный инструмент для разделения команд/запросов и их обработчиков, что особенно полезно в архитектурах, подобных Clean Architecture или CQRS.

Ключевые концепции и преимущества:

  • Уменьшение связанности: Компоненты (например, контроллеры) не знают о конкретной бизнес-логике. Они отправляют сообщение (команду или запрос) через медиатор, который находит и вызывает соответствующий обработчик.
  • Организация кода: Каждая бизнес-операция инкапсулируется в отдельном классе-обработчике (IRequestHandler<TRequest, TResponse>).
  • Легкое добавление сквозной функциональности: Через Behaviors (конвейеры) можно централизованно добавить логирование, валидацию, кэширование или транзакции для всех или определенных запросов.

Пример реализации команды с валидацией и логированием:

  1. Команда (Request):

    public record CreateProductCommand(string Name, decimal Price) : IRequest<int>;
  2. Валидатор (FluentValidation):

    public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
    {
        public CreateProductCommandValidator()
        {
            RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
            RuleFor(x => x.Price).GreaterThan(0);
        }
    }
  3. Обработчик (Handler):

    public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
    {
        private readonly IApplicationDbContext _context;
        public CreateProductCommandHandler(IApplicationDbContext context) => _context = context;
    
        public async Task<int> Handle(CreateProductCommand request, CancellationToken ct)
        {
            var product = new Product { Name = request.Name, Price = request.Price };
            _context.Products.Add(product);
            await _context.SaveChangesAsync(ct);
            return product.Id; // Возвращаем ID созданного продукта
        }
    }
  4. Использование в контроллере:

    [ApiController]
    [Route("api/[controller]")]
    public class ProductsController : ControllerBase
    {
        private readonly IMediator _mediator;
        public ProductsController(IMediator mediator) => _mediator = mediator;
    
        [HttpPost]
        public async Task<ActionResult<int>> Create(CreateProductCommand command)
        {
            var productId = await _mediator.Send(command);
            return Ok(productId);
        }
    }

Когда использовать: Отлично подходит для средних и крупных проектов, где важно разделение ответственности. Для простых CRUD-операций может быть избыточным.