Ответ
Да, применял паттерн Mediator, в основном через библиотеку MediatR в .NET-приложениях. Это мощный инструмент для разделения команд/запросов и их обработчиков, что особенно полезно в архитектурах, подобных Clean Architecture или CQRS.
Ключевые концепции и преимущества:
- Уменьшение связанности: Компоненты (например, контроллеры) не знают о конкретной бизнес-логике. Они отправляют сообщение (команду или запрос) через медиатор, который находит и вызывает соответствующий обработчик.
- Организация кода: Каждая бизнес-операция инкапсулируется в отдельном классе-обработчике (
IRequestHandler<TRequest, TResponse>). - Легкое добавление сквозной функциональности: Через Behaviors (конвейеры) можно централизованно добавить логирование, валидацию, кэширование или транзакции для всех или определенных запросов.
Пример реализации команды с валидацией и логированием:
-
Команда (Request):
public record CreateProductCommand(string Name, decimal Price) : IRequest<int>; -
Валидатор (FluentValidation):
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand> { public CreateProductCommandValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Price).GreaterThan(0); } } -
Обработчик (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 созданного продукта } } -
Использование в контроллере:
[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, настраиваешь конвейер, и всё автоматом оборачивается. Красота, ёпта.
Вот, смотри, как это обычно выглядит на практике:
-
Сама команда (что нужно сделать):
public record CreateProductCommand(string Name, decimal Price) : IRequest<int>;Просто запись, данные. Никакой логики.
-
Валидатор (чтобы не присылали хуйню):
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand> { public CreateProductCommandValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Price).GreaterThan(0); // Иначе какой смысл, товар за бесплатно? } } -
Обработчик (тот самый чувак, который всё делает):
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 нового творения } } -
Использование где-нибудь в контроллере:
[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); } }
А теперь про «когда это надо»: Если у тебя проект на три кнопки и одна таблица в базе — не еби себе мозг, сделай проще. Но когда уже чувствуешь, что сервисы начинают зависеть друг от друга как соседи по коммуналке, и добавление новой фичи вызывает волнение ебать на весь код — вот тут самое время. Иначе потом будет больно, как от мартышлюшки по яйцам.