Что такое MediatR и зачем она нужна?
MediatR — это легковесная, но мощная библиотека для .NET, реализующая паттерн Mediator (Посредник). В мире разработки программного обеспечения, особенно в приложениях, построенных на архитектуре CQRS (Command Query Responsibility Segregation — разделение ответственности на команды и запросы), MediatR стала де-факто стандартом.
Зачем она нужна?
В классической архитектуре, когда один класс (например, контроллер) хочет выполнить действие в другом классе (например, сервисе), он должен знать о существовании этого сервиса и его интерфейсе. Это создает жесткую связность (tight coupling). Если вы захотите добавить логирование, валидацию или кэширование перед вызовом сервиса, вам придется менять код контроллера или создавать декораторы, что усложняет поддержку.
MediatR решает эту проблему, вводя понятие посредника. Компоненты системы (например, контроллеры) больше не общаются напрямую. Вместо этого они отправляют запросы (Request) или команды (Command) через MediatR. Библиотека сама находит и вызывает соответствующий обработчик (Handler).
Ключевые преимущества использования MediatR:
- Слабая связность (Loose Coupling): Отправитель запроса ничего не знает о получателе. Он знает только о типе запроса.
- Принцип единой ответственности (SRP): Каждый обработчик отвечает только за одно действие (например, "Создать пользователя", "Получить список заказов").
- Упрощение CQRS: MediatR идеально подходит для разделения команд (изменяющих состояние) и запросов (читающих состояние).
- Поведенческие цепочки (Pipeline Behaviors): Вы можете легко добавлять сквозную функциональность (логирование, валидацию, транзакции) без изменения кода обработчиков.
- Тестируемость: Легко тестировать обработчики изолированно, а также легко заменять их реализацию.
Установка MediatR
MediatR распространяется через NuGet. Установить её можно несколькими способами:
1. Через .NET CLI (рекомендуемый способ)
Откройте терминал в корне вашего проекта и выполните команду:
dotnet add package MediatR
Также вам понадобится пакет для внедрения зависимостей (DI), если вы используете ASP.NET Core:
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
2. Через Package Manager Console (Visual Studio)
Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
3. Через NuGet Package Manager UI
В Visual Studio: Tools → NuGet Package Manager → Manage NuGet Packages for Solution.... Найдите "MediatR" и установите оба пакета.
Быстрый старт с примером кода
Давайте создадим простой пример: отправка приветствия пользователю.
Шаг 1: Создайте проект
Создайте новый консольный проект или проект ASP.NET Core Web API.
Шаг 2: Определите Запрос (Request)
Создайте класс, который будет представлять запрос. Он должен реализовывать интерфейс IRequest<TResponse>, где TResponse — тип возвращаемого значения.
using MediatR;
public class PingRequest : IRequest<string>
{
public string Message { get; set; } = "Ping";
}
Шаг 3: Создайте Обработчик (Handler)
Создайте класс, который реализует интерфейс IRequestHandler<TRequest, TResponse>.
using MediatR;
using System.Threading;
using System.Threading.Tasks;
public class PingHandler : IRequestHandler<PingRequest, string>
{
public Task<string> Handle(PingRequest request, CancellationToken cancellationToken)
{
Console.WriteLine($"Получен запрос: {request.Message}");
return Task.FromResult($"Ответ на {request.Message}: Pong!");
}
}
Шаг 4: Настройте DI и отправьте запрос
В файле Program.cs (для .NET 6+):
using MediatR;
using Microsoft.Extensions.DependencyInjection;
// 1. Создаем коллекцию сервисов
var services = new ServiceCollection();
// 2. Регистрируем MediatR. ScanAssembly сканирует сборку на наличие обработчиков
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
// 3. Строим провайдер сервисов
var serviceProvider = services.BuildServiceProvider();
// 4. Получаем экземпляр IMediator
var mediator = serviceProvider.GetRequiredService<IMediator>();
// 5. Отправляем запрос
var response = await mediator.Send(new PingRequest { Message = "Hello" });
Console.WriteLine(response); // Вывод: Ответ на Hello: Pong!
Основные методы и функции MediatR
Рассмотрим ключевые интерфейсы и методы, которые предоставляет библиотека.
1. IMediator.Send(TRequest request)
Что делает: Отправляет команду или запрос, который ожидает один обработчик. Это основной метод для CQRS-команд (Create, Update, Delete) и запросов (Get).
Параметры:
request: Объект, реализующийIRequest<TResponse>илиIRequest.cancellationToken(опционально): Токен для отмены операции.
Пример:
// Команда без возвращаемого значения
public class CreateUserCommand : IRequest { public string Name { get; set; } }
public class CreateUserHandler : IRequestHandler<CreateUserCommand, Unit>
{
public Task<Unit> Handle(CreateUserCommand request, CancellationToken ct)
{
// Логика создания пользователя
return Unit.Task;
}
}
// Отправка
await mediator.Send(new CreateUserCommand { Name = "Alice" });
2. IMediator.Publish(TNotification notification)
Что делает: Публикует уведомление, которое может быть обработано несколькими обработчиками одновременно. Используется для событий (Event-Driven).
Параметры:
notification: Объект, реализующийINotification.cancellationToken(опционально): Токен для отмены.
Пример:
public class UserCreatedNotification : INotification
{
public int UserId { get; set; }
}
// Обработчик 1: Отправка email
public class EmailHandler : INotificationHandler<UserCreatedNotification>
{
public Task Handle(UserCreatedNotification notification, CancellationToken ct)
{
Console.WriteLine($"Отправка email пользователю {notification.UserId}");
return Task.CompletedTask;
}
}
// Обработчик 2: Логирование
public class LoggingHandler : INotificationHandler<UserCreatedNotification>
{
public Task Handle(UserCreatedNotification notification, CancellationToken ct)
{
Console.WriteLine($"Лог: создан пользователь {notification.UserId}");
return Task.CompletedTask;
}
}
// Публикация
await mediator.Publish(new UserCreatedNotification { UserId = 123 });
// Вывод (порядок может отличаться):
// Отправка email пользователю 123
// Лог: создан пользователь 123
3. IRequest<TResponse>
Что делает: Базовый интерфейс для всех команд/запросов, которые возвращают результат типа TResponse.
Параметры: Нет методов. Просто маркерный интерфейс.
Пример:
public class GetUserQuery : IRequest<UserDto>
{
public int UserId { get; set; }
}
public class GetUserHandler : IRequestHandler<GetUserQuery, UserDto>
{
public Task<UserDto> Handle(GetUserQuery request, CancellationToken ct)
{
// Логика получения пользователя из БД
return Task.FromResult(new UserDto { Id = request.UserId, Name = "Bob" });
}
}
4. IRequest (без возвращаемого значения)
Что делает: Маркерный интерфейс для команд, которые ничего не возвращают (void). Внутри MediatR преобразует его в IRequest<Unit>.
Параметры: Нет.
Пример:
public class DeleteUserCommand : IRequest { public int UserId { get; set; } }
public class DeleteUserHandler : IRequestHandler<DeleteUserCommand, Unit>
{
public Task<Unit> Handle(DeleteUserCommand request, CancellationToken ct)
{
Console.WriteLine($"Удаление пользователя {request.UserId}");
return Unit.Task;
}
}
5. INotification
Что делает: Маркерный интерфейс для уведомлений (событий). Не возвращает значения.
Параметры: Нет.
Пример: См. пример для Publish выше.
6. IRequestHandler<TRequest, TResponse>
Что делает: Интерфейс для обработчика, который принимает запрос TRequest и возвращает TResponse.
Параметры:
Handle(TRequest request, CancellationToken cancellationToken): Основной метод, который должен быть реализован.
Пример:
public class MyHandler : IRequestHandler<MyRequest, MyResponse>
{
public Task<MyResponse> Handle(MyRequest request, CancellationToken ct)
{
// Реализация
}
}
7. INotificationHandler<TNotification>
Что делает: Интерфейс для обработчика уведомлений.
Параметры:
Handle(TNotification notification, CancellationToken cancellationToken): Метод, вызываемый при публикации уведомления.
Пример:
public class MyNotificationHandler : INotificationHandler<MyNotification>
{
public Task Handle(MyNotification notification, CancellationToken ct)
{
// Реакция на событие
}
}
8. IStreamRequest<TResponse> и IStreamRequestHandler<TRequest, TResponse>
Что делает: Поддержка потоковой передачи данных (IAsyncEnumerable). Позволяет обрабатывать большие объемы данных без загрузки всего в память.
Параметры:
CreateHandler(IStreamRequestHandler): Обработчик должен возвращатьIAsyncEnumerable<TResponse>.
Пример:
public class StreamRequest : IStreamRequest<int> { public int Count { get; set; } }
public class StreamHandler : IStreamRequestHandler<StreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(StreamRequest request, [EnumeratorCancellation] CancellationToken ct)
{
for (int i = 0; i < request.Count; i++)
{
await Task.Delay(100, ct); // Симуляция работы
yield return i;
}
}
}
// Использование
await foreach (var item in mediator.CreateStream(new StreamRequest { Count = 5 }))
{
Console.WriteLine(item); // 0, 1, 2, 3, 4
}
9. IPipelineBehavior<TRequest, TResponse>
Также в библиотеке
Livewire для PHP: полное руководство с примерами
Генерация PDF отчетов на Kotlin: iText, Apache PDFBox, OpenPDF
PDF генерация в JavaScript (Node.js): библиотеки и инструменты
Mongoose Paginate для JavaScript (Node.js): полное руководство с примерами
Логирование для JavaScript (Node.js): лучшие решения и библиотеки
Рекомендаций
Также в библиотеке
Генерация PDF отчетов на Kotlin: iText, Apache PDFBox, OpenPDF
PDF генерация в JavaScript (Node.js): библиотеки и инструменты
Mongoose Paginate для JavaScript (Node.js): полное руководство с примерами
Логирование для JavaScript (Node.js): лучшие решения и библиотеки