MediatR и pipeline в слое Application
MediatR и pipeline в слое Application
MediatR — библиотека «медиатора» для .NET: один объект (ISender) принимает сообщение (команду или запрос) и передаёт его зарегистрированному обработчику. В шаблоне Clean Architecture на ASP.NET Core через MediatR связаны HTTP endpoints и классы в Application.
Теория CQRS — 2122; сквозная структура solution — 2143. Web API и endpoints — 4511.
Где применяют MediatR в Clean Architecture
| Без MediatR | С MediatR |
|---|---|
Endpoint вызывает IOrderService.Create(...) | Endpoint вызывает sender.Send(new CreateOrderCommand(...)) |
| Сервис растёт по мере фич | Каждая операция — отдельный handler в своей папке |
| Cross-cutting копируют в методы | Pipeline behaviours — один раз на все запросы |
MediatR не заменяет домен: бизнес-правила остаются в сущностях и value objects. Он маршрутизирует вызов сценария из Presentation в Application.
Команда и обработчик
// Команда — DTO намерения (изменяет состояние)
public record CreateTodoListCommand : IRequest<int>
{
public string? Title { get; init; }
}
// Обработчик — один класс на сценарий
public class CreateTodoListCommandHandler : IRequestHandler<CreateTodoListCommand, int>
{
private readonly IApplicationDbContext _context;
public CreateTodoListCommandHandler(IApplicationDbContext context) => _context = context;
public async Task<int> Handle(CreateTodoListCommand request, CancellationToken cancellationToken)
{
var entity = new TodoList { Title = request.Title };
_context.TodoLists.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity.Id;
}
}
Запрос (query) устроен так же, но возвращает данные и не меняет состояние по контракту CQRS:
public record GetTodosQuery : IRequest<TodosVm>;
public class GetTodosQueryHandler : IRequestHandler<GetTodosQuery, TodosVm> { /* ... */ }
Регистрация в DI (типично в Application/DependencyInjection.cs):
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
ISender из Web-слоя
app.MapPost("/api/todo-lists", async (ISender sender, CreateTodoListCommand cmd, CancellationToken ct) =>
{
var id = await sender.Send(cmd, ct);
return Results.Created($"/api/todo-lists/{id}", id);
});
ISender скрывает конкретный handler: endpoint зависит только от типа команды. Новый сценарий = новая пара Command + Handler, без правок существующих endpoints (кроме маршрута).
Pipeline behaviours
IPipelineBehavior<TRequest, TResponse> оборачивает вызов handler'а цепочкой, похожей на middleware ASP.NET:
ValidationBehaviour — запускает все IValidator<TRequest> до handler'а:
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var results = await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = results.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
Регистрация:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehaviour<,>));
Порядок регистрации задаёт порядок выполнения (первый зарегистрированный — внешний слой цепочки).
FluentValidation
Правила входа команды — рядом с командой:
public class CreateTodoListCommandValidator : AbstractValidator<CreateTodoListCommand>
{
public CreateTodoListCommandValidator()
{
RuleFor(v => v.Title)
.MaximumLength(200)
.NotEmpty();
}
}
AddValidatorsFromAssembly подхватывает валидаторы; ValidationBehaviour вызывает их автоматически. Инварианты домена («нельзя завершить пустую задачу») остаются в entity; валидатор проверяет форму запроса (длина, обязательные поля).
См. также Minimal API и OpenAPI для ошибок 400 на уровне HTTP.
Notifications (события внутри процесса)
INotification + INotificationHandler<T> — слабая связь внутри одного приложения: после SaveChanges handler публикует TodoItemCreatedNotification, другие handlers реагируют (кэш, email). Это in-process pub/sub, не замена брокеру сообщений между сервисами.
Использовать умеренно: избыточная сеть notification'ов усложняет трассировку.
Когда MediatR избыточен
| Признак | Альтернатива |
|---|---|
| 5–10 endpoint'ов, один автор | Прямой вызов сервисов или use-case классов без MediatR |
| Нет общих cross-cutting | Pipeline не окупает абстракцию |
| Команда «нажми F5» для junior | Явные CreateOrderHandler в DI без брокера сообщений |
Минимальный Clean Architecture из 2132 работает без MediatR: CreateTaskUseCase инжектируется в контроллер напрямую.
Связь с тестами
Юнит-тест handler'а:
[Fact]
public async Task Handle_Empty_title_fails_validation()
{
var handler = new CreateTodoListCommandHandler(new FakeDbContext());
var validator = new CreateTodoListCommandValidator();
var cmd = new CreateTodoListCommand { Title = "" };
var result = await validator.ValidateAsync(cmd);
Assert.False(result.IsValid);
}
Интеграционный тест — WebApplicationFactory + POST с телом JSON (4516): проверяется вся цепочка endpoint → MediatR → EF.
Итог
MediatR в слое Application даёт единую точку входа для сценариев (ISender), vertical slices (команда + handler + validator в одной папке) и pipeline для валидации и наблюдаемости. Для учебного и корпоративного .NET это удобная реализация CQRS-light; для маленького API достаточно тонких endpoints и явных use-case классов без дополнительной библиотеки.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C# как язык платформы .NET - устройство проекта, роль `.cs`-файлов и базовые принципы организации кода. C# — это современный, типизированный язык программирования общего назначения, разработанный корпорацией Microsoft. Справочник-шпаргалка по конфигурациям в C — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс — раздел. Набор советов, правил, принципов и обычаев в разработке на этом языке. Кавычки, точки, запятые, скобки и прочие знаки препинания. Ключевые слова C# - назначение базовых конструкций языка и примеры их применения в типичном коде. Набор функций, которые включены в стандартную библиотеку языка. Пространства имён в C# - организация модулей, `file-scoped namespace` и поддержание чистой структуры кода. манипулировать данными (арифметические, логические, сравнительные операторы). Самый базовый способ ветвления — оператор if. Он проверяет условие и, если оно истинно (true), выполняет блок кода. Обработка исключений в C# - типы исключений, `try/catch/finally` и практики надежного кода. Платформо-зависимые исключения — например, PlatformNotSupportedException используется в кроссплатформенных API, когда функция недоступна на текущей ОС.C# - язык программирования платформы .NET
Что требуется знать перед началом изучения языка программирования C#
Справочник по конфигурациям в C#
Рекомендации по разработке на C#
Синтаксис и пунктуация в C#
Ключевые слова языка C#
Встроенные функции и методы C#
Пространства имён в C#
Управляющие конструкции и логические операторы
Условные выражения и ветвления
Обработка исключений в C#
Иерархия классов исключений в C#