1 added
1 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#статьи</a></p>
1
<p><a>#статьи</a></p>
2
<ul><li>19 июн 2025</li>
2
<ul><li>19 июн 2025</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Отделяем мух от котлет команды от запросов.</p>
4
</ul><p>Отделяем мух от котлет команды от запросов.</p>
5
<p>Иллюстрация: Polina Vari для Skillbox Media</p>
5
<p>Иллюстрация: Polina Vari для Skillbox Media</p>
6
<p>Пишет о сетях, инструментах для разработчиков и языках программирования. Любит готовить, играть в инди‑игры и программировать на Python.</p>
6
<p>Пишет о сетях, инструментах для разработчиков и языках программирования. Любит готовить, играть в инди‑игры и программировать на Python.</p>
7
<p>Когда в приложении становится слишком много операций чтения, система начинает тормозить. Особенно если при этом количество изменений в данных остаётся небольшим. В таких ситуациях классическая архитектура CRUD даёт сбой: одно и то же хранилище обрабатывает и запросы, и команды, что приводит к конкуренции за ресурсы и снижению производительности.</p>
7
<p>Когда в приложении становится слишком много операций чтения, система начинает тормозить. Особенно если при этом количество изменений в данных остаётся небольшим. В таких ситуациях классическая архитектура CRUD даёт сбой: одно и то же хранилище обрабатывает и запросы, и команды, что приводит к конкуренции за ресурсы и снижению производительности.</p>
8
<p>Решить эту проблему помогает CQRS - архитектурный паттерн, который разделяет обработку команд и запросов. Такой подход особенно полезен в микросервисной архитектуре, где нагрузка может быть неравномерной.</p>
8
<p>Решить эту проблему помогает CQRS - архитектурный паттерн, который разделяет обработку команд и запросов. Такой подход особенно полезен в микросервисной архитектуре, где нагрузка может быть неравномерной.</p>
9
<p>В этой статье разберём, как работает CQRS, какие задачи он решает и чем может быть полезен в реальных проектах.</p>
9
<p>В этой статье разберём, как работает CQRS, какие задачи он решает и чем может быть полезен в реальных проектах.</p>
10
<p><strong>Содержание</strong></p>
10
<p><strong>Содержание</strong></p>
11
<ul><li><a>Что такое CQRS</a></li>
11
<ul><li><a>Что такое CQRS</a></li>
12
<li><a>Из чего состоит CQRS</a></li>
12
<li><a>Из чего состоит CQRS</a></li>
13
<li><a>CQRS на примере</a></li>
13
<li><a>CQRS на примере</a></li>
14
<li><a>Преимущества и недостатки паттерна</a></li>
14
<li><a>Преимущества и недостатки паттерна</a></li>
15
<li><a>Что в итоге</a></li>
15
<li><a>Что в итоге</a></li>
16
</ul><p><strong>CQRS</strong>(command and query responsibility segregation - "разделение обязанностей команд и запросов") - это архитектурный паттерн, который предлагает разделять операции изменения данных (<strong>команды</strong>) и чтения данных (<strong>запросы</strong>). Такой подход помогает упростить логику приложения, особенно если нагрузка на чтение и запись существенно различается.</p>
16
</ul><p><strong>CQRS</strong>(command and query responsibility segregation - "разделение обязанностей команд и запросов") - это архитектурный паттерн, который предлагает разделять операции изменения данных (<strong>команды</strong>) и чтения данных (<strong>запросы</strong>). Такой подход помогает упростить логику приложения, особенно если нагрузка на чтение и запись существенно различается.</p>
17
<p>Представьте себе блог-платформу. Ежедневно десятки авторов публикуют новые статьи, а миллионы пользователей их читают. Количество операций чтения здесь в десятки раз больше, чем операций изменения. В таких случаях CQRS позволяет оптимизировать работу приложения: отдельно проектируется модель для чтения, отдельно - для записи.</p>
17
<p>Представьте себе блог-платформу. Ежедневно десятки авторов публикуют новые статьи, а миллионы пользователей их читают. Количество операций чтения здесь в десятки раз больше, чем операций изменения. В таких случаях CQRS позволяет оптимизировать работу приложения: отдельно проектируется модель для чтения, отдельно - для записи.</p>
18
<p>Суть паттерна - в разделении двух типов операций:</p>
18
<p>Суть паттерна - в разделении двух типов операций:</p>
19
<ul><li><strong>Команды (commands)</strong> - изменяют состояние системы. Например, создать статью, обновить профиль или удалить комментарий. Команды обычно не возвращают данные, а лишь инициируют изменение.</li>
19
<ul><li><strong>Команды (commands)</strong> - изменяют состояние системы. Например, создать статью, обновить профиль или удалить комментарий. Команды обычно не возвращают данные, а лишь инициируют изменение.</li>
20
<li><strong>Запросы (queries)</strong> - читают данные без их изменения. Их задача - как можно быстрее вернуть нужную информацию. Часто для этого используют специализированные модели, базы данных или кэш.</li>
20
<li><strong>Запросы (queries)</strong> - читают данные без их изменения. Их задача - как можно быстрее вернуть нужную информацию. Часто для этого используют специализированные модели, базы данных или кэш.</li>
21
</ul><p>Запросы и команды работают независимо друг от друга, что делает архитектуру гибкой и более предсказуемой при нагрузках. Всегда можно понять, из-за чего именно тормозит система, и оптимизировать именно эту часть приложения.</p>
21
</ul><p>Запросы и команды работают независимо друг от друга, что делает архитектуру гибкой и более предсказуемой при нагрузках. Всегда можно понять, из-за чего именно тормозит система, и оптимизировать именно эту часть приложения.</p>
22
<p>Представьте, что перед вами бэкенд онлайн-магазина, разработанный на базе паттерна CQRS. В системе есть<strong>команды</strong>, которые вносят изменения в базу данных (создают и редактируют заказы), и <strong>запросы</strong>(выводят информацию о заказах). Помимо этого, у нашего магазина есть пользовательский интерфейс, с помощью которого с ним взаимодействуют покупатели, и отдельные базы данных для команд и запросов. Для связи между базами данных предусмотрен модуль синхронизации, который обновляет данные.</p>
22
<p>Представьте, что перед вами бэкенд онлайн-магазина, разработанный на базе паттерна CQRS. В системе есть<strong>команды</strong>, которые вносят изменения в базу данных (создают и редактируют заказы), и <strong>запросы</strong>(выводят информацию о заказах). Помимо этого, у нашего магазина есть пользовательский интерфейс, с помощью которого с ним взаимодействуют покупатели, и отдельные базы данных для команд и запросов. Для связи между базами данных предусмотрен модуль синхронизации, который обновляет данные.</p>
23
Схема паттерна CQRS<em>Инфографика: Polina Vari для Skillbox Media</em><p>Команды и запросы не могут существовать сами по себе. Их объединяют в модули - файлы, в которых содержатся все необходимые функции, классы и переменные. Рассмотрим, какие модули чаще всего встречаются в проектах на основе паттерна CQRS.</p>
23
Схема паттерна CQRS<em>Инфографика: Polina Vari для Skillbox Media</em><p>Команды и запросы не могут существовать сами по себе. Их объединяют в модули - файлы, в которых содержатся все необходимые функции, классы и переменные. Рассмотрим, какие модули чаще всего встречаются в проектах на основе паттерна CQRS.</p>
24
<p>Если пользователь добавит товары в корзину и оформит заказ, то эти операции обработает модуль команд:</p>
24
<p>Если пользователь добавит товары в корзину и оформит заказ, то эти операции обработает модуль команд:</p>
25
<ul><li><strong>Обработчик</strong>примет имя пользователя, наименование товара, количество и внесёт информацию о заказе в базу данных.</li>
25
<ul><li><strong>Обработчик</strong>примет имя пользователя, наименование товара, количество и внесёт информацию о заказе в базу данных.</li>
26
<li><strong>Валидатор</strong>проверит команды на корректность входных данных и убедится, что пользователь авторизован. Например, если покупатель закажет товар, которого нет на складе, то система выдаст ошибку.</li>
26
<li><strong>Валидатор</strong>проверит команды на корректность входных данных и убедится, что пользователь авторизован. Например, если покупатель закажет товар, которого нет на складе, то система выдаст ошибку.</li>
27
</ul><p>Пользователи онлайн-магазинов не только оформляют заказы, но и запрашивают информацию. Например, чтобы узнать статус доставки или скачать электронный чек. Эти операции не вносят изменений в базу данных, поэтому их выполняет отдельный модуль запросов:</p>
27
</ul><p>Пользователи онлайн-магазинов не только оформляют заказы, но и запрашивают информацию. Например, чтобы узнать статус доставки или скачать электронный чек. Эти операции не вносят изменений в базу данных, поэтому их выполняет отдельный модуль запросов:</p>
28
<ul><li><strong>Обработчик</strong>выдаёт данные в ответ на запрос (например, о скачивании чека заказа). Он может обращаться к отдельной базе данных, где хранится оптимизированная для чтения информация.</li>
28
<ul><li><strong>Обработчик</strong>выдаёт данные в ответ на запрос (например, о скачивании чека заказа). Он может обращаться к отдельной базе данных, где хранится оптимизированная для чтения информация.</li>
29
<li><strong>Оптимизатор</strong>ускоряет выполнение запросов. Например, для ускорения можно использовать специальные базы данных с быстрым доступом, индексирование и кэширование.</li>
29
<li><strong>Оптимизатор</strong>ускоряет выполнение запросов. Например, для ускорения можно использовать специальные базы данных с быстрым доступом, индексирование и кэширование.</li>
30
</ul><p>Для более удобной работы вместе с паттерном CQRS часто используют следующие элементы:</p>
30
</ul><p>Для более удобной работы вместе с паттерном CQRS часто используют следующие элементы:</p>
31
<ul><li><strong>Механизмы синхронизации</strong>- системы, которые согласуют данные между разделёнными системами чтения и записи информации. В нашем примере есть две базы: в одну система вносит изменения, а из другой получает данные. Модуль синхронизации обновляет информацию между базами, чтобы пользователи получали актуальные данные без задержек.</li>
31
<ul><li><strong>Механизмы синхронизации</strong>- системы, которые согласуют данные между разделёнными системами чтения и записи информации. В нашем примере есть две базы: в одну система вносит изменения, а из другой получает данные. Модуль синхронизации обновляет информацию между базами, чтобы пользователи получали актуальные данные без задержек.</li>
32
<li><strong>Агрегаты</strong>- группы объектов, объединённые общей бизнес-логикой и целостностью данных. Например, агрегатом может быть заказ, включающий в себя информацию о покупателе, товаре и платеже. В таком случае все изменения в заказе будут обрабатываться как единое целое.</li>
32
<li><strong>Агрегаты</strong>- группы объектов, объединённые общей бизнес-логикой и целостностью данных. Например, агрегатом может быть заказ, включающий в себя информацию о покупателе, товаре и платеже. В таком случае все изменения в заказе будут обрабатываться как единое целое.</li>
33
<li><strong>Сервисы домена</strong>-<strong></strong>сервисы, отвечающие за выполнение специфичных для предметной области операций, которые не укладываются в рамки одного агрегата. Так, сервис для расчёта скидок может объединять данные из нескольких заказов или даже внешних источников, чтобы определить окончательную стоимость покупки, когда логика распределена между разными частями системы.</li>
33
<li><strong>Сервисы домена</strong>-<strong></strong>сервисы, отвечающие за выполнение специфичных для предметной области операций, которые не укладываются в рамки одного агрегата. Так, сервис для расчёта скидок может объединять данные из нескольких заказов или даже внешних источников, чтобы определить окончательную стоимость покупки, когда логика распределена между разными частями системы.</li>
34
</ul><p>Чтобы лучше понять, как работает паттерн CQRS, разработаем бэкенд для онлайн-магазина на Python. В коде разделим модули запросов и обработки заказов. Это позволит нам редактировать модули независимо друг от друга.</p>
34
</ul><p>Чтобы лучше понять, как работает паттерн CQRS, разработаем бэкенд для онлайн-магазина на Python. В коде разделим модули запросов и обработки заказов. Это позволит нам редактировать модули независимо друг от друга.</p>
35
<p>Если вы пишете код на другом языке программирования, то вам будет относительно просто разобраться в нашем проекте. Если вы только начинаете изучать разработку, то мы рекомендуем обратить внимание на наше<a>руководство</a>по Python.</p>
35
<p>Если вы пишете код на другом языке программирования, то вам будет относительно просто разобраться в нашем проекте. Если вы только начинаете изучать разработку, то мы рекомендуем обратить внимание на наше<a>руководство</a>по Python.</p>
36
<p>В примере с онлайн-магазином можно применить две стратегии: написать весь бэкенд в одном файле или разделить проект на модули. Предположим, что пользователи чаще будут проверять статусы заказов, чем оформлять покупки. При этом перед праздниками будут возникать аномалии - будет увеличиваться как количество заказов, так и количество запросов к базе данных.</p>
36
<p>В примере с онлайн-магазином можно применить две стратегии: написать весь бэкенд в одном файле или разделить проект на модули. Предположим, что пользователи чаще будут проверять статусы заказов, чем оформлять покупки. При этом перед праздниками будут возникать аномалии - будет увеличиваться как количество заказов, так и количество запросов к базе данных.</p>
37
<p>Чтобы система справлялась с такими нагрузками и мы могли масштабировать её при необходимости, применим паттерн CQRS - то есть разделим ответственность за команды (изменения данных) и запросы (получение данных).</p>
37
<p>Чтобы система справлялась с такими нагрузками и мы могли масштабировать её при необходимости, применим паттерн CQRS - то есть разделим ответственность за команды (изменения данных) и запросы (получение данных).</p>
38
<p>Это ключевая идея CQRS: команды и запросы должны быть реализованы независимо друг от друга. На уровне структуры проекта это выглядит так:</p>
38
<p>Это ключевая идея CQRS: команды и запросы должны быть реализованы независимо друг от друга. На уровне структуры проекта это выглядит так:</p>
39
<ul><li><strong>models.py</strong> - имитация хранилища данных (вместо базы данных), описывает структуру заказов.</li>
39
<ul><li><strong>models.py</strong> - имитация хранилища данных (вместо базы данных), описывает структуру заказов.</li>
40
<li><strong>commands.py</strong> - функции для создания и обновления заказов.</li>
40
<li><strong>commands.py</strong> - функции для создания и обновления заказов.</li>
41
<li><strong>queries.py</strong> - функции для получения информации о заказе.</li>
41
<li><strong>queries.py</strong> - функции для получения информации о заказе.</li>
42
<li><strong>app.py</strong> - основной файл, объединяющий обе части.</li>
42
<li><strong>app.py</strong> - основной файл, объединяющий обе части.</li>
43
</ul><p>Чтобы не тратить время на настройку базы данных, мы будем хранить данные прямо в Python - в памяти. Такой подход подходит только для демонстрации. Если вы захотите использовать CQRS в реальном проекте, обязательно подключите полноценную СУБД.</p>
43
</ul><p>Чтобы не тратить время на настройку базы данных, мы будем хранить данные прямо в Python - в памяти. Такой подход подходит только для демонстрации. Если вы захотите использовать CQRS в реальном проекте, обязательно подключите полноценную СУБД.</p>
44
<p>Начнём с файла models.py. В нём опишем класс Order со следующими полями:</p>
44
<p>Начнём с файла models.py. В нём опишем класс Order со следующими полями:</p>
45
<ul><li>order_id - уникальный идентификатор заказа.</li>
45
<ul><li>order_id - уникальный идентификатор заказа.</li>
46
<li>customer - имя покупателя.</li>
46
<li>customer - имя покупателя.</li>
47
<li>product - название или объект товара.</li>
47
<li>product - название или объект товара.</li>
48
<li>quantity - количество товара.</li>
48
<li>quantity - количество товара.</li>
49
</ul><p>Класс Order - конструктор, с помощью которого мы сможем быстро создавать новые заказы в системе. Полностью код класса выглядит так:</p>
49
</ul><p>Класс Order - конструктор, с помощью которого мы сможем быстро создавать новые заказы в системе. Полностью код класса выглядит так:</p>
50
class Order: def __init__(self, order_id, customer, product, quantity): self.order_id = order_id self.customer = customer self.product = product self.quantity = quantity<p>Класс Order - это не просто шаблон для создания заказов. Чтобы им было удобно управлять, добавим в него вспомогательные функции:</p>
50
class Order: def __init__(self, order_id, customer, product, quantity): self.order_id = order_id self.customer = customer self.product = product self.quantity = quantity<p>Класс Order - это не просто шаблон для создания заказов. Чтобы им было удобно управлять, добавим в него вспомогательные функции:</p>
51
<ul><li>update() - обновляет поля заказа.</li>
51
<ul><li>update() - обновляет поля заказа.</li>
52
<li>to_dict() - преобразует объект заказа в словарь, чтобы, например, передать его во внешний сервис или сериализовать в JSON.</li>
52
<li>to_dict() - преобразует объект заказа в словарь, чтобы, например, передать его во внешний сервис или сериализовать в JSON.</li>
53
</ul><p>Вот как эти функции выглядят:</p>
53
</ul><p>Вот как эти функции выглядят:</p>
54
def update(self, customer=None, product=None, quantity=None): if customer: self.customer = customer if product: self.product = product if quantity: self.quantity = quantity def to_dict(self): return { "order_id": self.order_id, "customer": self.customer, "product": self.product, "quantity": self.quantity }<p>Также нам нужно хранилище для всех заказов. Вместо полноценной базы данных мы создадим словарь order_store, где ключом будет order_id, а значением - сам объект Order.</p>
54
def update(self, customer=None, product=None, quantity=None): if customer: self.customer = customer if product: self.product = product if quantity: self.quantity = quantity def to_dict(self): return { "order_id": self.order_id, "customer": self.customer, "product": self.product, "quantity": self.quantity }<p>Также нам нужно хранилище для всех заказов. Вместо полноценной базы данных мы создадим словарь order_store, где ключом будет order_id, а значением - сам объект Order.</p>
55
order_store = {}<p>Полностью код файла models.py выглядит так:</p>
55
order_store = {}<p>Полностью код файла models.py выглядит так:</p>
56
# Определяем модель заказа и глобальное хранилище (имитация базы данных). class Order: def __init__(self, order_id, customer, product, quantity): self.order_id = order_id self.customer = customer self.product = product self.quantity = quantity def update(self, customer=None, product=None, quantity=None): if customer: self.customer = customer if product: self.product = product if quantity: self.quantity = quantity def to_dict(self): return { "order_id": self.order_id, "customer": self.customer, "product": self.product, "quantity": self.quantity } # Глобальное хранилище заказов order_store = {}<p>Теперь мы можем создать заказ и сохранить его. Создадим и запишем в переменную order_store заказ пользователя Ивана, который купил два ноутбука:</p>
56
# Определяем модель заказа и глобальное хранилище (имитация базы данных). class Order: def __init__(self, order_id, customer, product, quantity): self.order_id = order_id self.customer = customer self.product = product self.quantity = quantity def update(self, customer=None, product=None, quantity=None): if customer: self.customer = customer if product: self.product = product if quantity: self.quantity = quantity def to_dict(self): return { "order_id": self.order_id, "customer": self.customer, "product": self.product, "quantity": self.quantity } # Глобальное хранилище заказов order_store = {}<p>Теперь мы можем создать заказ и сохранить его. Создадим и запишем в переменную order_store заказ пользователя Ивана, который купил два ноутбука:</p>
57
new_order = Order(1, "Иван", "Ноутбук", 2) order_store[new_order.order_id] = new_order<p>Наш онлайн-магазин уже умеет сохранять в памяти историю заказов пользователей. Теперь в файле commands.py создадим функции для управления заказами.</p>
57
new_order = Order(1, "Иван", "Ноутбук", 2) order_store[new_order.order_id] = new_order<p>Наш онлайн-магазин уже умеет сохранять в памяти историю заказов пользователей. Теперь в файле commands.py создадим функции для управления заказами.</p>
58
-
<p>Для простоты мы реализуе�� две команды:</p>
58
+
<p>Для простоты мы реализуем две команды:</p>
59
<ul><li>create_order - создаёт новый заказ.</li>
59
<ul><li>create_order - создаёт новый заказ.</li>
60
<li>update_order - редактирует уже существующий.</li>
60
<li>update_order - редактирует уже существующий.</li>
61
</ul><p>Функция create_order принимает данные в формате, который мы описали в файле models.py с помощью класса Order, и проверяет поле order_id. Если заказ с указанным номером уже есть в базе данных, то функция вернёт ошибку. В остальных случаях create_order создаст заказ и сохранит его в переменную order_store.</p>
61
</ul><p>Функция create_order принимает данные в формате, который мы описали в файле models.py с помощью класса Order, и проверяет поле order_id. Если заказ с указанным номером уже есть в базе данных, то функция вернёт ошибку. В остальных случаях create_order создаст заказ и сохранит его в переменную order_store.</p>
62
def create_order(order_id, customer, product, quantity): if order_id in order_store: raise Exception("Заказ с таким идентификатором уже существует.") order = Order(order_id, customer, product, quantity) order_store[order_id] = order # Генерируем событие создания заказа (можно интегрировать с event store) return {"event": "OrderCreated", "data": order.to_dict()}<p>Функция update_order также принимает данные в формате класса-конструктора Order и ищет номер заказа. Если заказа не существует, то функция изменит поля, если нет - выдаст ошибку "Заказ не найден.".</p>
62
def create_order(order_id, customer, product, quantity): if order_id in order_store: raise Exception("Заказ с таким идентификатором уже существует.") order = Order(order_id, customer, product, quantity) order_store[order_id] = order # Генерируем событие создания заказа (можно интегрировать с event store) return {"event": "OrderCreated", "data": order.to_dict()}<p>Функция update_order также принимает данные в формате класса-конструктора Order и ищет номер заказа. Если заказа не существует, то функция изменит поля, если нет - выдаст ошибку "Заказ не найден.".</p>
63
def update_order(order_id, customer=None, product=None, quantity=None): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] order.update(customer, product, quantity) # Генерируем событие обновления заказа return {"event": "OrderUpdated", "data": order.to_dict()}<p>Обе функции возвращают событие в формате {"event": "OrderUpdated", "data": order.to_dict()}. Благодаря этому мы можем передавать данные о заказах в сервисы аналитики, генерировать отчёты, выписывать чеки или автоматически отправлять оповещения на электронную почту.</p>
63
def update_order(order_id, customer=None, product=None, quantity=None): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] order.update(customer, product, quantity) # Генерируем событие обновления заказа return {"event": "OrderUpdated", "data": order.to_dict()}<p>Обе функции возвращают событие в формате {"event": "OrderUpdated", "data": order.to_dict()}. Благодаря этому мы можем передавать данные о заказах в сервисы аналитики, генерировать отчёты, выписывать чеки или автоматически отправлять оповещения на электронную почту.</p>
64
<p>Например, если пользователь Иван закажет два ноутбука, то функция вернёт событие с такими данными:</p>
64
<p>Например, если пользователь Иван закажет два ноутбука, то функция вернёт событие с такими данными:</p>
65
{'event': 'OrderCreated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 2}}<p>В нём:</p>
65
{'event': 'OrderCreated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 2}}<p>В нём:</p>
66
<ul><li>'event': 'OrderCreated' - статус события.</li>
66
<ul><li>'event': 'OrderCreated' - статус события.</li>
67
<li>'order_id': 1 - уникальный номер заказа.</li>
67
<li>'order_id': 1 - уникальный номер заказа.</li>
68
<li>'customer': 'Иван' - имя покупателя.</li>
68
<li>'customer': 'Иван' - имя покупателя.</li>
69
<li>'product': 'Ноутбук' - наименование товара.</li>
69
<li>'product': 'Ноутбук' - наименование товара.</li>
70
<li>'quantity': 2 - количество.</li>
70
<li>'quantity': 2 - количество.</li>
71
</ul><p>Полностью код файла commands.py выглядит так:</p>
71
</ul><p>Полностью код файла commands.py выглядит так:</p>
72
from models import Order, order_store def create_order(order_id, customer, product, quantity): if order_id in order_store: raise Exception("Заказ с таким идентификатором уже существует.") order = Order(order_id, customer, product, quantity) order_store[order_id] = order # Генерируем событие создания заказа (можно интегрировать с event store) return {"event": "OrderCreated", "data": order.to_dict()} def update_order(order_id, customer=None, product=None, quantity=None): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] order.update(customer, product, quantity) # Генерируем событие обновления заказа return {"event": "OrderUpdated", "data": order.to_dict()}<p>Важная часть любого онлайн-магазина - возможность проверить статус заказа. Без неё пользователи не смогут отслеживать доставку, а служба поддержки не сможет узнать, что покупатель заказал, чтобы оформить возврат.</p>
72
from models import Order, order_store def create_order(order_id, customer, product, quantity): if order_id in order_store: raise Exception("Заказ с таким идентификатором уже существует.") order = Order(order_id, customer, product, quantity) order_store[order_id] = order # Генерируем событие создания заказа (можно интегрировать с event store) return {"event": "OrderCreated", "data": order.to_dict()} def update_order(order_id, customer=None, product=None, quantity=None): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] order.update(customer, product, quantity) # Генерируем событие обновления заказа return {"event": "OrderUpdated", "data": order.to_dict()}<p>Важная часть любого онлайн-магазина - возможность проверить статус заказа. Без неё пользователи не смогут отслеживать доставку, а служба поддержки не сможет узнать, что покупатель заказал, чтобы оформить возврат.</p>
73
<p>Проверка статуса не изменяет данные, а лишь запрашивает их. В паттерне CQRS такие операции выносят в отдельный модуль. Мы сделаем то же самое - создадим файл queries.py и реализуем в нём функцию get_order.</p>
73
<p>Проверка статуса не изменяет данные, а лишь запрашивает их. В паттерне CQRS такие операции выносят в отдельный модуль. Мы сделаем то же самое - создадим файл queries.py и реализуем в нём функцию get_order.</p>
74
<p>Функция get_order принимает номер заказа, ищет его в системе и возвращает все нужные данные:</p>
74
<p>Функция get_order принимает номер заказа, ищет его в системе и возвращает все нужные данные:</p>
75
def get_order(order_id): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] return order.to_dict()<p><strong>Обратите внимание</strong>: эта функция не генерирует событие, поскольку обычно запросы статуса не требуют уведомлений, логирования или передачи в систему аналитики. get_order просто возвращает словарь с информацией о заказе.</p>
75
def get_order(order_id): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] return order.to_dict()<p><strong>Обратите внимание</strong>: эта функция не генерирует событие, поскольку обычно запросы статуса не требуют уведомлений, логирования или передачи в систему аналитики. get_order просто возвращает словарь с информацией о заказе.</p>
76
<p>Полностью код файла queries.py выглядит так:</p>
76
<p>Полностью код файла queries.py выглядит так:</p>
77
from models import order_store def get_order(order_id): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] return order.to_dict()<p>Теперь у нас есть все функции бэкенда небольшого онлайн-магазина. Создадим файл app.py и применим в нём все функции, которые описали выше: создадим заказ, отредактируем его и запросим информацию.</p>
77
from models import order_store def get_order(order_id): if order_id not in order_store: raise Exception("Заказ не найден.") order = order_store[order_id] return order.to_dict()<p>Теперь у нас есть все функции бэкенда небольшого онлайн-магазина. Создадим файл app.py и применим в нём все функции, которые описали выше: создадим заказ, отредактируем его и запросим информацию.</p>
78
from commands import create_order, update_order from queries import get_order if __name__ == '__main__': print("Создание заказа:") event = create_order(1, "Иван", "Ноутбук", 2) print(event) print("Обновление заказа:") event = update_order(1, quantity=3) print(event) print("Получение заказа:") order_info = get_order(1) print(order_info)<p>Если запустить код, Python выведет в терминал следующее:</p>
78
from commands import create_order, update_order from queries import get_order if __name__ == '__main__': print("Создание заказа:") event = create_order(1, "Иван", "Ноутбук", 2) print(event) print("Обновление заказа:") event = update_order(1, quantity=3) print(event) print("Получение заказа:") order_info = get_order(1) print(order_info)<p>Если запустить код, Python выведет в терминал следующее:</p>
79
Создание заказа: {'event': 'OrderCreated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 2}} Обновление заказа: {'event': 'OrderUpdated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 3}} Получение заказа: {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 3}<p>В нашей архитектуре команды (create_order, update_order) и запросы (get_order) находятся в разных модулях. Это делает систему гибкой: их можно дорабатывать независимо.</p>
79
Создание заказа: {'event': 'OrderCreated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 2}} Обновление заказа: {'event': 'OrderUpdated', 'data': {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 3}} Получение заказа: {'order_id': 1, 'customer': 'Иван', 'product': 'Ноутбук', 'quantity': 3}<p>В нашей архитектуре команды (create_order, update_order) и запросы (get_order) находятся в разных модулях. Это делает систему гибкой: их можно дорабатывать независимо.</p>
80
<p>Например, если по аналитическим отчётам мы заметим, что пользователи стали чаще оформлять заказы и реже проверяют их статус, мы сможем перераспределить ресурсы - и при этом не придётся переписывать всю платформу.</p>
80
<p>Например, если по аналитическим отчётам мы заметим, что пользователи стали чаще оформлять заказы и реже проверяют их статус, мы сможем перераспределить ресурсы - и при этом не придётся переписывать всю платформу.</p>
81
<p>Паттерн CQRS - мощный архитектурный подход, но не универсальное решение. У него есть как очевидные плюсы, так и важные ограничения, которые нужно учитывать при выборе архитектуры.</p>
81
<p>Паттерн CQRS - мощный архитектурный подход, но не универсальное решение. У него есть как очевидные плюсы, так и важные ограничения, которые нужно учитывать при выборе архитектуры.</p>
82
<p>Преимущества:</p>
82
<p>Преимущества:</p>
83
<ul><li><strong>Масштабируемость.</strong>Чтение и запись работают независимо, поэтому каждую часть можно масштабировать отдельно. Например, если растёт количество запросов к базе, можно добавить серверы только для чтения или внедрить кэш, - и всё это без изменений в логике записи.</li>
83
<ul><li><strong>Масштабируемость.</strong>Чтение и запись работают независимо, поэтому каждую часть можно масштабировать отдельно. Например, если растёт количество запросов к базе, можно добавить серверы только для чтения или внедрить кэш, - и всё это без изменений в логике записи.</li>
84
<li><strong>Оптимизация под задачи.</strong>CQRS позволяет использовать разные модели данных для разных целей. Для чтения можно применить упрощённые структуры ради скорости, а для записи - строгую и безопасную модель, которая гарантирует корректные изменения.</li>
84
<li><strong>Оптимизация под задачи.</strong>CQRS позволяет использовать разные модели данных для разных целей. Для чтения можно применить упрощённые структуры ради скорости, а для записи - строгую и безопасную модель, которая гарантирует корректные изменения.</li>
85
<li><strong>Упрощённое тестирование.</strong>Когда логика чтения и изменения данных разделена, модули проще тестировать независимо друг от друга. Например, если в коде проекта переписали только модуль запросов, то можно сэкономить время и не тестировать всю систему.</li>
85
<li><strong>Упрощённое тестирование.</strong>Когда логика чтения и изменения данных разделена, модули проще тестировать независимо друг от друга. Например, если в коде проекта переписали только модуль запросов, то можно сэкономить время и не тестировать всю систему.</li>
86
<li><strong>Поддержка сложных бизнес-процессов.</strong>CQRS хорошо сочетается с паттерном event sourcing - когда все изменения сохраняются в виде событий. Это особенно полезно, если важно отслеживать историю изменений, вести аудит или восстанавливать состояние системы.</li>
86
<li><strong>Поддержка сложных бизнес-процессов.</strong>CQRS хорошо сочетается с паттерном event sourcing - когда все изменения сохраняются в виде событий. Это особенно полезно, если важно отслеживать историю изменений, вести аудит или восстанавливать состояние системы.</li>
87
</ul><p>Недостатки:</p>
87
</ul><p>Недостатки:</p>
88
<ul><li><strong>Усложнение архитектуры.</strong>Внедрение CQRS сильно усложняет систему, особенно в небольших проектах. Например, код проекта становится запутанным и появляется избыточная абстракция.</li>
88
<ul><li><strong>Усложнение архитектуры.</strong>Внедрение CQRS сильно усложняет систему, особенно в небольших проектах. Например, код проекта становится запутанным и появляется избыточная абстракция.</li>
89
<li><strong>Проблемы синхронизации данных.</strong>Если чтение и запись разделены, то все изменения при записи нужно синхронизировать с данными для чтения. Дополнительные механизмы синхронизации приводят к задержкам в обновлении данных.</li>
89
<li><strong>Проблемы синхронизации данных.</strong>Если чтение и запись разделены, то все изменения при записи нужно синхронизировать с данными для чтения. Дополнительные механизмы синхронизации приводят к задержкам в обновлении данных.</li>
90
<li><strong>Высокие затраты на внедрение.</strong>Для каждого модуля нужна отдельная инфраструктура и интеграция с другими компонентами системы. Это дольше и дороже.</li>
90
<li><strong>Высокие затраты на внедрение.</strong>Для каждого модуля нужна отдельная инфраструктура и интеграция с другими компонентами системы. Это дольше и дороже.</li>
91
<li><strong>Повышенная сложность отладки и мониторинга.</strong>Изменение состояния системы проходит через множество компонентов и слоёв. Отслеживать изменения в разделённой системе сложнее.</li>
91
<li><strong>Повышенная сложность отладки и мониторинга.</strong>Изменение состояния системы проходит через множество компонентов и слоёв. Отслеживать изменения в разделённой системе сложнее.</li>
92
</ul><p>CQRS - это архитектурный паттерн, который разделяет команды (изменение данных) и запросы (чтение данных) в системе. Это даёт возможность не только раздельно обрабатывать данные, но и использовать разные модели данных для каждой задачи.</p>
92
</ul><p>CQRS - это архитектурный паттерн, который разделяет команды (изменение данных) и запросы (чтение данных) в системе. Это даёт возможность не только раздельно обрабатывать данные, но и использовать разные модели данных для каждой задачи.</p>
93
<p>Такое разделение помогает масштабировать чтение и запись независимо друг от друга, оптимизировать каждую часть для своих целей, проще тестировать и сопровождать системы со сложной бизнес-логикой.</p>
93
<p>Такое разделение помогает масштабировать чтение и запись независимо друг от друга, оптимизировать каждую часть для своих целей, проще тестировать и сопровождать системы со сложной бизнес-логикой.</p>
94
<p>Однако у паттерна есть и недостатки: он усложняет архитектуру, требует дополнительных механизмов синхронизации, и его использование не оправдано в маленьких проектах с простой логикой.</p>
94
<p>Однако у паттерна есть и недостатки: он усложняет архитектуру, требует дополнительных механизмов синхронизации, и его использование не оправдано в маленьких проектах с простой логикой.</p>
95
<p>Используйте CQRS, когда система действительно нуждается в масштабировании, гибкости или отслеживании истории изменений. В остальных случаях можно обойтись более простой архитектурой.</p>
95
<p>Используйте CQRS, когда система действительно нуждается в масштабировании, гибкости или отслеживании истории изменений. В остальных случаях можно обойтись более простой архитектурой.</p>
96
<a>Курс с трудоустройством: "Веб-разработчик" Узнать о курсе</a>
96
<a>Курс с трудоустройством: "Веб-разработчик" Узнать о курсе</a>