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

Ответ

Да, применял паттерн 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-операций может быть избыточным.

Ответ 18+ 🔞

Ну, смотри, я эту штуку, MediatR, на проектах применял. В общем, вещь мощная, если у тебя там не просто три экшена на контроллере, а уже какая-то архитектура начинает вырисовываться, типа Clean или CQRS.

Что за плюшки, и почему не полный пиздец:

  • Все друг про друга не знают. Контроллеру вообще похуй, как там продукт в базу лезет. Он просто кричит в пространство: «Эй, создай продукт с такими-то параметрами!» А медиатор уже сам находит того чувака, который знает, что с этим делать. Связанность — ноль ебать.
  • Код не превращается в спагетти. Каждая операция — это отдельный класс-обработчик. Хочешь понять, как создаётся заказ? Идешь в CreateOrderCommandHandler и там всё видишь. Никакой хуйни в сервисах на 5000 строк.
  • Сквозная функциональность — просто песня. Нужно ко всем командам прикрутить валидацию и логирование? Не надо в каждом обработчике писать _logger.LogInformation("Начало"). Пишешь один раз Behavior, настраиваешь конвейер, и всё автоматом оборачивается. Красота, ёпта.

Вот, смотри, как это обычно выглядит на практике:

  1. Сама команда (что нужно сделать):

    public record CreateProductCommand(string Name, decimal Price) : IRequest<int>;

    Просто запись, данные. Никакой логики.

  2. Валидатор (чтобы не присылали хуйню):

    public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
    {
        public CreateProductCommandValidator()
        {
            RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
            RuleFor(x => x.Price).GreaterThan(0); // Иначе какой смысл, товар за бесплатно?
        }
    }
  3. Обработчик (тот самый чувак, который всё делает):

    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);
        }
    }

А теперь про «когда это надо»: Если у тебя проект на три кнопки и одна таблица в базе — не еби себе мозг, сделай проще. Но когда уже чувствуешь, что сервисы начинают зависеть друг от друга как соседи по коммуналке, и добавление новой фичи вызывает волнение ебать на весь код — вот тут самое время. Иначе потом будет больно, как от мартышлюшки по яйцам.