DDDesigner

CQRS

Что такое CQRS? Command Query Responsibility Segregation

CQRS (Command Query Responsibility Segregation) разделяет модель, которая принимает Commands и защищает бизнес-инварианты, и модели, оптимизированные для чтения. Это решение о разных ответственностях, а не обязательный технологический стек. CQRS может жить в одном приложении и одной базе; микросервисы, Event Sourcing и брокеры сообщений необязательны.

Определение 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 для операционной команды. Повторное событие отбрасывается по идентификатору, версия агрегата защищает порядок, а интерфейс после команды показывает статус обработки, пока нужная версия проекции не будет применена.

Начать бесплатно