вторник, 18 ноября 2025 г.

25.11.19, Qwen, MediatR, MainUse

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`.**

Комментариев нет:

Отправить комментарий