CQRS (разделение ответственности за выполнение команд и запросов) — это шаблон проектирования, который разделяет ваши операции READ и WRITE/UPDATE/DELETE в вашем коде.

На практике CQRS — очень простой паттерн, не требующий больших вложений. Его можно легко расширить с помощью более сложных методов, таких как архитектура, управляемая событиями, источник событий или многоязычное постоянство. Но они не всегда нужны. Даже без применения каких-либо дополнительных шаблонов CQRS может предложить лучшую развязку и более легкую для понимания структуру кода.

TLDR — вы можете получить общее представление, просто взглянув на это изображение

Вариант использования

Давайте рассмотрим базовую CRUD-модель для заказов в ресторане и ту же бизнес-модель — клиент размещает заказ, редко обновляет или удаляет его, но один и тот же заказ видят покупатель, кассир, повара и другие люди в между. Эта модель является моделью READ Heavy.

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

Это классический случай системы, управляемой данными, где у нас есть новые сущности, вложенные друг в друга, связанные друг с другом, и давайте добавим к этому миксу СОБЫТИЯ — такие как OrderTaken, OrderCooked, OrderDeliverd, BillPaid и другие.

Вдобавок к этому мы расширяемся за счет новых услуг, например, клиенты могут заказывать онлайн, они могут запрашивать небольшие изменения в ингредиентах — в основном мы добавляем массу новых вариантов использования в бизнесе по мере создания системы.

Что такое CQRS

Идея CQRS состоит в том, чтобы разделить приложение на два разных раздела, которые работают с разными моделями:

  • КОМАНДА "Создать-обновить-удалить", которая отвечает за создание или изменение данных, и ЗАПРОС, который извлекает данные.
  • У него также есть внутренняя модель, с которой он пишет, и одна или несколько моделей, с помощью которых мы можем читать (подробнее об этом позже).

Команды

Commands это то, что пишет модель. Он используется для создания нового объекта или изменения состояния объекта.

  • Command — это операция, основанная на задачах, а не на данных.
  • Command может быть вызовом функции с API.
  • Command можно поместить в очередь для асинхронной обработки (события, задачи).

В каком-то смысле Command может изменить состояние, создать сущность, может вызвать изменения в системе прямо или косвенно. В терминологии API это определяется как Create/Update/Delete : Entity

Command ориентирован на поведение, а не на данные. Речь идет о намерении что-то изменить, оно не соответствует формату ресурса (например, DTO в API). Он может содержать данные, которые помогут обработать намерение, но не более того.

Мы также говорим о системах, основанных на задачах, а не о системах, основанных на ресурсах. Часть записи не принимает новые ресурсы или исправления существующих ресурсов: они принимают задачи, также известные как команды.

Запрос

Queries — это методы, которые используются для чтения данных. Эти операции не изменяют данные, как вы можете себе представить, при этом у нас есть несколько моделей данных, которые мы можем комбинировать для ответа API. Например, UserModel и OrderModel для отображения всех заказов, сделанных указанным пользователем.

Преимущества этой системы проявляются, когда мы начинаем добавлять другие компоненты в нашу архитектуру.

  • Добавление кеша
  • Более быстрые запросы (у нас есть 3 БД для чтения и 1 БД для записи)
  • Делайте много предварительных вычислений на серверной части — помогает поддерживать легкость на стороне клиента.
  • Высокая задержка — есть разница между быстрыми запросами и этим ;)

Течение

Поток команд, здесь модель API, используемая клиентом, может делать две вещи:

  1. Он может создать объект, используя, отображая и проверяя внутреннюю модель.
  2. Он может вызвать событие, которое отвечает за изменение состояния объекта.

Поток запросов довольно прямолинеен, у вас есть потребность на стороне клиента, то есть либо —

  1. Является частью единой внутренней модели, где извлекается напрямую.
  2. Или это комбинация внутренних моделей, и есть некоторая агрегация моделей.

Преимущества CQRS

  • Непротиворечивость. Гораздо проще обрабатывать транзакции с непротиворечивыми данными, чем обрабатывать все крайние случаи, которые могут возникнуть в результате непротиворечивости. Большинство систем в конечном итоге могут быть непротиворечивыми на стороне запросов.
  • Хранение данных. Командная сторона, являющаяся обработчиком транзакций в реляционной структуре, хотела бы хранить данные в нормализованном виде, вероятно, близком к 3-й нормальной форме (3NF). Стороне запроса нужны данные денормализованным способом, чтобы минимизировать количество соединений, необходимых для получения заданного набора данных. В реляционной структуре, вероятно, в 1-й нормальной форме (1NF).
  • Независимое масштабирование. CQRS позволяет независимо масштабировать рабочие нагрузки чтения и записи и может привести к меньшему количеству конфликтов за блокировку.
  • Оптимизированные схемы данных. Сторона чтения может использовать схему, оптимизированную для запросов, а сторона записи — схему, оптимизированную для обновлений.
  • Безопасность. Легче обеспечить, чтобы записи данных выполнялись только правильными объектами домена.
  • Разделение ответственности. Разделение сторон чтения и записи может привести к созданию более удобных и гибких моделей. Большая часть сложной бизнес-логики входит в модель записи. Модель чтения может быть относительно простой.
  • Простые запросы. Сохраняя материализованное представление в базе данных для чтения, приложение может избежать сложных объединений при выполнении запросов.

Когда использовать CQRS

Рассмотрите CQRS для следующих сценариев:

  • Совместные домены, в которых множество пользователей получают доступ к одним и тем же данным параллельно. CQRS позволяет вам определять команды с достаточной степенью детализации, чтобы свести к минимуму конфликты слияния на уровне домена, а возникающие конфликты могут быть объединены командой.
  • Пользовательские интерфейсы на основе задач, в которых пользователи проходят сложный процесс в виде последовательности шагов или сложных моделей предметной области. Модель записи имеет полный стек обработки команд с бизнес-логикой, проверкой ввода и бизнес-проверкой. Модель записи может рассматривать набор связанных объектов как единую единицу для изменения данных (агрегат в терминологии DDD) и гарантировать, что эти объекты всегда находятся в согласованном состоянии. Модель чтения не имеет бизнес-логики или стека проверки и просто возвращает DTO для использования в модели представления. Модель чтения в конечном итоге согласуется с моделью записи.
  • Сценарии, в которых производительность операций чтения данных необходимо настраивать отдельно от производительности операций записи данных, особенно когда количество операций чтения значительно превышает количество операций записи. В этом сценарии вы можете масштабировать модель чтения, но запускать модель записи только на нескольких экземплярах. Небольшое количество экземпляров модели записи также помогает свести к минимуму возникновение конфликтов слияния.
  • Сценарии, в которых одна группа разработчиков может сосредоточиться на сложной модели предметной области, являющейся частью модели записи, а другая команда может сосредоточиться на модели чтения и пользовательском интерфейсе.
  • Сценарии, в которых ожидается, что система будет развиваться с течением времени и может содержать несколько версий модели, или где бизнес-правила регулярно меняются.
  • Интеграция с другими системами, особенно в сочетании с источниками событий, когда временный сбой одной подсистемы не должен влиять на доступность других.

Этот шаблон не рекомендуется использовать, когда:

  • Домен или бизнес-правила просты.
  • Достаточно простого пользовательского интерфейса в стиле CRUD и операций доступа к данным.

Рассмотрите возможность применения CQRS к ограниченным разделам вашей системы, где это будет наиболее ценно.

Посмотрим, как это выглядит в коде

В этой части будет не «Код», а общий низкоуровневый дизайн, который помог мне понять CQRS и реализовать его в моих личных проектах, а также в некоторых проектах, над которыми я работал — CQRS действительно изменил мой способ написания микросервисов, которые являются ДДД.

Controller Orders
- GET/Orders?id
- GET/Orders
- POST/Orders
- PUT/Orders
- DELTE/Orders?id
Controller Restaurant 
- GET/Restaurant
- GET/Restaurant?id 
- POST/Restaurant
- PUT/Restaurant
- DELTE/Restaurant?id
Controller Dish 
- GET/Dish
- GET/Dish?id 
- POST/Dish
- PUT/Dish
- DELTE/Dish?id
User 
- GET/User?id

Давайте определим модели запросов

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

Давайте определим модели команд

Теперь самое интересное, обработчик — давайте посмотрим

Интеграция в существующее приложение.

CQRS довольно гибок, вот несколько моих советов по добавлению CQRS и использованию его преимуществ.

  1. Начните с одного сервиса или API — это, хотя и не добавит прироста производительности, но сделает код более читабельным и упростит добавление новых функций и возможностей.
  2. Разделите свои базы данных — один набор (я сказал набор, потому что в масштабе вы бы разделили свою БД), который в основном используется для записи, и один набор для чтения. Вы можете сказать, что это что-то вроде инфрауровня CQRS?
  3. Разделите свои сервисы — Набор сервисов, которые используются только для записи в базу данных, которые связаны только с «базой набора записей», а другие только для чтения, явно связанные с «базой набора чтения», с этим вы добились очень высокий уровень разделения. Эта структура очень полезна, когда вы используете REST для записи, обновления, удаления и GraphQL для ПОЛУЧЕНИЯ данных.

Что хорошо сочетается с CQRS

  1. Кэширование — это хорошо, потому что у вас есть модели для написания, которые связаны с сервисами — поэтому легко выполнить инвалидацию кеша. Да, я бы сказал, что его легко аннулировать как больший профи, чем производительность.
  2. Event Sourcing — я скоро напишу об этом

Сноски