CQRS и MediatR
↑ 8.2 Архитектурные стили и DDD · ← Предыдущая · → Следующая
Зачем это на собесе
CQRS упоминают везде; нужно различать CQRS как принцип и «тяжёлый» вариант с Event Sourcing и отдельными хранилищами.
Объяснение
CQRS (Command Query Responsibility Segregation) — разделение модели записи (команды меняют состояние) и модели чтения (запросы возвращают данные).
| Уровень | Что | Когда |
|---|---|---|
| Простой | отдельные обработчики команд и запросов, одна БД | почти всегда полезен |
| Раздельные модели | запись — доменная модель, чтение — проекции/DTO (Dapper, view) | разные требования к чтению и записи |
| Раздельные хранилища | чтение из денормализованного хранилища (реплика, ClickHouse, Elasticsearch), синхронизация событиями | высокая нагрузка на чтение, eventual consistency |
| CQRS + Event Sourcing | запись — поток событий | особые требования к аудиту |
public record CreateOrder(Guid CustomerId, List<ItemDto> Items) : IRequest<Guid>; // команда
public record GetOrder(Guid Id) : IRequest<OrderDto?>; // запрос
public class CreateOrderHandler(AppDbContext db) : IRequestHandler<CreateOrder, Guid> { /* меняет состояние */ }
public class GetOrderHandler(IDbConnection conn) : IRequestHandler<GetOrder, OrderDto?> { /* читает Dapper-ом */ }
builder.Services.AddMediatR(c => c.RegisterServicesFromAssemblyContaining<Program>());
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); // сквозная валидация
var id = await mediator.Send(new CreateOrder(customerId, items), ct);MediatR — реализация паттерна Mediator: Send для запросов/команд, Publish для уведомлений, IPipelineBehavior для сквозных задач (валидация, логирование, транзакция). Альтернативы: Wolverine, свой мини-диспетчер, прямые вызовы обработчиков (Vertical Slice).
Нюансы и подводные камни
- MediatR — не обязательная часть CQRS; в новых версиях лицензия изменилась (коммерческая для организаций), рассмотрите альтернативы.
- Разные хранилища читателей приводят к eventual consistency: клиент может не видеть свою запись сразу.
- Команда не должна возвращать данные для отображения (максимум идентификатор).
- Скрытая «магия» Mediator усложняет навигацию по коду.
- CQRS для простого CRUD — избыточен.
Практика
- Разделите один сервис на команды и запросы с MediatR.
- Добавьте pipeline behavior валидации и логирования.
- Реализуйте read-модель списка заказов через Dapper.
Вопросы с ответами
Что такое CQRS?
Разделение операций записи и чтения на разные модели/обработчики с независимой оптимизацией.
Обязательно ли CQRS требует Event Sourcing?
Нет: это независимые паттерны, хотя часто используются вместе.