25.11.19, Qwen, MediatR, MainUse
https://chat.qwen.ai/c/11bf3b5b-dded-482e-84fe-6595b55015ab
Хорошо, давайте разберем самый распространенный и "родной" use-case для MediatR и посмотрим, для чего он создан и что делает лучше всего.
Основное назначение MediatR
MediatR реализует паттерн Mediator. Его основная цель — уменьшить связанность (decoupling) между компонентами в приложении. Вместо того чтобы один класс напрямую вызывал методы другого (например, orderService.ProcessOrder(...), notificationService.Send(...)), он отправляет сообщение (например, ProcessOrderCommand), и MediatR находит и вызывает соответствующий обработчик (ProcessOrderCommandHandler).
Это особенно полезно в архитектуре, ориентированной на CQRS (Command Query Responsibility Segregation), где:
Команды (Commands) — это запросы на изменение состояния системы (например, CreateUserCommand, UpdateOrderStatusCommand). Они обрабатываются IRequestHandler<,> и не возвращают данные.
Запросы (Queries) — это запросы на получение данных (например, GetUserByIdQuery, GetOrdersByUserQuery). Они обрабатываются IRequestHandler<, TResponse> и возвращают данные.
События (Events) — это оповещения о том, что что-то произошло (например, OrderCreatedEvent, PaymentFailedEvent). Они обрабатываются INotificationHandler<> и могут вызываться множеством обработчиков.
Самый распространенный и "родной" Use-case: CQRS с обработкой команд и запросов в слое Application/Domain
Это классический сценарий использования MediatR — обработка бизнес-операций (команд) и запросов данных (queries) в веб-приложении, например, в ASP.NET Core MVC или Razor Pages.
Пример: Создание пользователя
Представим, что у нас есть ASP.NET Core приложение. Пользователь заполняет форму регистрации, и нам нужно:
Принять данные из формы.
Создать пользователя в системе.
Отправить ему приветственное письмо.
Записать событие в лог аудита.
Без MediatR это может выглядеть так:
// В контроллере или PageModel
public async Task<IActionResult> OnPostAsync(CreateUserModel model)
{
if (!ModelState.IsValid)
return Page();
// 1. Создание пользователя
var user = new User { Name = model.Name, Email = model.Email };
await _userRepository.AddAsync(user);
// 2. Отправка письма
await _emailService.SendWelcomeEmailAsync(user.Email);
// 3. Запись в лог аудита
await _auditLogService.LogAsync($"User {user.Name} created at {DateTime.UtcNow}");
return RedirectToPage("/Success");
}
Код в контроллере сильно связан с UserRepository, EmailService, AuditLogService. Это затрудняет тестирование и усложняет добавление новой логики (например, отправки SMS).
Как это выглядит с MediatR:
1. Определение команды:
using MediatR;
// Команда - запрос на изменение состояния
public record CreateUserCommand(string Name, string Email) : IRequest<Guid>; // Возвращаем ID созданного пользователя
2. Определение обработчика команды:
using MediatR;
using Microsoft.Extensions.Logging;
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _userRepository;
private readonly IPublisher _publisher; // MediatR также предоставляет IPublisher для отправки событий
private readonly ILogger<CreateUserCommandHandler> _logger;
public CreateUserCommandHandler(
IUserRepository userRepository,
IPublisher publisher, // Для отправки событий после создания
ILogger<CreateUserCommandHandler> logger)
{
_userRepository = userRepository;
_publisher = publisher;
_logger = logger;
}
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// 1. Создание и сохранение пользователя
var user = new User { Id = Guid.NewGuid(), Name = request.Name, Email = request.Email };
await _userRepository.AddAsync(user, cancellationToken);
// 2. Логика завершена, отправляем событие
var @event = new UserCreatedEvent(user.Id, user.Name, user.Email);
await _publisher.Publish(@event, cancellationToken);
_logger.LogInformation("User {UserId} created via command handler.", user.Id);
// 3. Возвращаем ID созданного пользователя
return user.Id;
}
}
3. Определение события:
using MediatR;
// Событие - произошло что-то
public record UserCreatedEvent(Guid UserId, string UserName, string UserEmail) : INotification;
4. Определение обработчиков событий:
using MediatR;
using Microsoft.Extensions.Logging;
public class WelcomeEmailNotificationHandler : INotificationHandler<UserCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<WelcomeEmailNotificationHandler> _logger;
public WelcomeEmailNotificationHandler(IEmailService emailService, ILogger<WelcomeEmailNotificationHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// 1. Отправка письма
await _emailService.SendWelcomeEmailAsync(notification.UserEmail);
_logger.LogInformation("Welcome email sent to {UserEmail} for user {UserId}.", notification.UserEmail, notification.UserId);
}
}
public class AuditLogNotificationHandler : INotificationHandler<UserCreatedEvent>
{
private readonly IAuditLogService _auditLogService;
private readonly ILogger<AuditLogNotificationHandler> _logger;
public AuditLogNotificationHandler(IAuditLogService auditLogService, ILogger<AuditLogNotificationHandler> logger)
{
_auditLogService = auditLogService;
_logger = logger;
}
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// 1. Запись в лог аудита
await _auditLogService.LogAsync($"User {notification.UserName} (ID: {notification.UserId}) created at {DateTime.UtcNow}");
_logger.LogInformation("Audit log entry created for user {UserId}.", notification.UserId);
}
}
--
5. Контроллер / PageModel становится чистым:
// В контроллере или PageModel
public class RegisterModel : PageModel
{
private readonly IMediator _mediator; // MediatR внедряется
public RegisterModel(IMediator mediator)
{
_mediator = mediator; // Получаем MediatR через DI
}
[BindProperty]
public CreateUserModel Input { get; set; } = new();
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
// 1. Отправляем команду через MediatR
var command = new CreateUserCommand(Input.Name, Input.Email);
var userId = await _mediator.Send(command); // MediatR находит и вызывает CreateUserCommandHandler
// 2. Контроллер не знает и не заботится о том, что происходит внутри обработчика или его подписчиках
// Все побочные эффекты (email, audit log) происходят асинхронно через события.
return RedirectToPage("/Success", new { Id = userId });
}
}
--
6. Регистрация в Program.cs:
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Добавляем MediatR. Он автоматически регистрирует IRequestHandler и INotificationHandler из сборки
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>());
// Регистрируем другие зависимости
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddScoped<IAuditLogService, DatabaseAuditLogService>();
var app = builder.Build();
app.MapRazorPages();
app.Run();
}
}
-----
Хорошо, давайте разберем **самый распространенный и "родной"** use-case для `MediatR` и посмотрим, **для чего он создан** и **что делает лучше всего**.
---
### Основное назначение MediatR
`MediatR` реализует паттерн **Mediator**. Его основная цель — **уменьшить связанность (decoupling)** между компонентами в приложении. Вместо того чтобы один класс напрямую вызывал методы другого (например, `orderService.ProcessOrder(...)`, `notificationService.Send(...)`), он отправляет **сообщение** (например, `ProcessOrderCommand`), и `MediatR` находит и вызывает соответствующий **обработчик** (`ProcessOrderCommandHandler`).
Это особенно полезно в архитектуре, ориентированной на **CQRS** (Command Query Responsibility Segregation), где:
* **Команды (Commands)** — это запросы на изменение состояния системы (например, `CreateUserCommand`, `UpdateOrderStatusCommand`). Они обрабатываются `IRequestHandler<,>` и не возвращают данные.
* **Запросы (Queries)** — это запросы на получение данных (например, `GetUserByIdQuery`, `GetOrdersByUserQuery`). Они обрабатываются `IRequestHandler<, TResponse>` и возвращают данные.
* **События (Events)** — это оповещения о том, что *что-то произошло* (например, `OrderCreatedEvent`, `PaymentFailedEvent`). Они обрабатываются `INotificationHandler<>` и могут вызываться множеством обработчиков.
---
### Самый распространенный и "родной" Use-case: **CQRS с обработкой команд и запросов в слое Application/Domain**
Это **классический сценарий** использования `MediatR` — обработка бизнес-операций (команд) и запросов данных (queries) в веб-приложении, например, в ASP.NET Core MVC или Razor Pages.
---
### Пример: Создание пользователя
Представим, что у нас есть ASP.NET Core приложение. Пользователь заполняет форму регистрации, и нам нужно:
1. Принять данные из формы.
2. Создать пользователя в системе.
3. Отправить ему приветственное письмо.
4. Записать событие в лог аудита.
Без `MediatR` это может выглядеть так:
```csharp
// В контроллере или PageModel
public async Task<IActionResult> OnPostAsync(CreateUserModel model)
{
if (!ModelState.IsValid)
return Page();
// 1. Создание пользователя
var user = new User { Name = model.Name, Email = model.Email };
await _userRepository.AddAsync(user);
// 2. Отправка письма
await _emailService.SendWelcomeEmailAsync(user.Email);
// 3. Запись в лог аудита
await _auditLogService.LogAsync($"User {user.Name} created at {DateTime.UtcNow}");
return RedirectToPage("/Success");
}
```
Код в контроллере **сильно связан** с `UserRepository`, `EmailService`, `AuditLogService`. Это затрудняет тестирование и усложняет добавление новой логики (например, отправки SMS).
---
### Как это выглядит с `MediatR`:
#### 1. Определение команды:
```csharp
using MediatR;
// Команда - запрос на изменение состояния
public record CreateUserCommand(string Name, string Email) : IRequest<Guid>; // Возвращаем ID созданного пользователя
```
#### 2. Определение обработчика команды:
```csharp
using MediatR;
using Microsoft.Extensions.Logging;
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, Guid>
{
private readonly IUserRepository _userRepository;
private readonly IPublisher _publisher; // MediatR также предоставляет IPublisher для отправки событий
private readonly ILogger<CreateUserCommandHandler> _logger;
public CreateUserCommandHandler(
IUserRepository userRepository,
IPublisher publisher, // Для отправки событий после создания
ILogger<CreateUserCommandHandler> logger)
{
_userRepository = userRepository;
_publisher = publisher;
_logger = logger;
}
public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
// 1. Создание и сохранение пользователя
var user = new User { Id = Guid.NewGuid(), Name = request.Name, Email = request.Email };
await _userRepository.AddAsync(user, cancellationToken);
// 2. Логика завершена, отправляем событие
var @event = new UserCreatedEvent(user.Id, user.Name, user.Email);
await _publisher.Publish(@event, cancellationToken);
_logger.LogInformation("User {UserId} created via command handler.", user.Id);
// 3. Возвращаем ID созданного пользователя
return user.Id;
}
}
```
#### 3. Определение события:
```csharp
using MediatR;
// Событие - произошло что-то
public record UserCreatedEvent(Guid UserId, string UserName, string UserEmail) : INotification;
```
#### 4. Определение обработчиков событий:
```csharp
using MediatR;
using Microsoft.Extensions.Logging;
public class WelcomeEmailNotificationHandler : INotificationHandler<UserCreatedEvent>
{
private readonly IEmailService _emailService;
private readonly ILogger<WelcomeEmailNotificationHandler> _logger;
public WelcomeEmailNotificationHandler(IEmailService emailService, ILogger<WelcomeEmailNotificationHandler> logger)
{
_emailService = emailService;
_logger = logger;
}
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// 1. Отправка письма
await _emailService.SendWelcomeEmailAsync(notification.UserEmail);
_logger.LogInformation("Welcome email sent to {UserEmail} for user {UserId}.", notification.UserEmail, notification.UserId);
}
}
public class AuditLogNotificationHandler : INotificationHandler<UserCreatedEvent>
{
private readonly IAuditLogService _auditLogService;
private readonly ILogger<AuditLogNotificationHandler> _logger;
public AuditLogNotificationHandler(IAuditLogService auditLogService, ILogger<AuditLogNotificationHandler> logger)
{
_auditLogService = auditLogService;
_logger = logger;
}
public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// 1. Запись в лог аудита
await _auditLogService.LogAsync($"User {notification.UserName} (ID: {notification.UserId}) created at {DateTime.UtcNow}");
_logger.LogInformation("Audit log entry created for user {UserId}.", notification.UserId);
}
}
```
#### 5. Контроллер / PageModel становится чистым:
```csharp
// В контроллере или PageModel
public class RegisterModel : PageModel
{
private readonly IMediator _mediator; // MediatR внедряется
public RegisterModel(IMediator mediator)
{
_mediator = mediator; // Получаем MediatR через DI
}
[BindProperty]
public CreateUserModel Input { get; set; } = new();
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
return Page();
// 1. Отправляем команду через MediatR
var command = new CreateUserCommand(Input.Name, Input.Email);
var userId = await _mediator.Send(command); // MediatR находит и вызывает CreateUserCommandHandler
// 2. Контроллер не знает и не заботится о том, что происходит внутри обработчика или его подписчиках
// Все побочные эффекты (email, audit log) происходят асинхронно через события.
return RedirectToPage("/Success", new { Id = userId });
}
}
```
#### 6. Регистрация в `Program.cs`:
```csharp
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Добавляем MediatR. Он автоматически регистрирует IRequestHandler и INotificationHandler из сборки
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>());
// Регистрируем другие зависимости
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
builder.Services.AddScoped<IAuditLogService, DatabaseAuditLogService>();
var app = builder.Build();
app.MapRazorPages();
app.Run();
}
}
```
---
### Что делает `MediatR` лучше всего:
1. **Децентрализация и слабая связанность:** Контроллер не зависит от `EmailService` или `AuditLogService`. Он просто отправляет команду. Обработчик команды и обработчики событий зависят от конкретных сервисов.
2. **Чистота слоя представления:** Контроллер/PageModel сосредоточен на обработке HTTP-запроса и возврате ответа, а не на бизнес-логике.
3. **Легкость добавления новой логики:** Хотите отправлять SMS при создании пользователя? Просто создайте новый `INotificationHandler<UserCreatedEvent>` и зарегистрируйте его. Контроллер и основной обработчик команды не изменяются.
4. **Тестируемость:** Легко тестировать обработчик команды, подставив моки репозитория и `IPublisher`.
5. **Четкое разделение ответственности (CQRS):** Команды для изменений, Запросы для чтения. Это улучшает понимание архитектуры.
6. **Поддержка событийного взаимодействия:** Позволяет легко реализовать паттерн "Event Sourcing" или просто реагировать на события.
**Именно этот сценарий — обработка команд и запросов в бизнес-логике с использованием событий для побочных эффектов — является "родной" и наиболее распространенной областью применения `MediatR`.**
Комментариев нет:
Отправить комментарий