ConstructiCat Logo
CodeBust.
Browse section ▾

CQRS.

Also known as — Command Query Responsibility Segregation

Разделите модель, изменяющую состояние (команды), и модель, читающую состояние (запросы), чтобы каждую сторону можно было моделировать, масштабировать и оптимизировать независимо.

##Intent

Используйте одну модель для обновления информации и другую модель для её чтения. Команды выражают намерение изменить состояние; запросы возвращают данные в форме, удобной для отображения. Этим двум моделям больше не нужно идти на компромисс ради единого общего представления.

##Problem

В традиционном дизайне одна модель обслуживает и запись, и чтение. Записи требуют богатой валидации и инвариантов; чтения требуют денормализованных, готовых к отображению форм — нередко множества разных форм для разных экранов и отчётов.

Принуждение проводить и то и другое через одни и те же объекты и таблицы ведёт к неуклюжим компромиссам: избыточная выборка данных, сложные ORM-отображения, конкуренция за блокировки и модель, которая плохо справляется с обеими задачами.

##Solution

CQRS разделяет систему на две части. Сторона записи обрабатывает команды через обработчики, которые загружают агрегат, обеспечивают соблюдение инвариантов и сохраняют изменение (опционально порождая события). Сторона чтения обслуживает запросы из одной или нескольких моделей чтения, сформированных специально под потребляющие их представления.

Эти две стороны могут использовать общую базу данных или раздельные хранилища. Когда они раздельны, модели чтения поддерживаются в актуальном состоянии путём проецирования событий со стороны записи, принимая итоговую согласованность в обмен на независимое масштабирование и более простые запросы.

##Structure

  • Команда (Command) — запрос на изменение состояния, названный по намерению (PlaceOrder).
  • Обработчик команды (Command handler) — валидирует и применяет команду к модели записи.
  • Модель записи (Write model) — агрегаты, обеспечивающие соблюдение инвариантов; могут порождать события.
  • Модель чтения / проекция (Read model / projection) — денормализованные представления, построенные под запросы и обновляемые из событий.
  • Запрос (Query) & обработчик запроса (query handler) — запросы только для чтения, обслуживаемые из модели чтения.

##Applicability

  • Применяйте его там, где чтение и запись имеют сильно различающуюся форму или нагрузку — множество представлений для чтения, тяжёлая отчётность или высокое соотношение чтения к записи.
  • Применяйте его для интерфейсов, основанных на задачах, и совместно используемых предметных областей, где он естественно сочетается с предметно-ориентированным проектированием (DDD) и event sourcing.
  • Избегайте его для простого CRUD; единая модель проще, а разделение добавляет издержки без отдачи.

##How to Implement

  1. Моделируйте команды как явные намерения, а не как обобщённые обновления.
  2. Направляйте каждую команду в обработчик, который загружает соответствующий агрегат и обеспечивает соблюдение его инвариантов.
  3. Сохраняйте изменение и, если используются события, публикуйте произошедшее.
  4. Стройте модели чтения, адаптированные под ваши представления; если они отделены, обновляйте их, проецируя события.
  5. Обслуживайте запросы напрямую из моделей чтения — никакой доменной логики на стороне чтения.

##Pros & Cons

Pros
  • Стороны чтения и записи можно моделировать, оптимизировать и масштабировать независимо.
  • Каждая модель остаётся небольшой и сосредоточенной на одной задаче.
  • Подходит для интерфейсов, основанных на задачах, и хорошо сочетается с event sourcing и DDD.
  • Модели чтения можно адаптировать под каждое представление, устраняя неуклюжие соединения (joins).
Cons
  • Больше движущихся частей и больше кода, чем у единой CRUD-модели.
  • Отдельные хранилища для чтения вводят итоговую согласованность (eventual consistency), которую должен учитывать UX.
  • Легко переусложнить; редко оправдано для простых предметных областей.
  • Эксплуатационные издержки на проекции и инфраструктуру обмена сообщениями.