Определение CQRS
CQRS означает, что Commands изменяют состояние через write-модель, а Queries читают модели, построенные под конкретные вопросы. Сторона записи отвечает за решения и инварианты. Сторона чтения отвечает за формы данных для UI, поиска, аналитики или операций.
Интерес к запросу «CQRS» обычно включает три вопроса: что означает CQRS, чем он отличается от Event Sourcing и когда паттерн полезнее обычного CRUD.
- Commands выражают бизнес-намерение на стороне записи
- Queries используют отдельные read models или Projections
- Инварианты остаются в Aggregates, а не в Projection handlers
- Eventual consistency — явный tradeoff, а не случайность
Когда CQRS окупается
CQRS оправдан, когда форма записи отражает сложные решения, а чтение требует другого представления: плоских списков, полнотекстового поиска, аналитики, денормализованных карточек или данных из нескольких агрегатов. Раздельные модели позволяют защищать домен, не заставляя интерфейс восстанавливать удобное представление из сложного графа объектов.
Для простого CRUD с одинаковыми формами чтения и записи CQRS может добавить лишние типы, обработчики и точки отказа. Используйте его для конкретной сложности, а не как обязательный атрибут DDD.
- Сложные инварианты на стороне записи
- Несколько существенно разных моделей чтения
- Высокая или независимая нагрузка на чтение
- Необходимость объединять данные разных границ
- Разные требования к масштабированию и хранению
Путь команды
Команда выражает намерение и содержит данные, необходимые для решения. Обработчик определяет адресата, загружает агрегат, вызывает доменное поведение и сохраняет результат. Агрегат принимает или отклоняет намерение на основании текущего состояния и инвариантов.
Успешное решение создаёт доменное событие. Обработчик не должен дублировать правила агрегата в application layer. Команда может завершиться синхронным подтверждением принятия, но это не означает, что все модели чтения уже обновлены.
Проекции и модели чтения
Проекция строит представление под конкретный вопрос: OrderSummary, TicketBoard, CustomerBalance. Она обрабатывает события и обновляет структуру, удобную для запроса. В одной системе может быть несколько проекций одного факта — для UI, поиска, отчётности или интеграции.
Храните в модели чтения только необходимые данные и проектируйте её контракт от потребителя. Она не должна становиться вторым доменным агрегатом: проекция отображает факты, но не принимает бизнес-решения.
Согласованность и ожидания пользователя
При асинхронном обновлении модели чтения возникает eventual consistency: команда уже принята, а новое состояние ещё не видно в запросе. Это нужно учитывать в интерфейсе и API. Можно вернуть идентификатор операции, показать локально подтверждённое изменение, дождаться определённой версии проекции или явно отобразить статус обработки.
Не обещайте read-your-writes, если архитектура его не обеспечивает. Для критичных сценариев допустима синхронная проекция или чтение подтверждения из write side, но такое решение должно быть осознанным.
Идемпотентность и порядок событий
Обработчик проекции должен безопасно переживать повторную доставку. Храните обработанную позицию, версию события или уникальный идентификатор и не применяйте одно изменение дважды. Если порядок важен, определите его границу: глобальный порядок обычно дорог и не нужен, а порядок событий одного агрегата можно контролировать версией.
Позднее событие не должно незаметно перезаписывать более новое состояние. Правило разрешения конфликтов является частью дизайна конкретной проекции.
Перестроение и версия проекции
Проекции являются производными данными, поэтому их желательно уметь перестраивать из надёжного источника событий или другого журнала изменений. При изменении схемы создайте новую версию, заполните её параллельно и переключите запросы после проверки, вместо рискованного изменения рабочей таблицы.
Если полной истории событий нет, заранее определите источник backfill: snapshot write-модели, журнал интеграционных событий или миграционный запрос. CQRS не гарантирует автоматическую перестройку без такого источника.
CQRS и Event Sourcing — разные решения
CQRS разделяет обязанности команд и запросов. Event Sourcing хранит состояние как последовательность событий. Их часто используют вместе, но можно применять независимо: CQRS с обычной транзакционной write-моделью или Event Sourcing с простым интерфейсом запросов.
Выбирайте Event Sourcing только когда история решений, временные запросы или восстановление состояния оправдывают сложность версионирования событий и миграций.
Ошибки внедрения
Недостаточно создать CommandDTO и QueryDTO поверх одной анемичной модели. Другой перекос — сразу разделить приложение на сервисы и базы, хотя задача решается двумя модулями в одном процессе. Избегайте общей универсальной модели чтения, которое снова начинает обслуживать все запросы.
Не помещайте бизнес-правила в обработчики проекций. Если проекция решает, допустимо ли изменение, она превратилась в скрытую write-модель.
Чек-лист CQRS-дизайна
Перед внедрением зафиксируйте, какую конкретную сложность снимает разделение и как система работает при задержке или повторе событий.
- Команды названы как бизнес-намерения?
- Инварианты остаются в агрегатах?
- Каждая проекция отвечает на конкретный вопрос?
- Определено допустимое отставание модели чтения?
- Обработчики проекций идемпотентны?
- Есть стратегия порядка и поздних событий?
- Понятно, как перестроить или мигрировать проекцию?
- Интерфейс корректно показывает асинхронную обработку?
FAQ по CQRS
Что такое CQRS?
CQRS — Command Query Responsibility Segregation. Это разделение модели, которая принимает Commands и защищает бизнес-инварианты, и моделей, оптимизированных для чтения. CQRS — это разделение ответственности, а не обязательный набор брокеров, микросервисов или Event Sourcing.
Когда стоит использовать CQRS?
CQRS полезен, когда на стороне записи есть содержательные решения, а чтение требует другой формы данных: списки, поиск, аналитика, денормализованные карточки или данные из нескольких Aggregates. Для простого CRUD с похожими формами чтения и записи CQRS часто добавляет лишнюю сложность.
CQRS и Event Sourcing — это одно и то же?
Нет. CQRS разделяет ответственность Commands и Queries. Event Sourcing хранит состояние как последовательность событий. Их часто комбинируют, но можно использовать CQRS с обычной транзакционной write-моделью или Event Sourcing с простым query-интерфейсом.
Что такое projection или read model в CQRS?
Projection строит модель чтения под конкретный вопрос, например OrderSummary или TicketBoard. Она обрабатывает Domain Events и обновляет удобную для запросов структуру. Read model показывает факты для UI или отчётов и не должна становиться вторым местом для бизнес-решений.
Обязательна ли eventual consistency в CQRS?
Для асинхронных projection часто да, но не всегда. Если projection обновляется асинхронно, Command может уже завершиться, а Query ещё вернуть старые данные. UI и API должны учитывать эту задержку. Для критичных сценариев можно использовать синхронное подтверждение, если tradeoff осознан.
DDDesigner
Пример: OrderSummary и доска выполнения заказов
Агрегат Order принимает PlaceOrder, ConfirmOrder и CancelOrder, защищая правила жизненного цикла. События OrderPlaced, OrderConfirmed, OrderPaid и OrderCancelled обновляют OrderSummary для списка заказов. Отдельная FulfillmentBoard объединяет факты InventoryReserved, PaymentCaptured и DeliveryScheduled для операционной команды. Повторное событие отбрасывается по идентификатору, версия агрегата защищает порядок, а интерфейс после команды показывает статус обработки, пока нужная версия проекции не будет применена.
Начать бесплатно