🔥 CQRS - это 3-й всадник архитектурного апокалипсиса, которым обычно дополняют DDD и Event Sourcing. Если вы тоже сорвали джекпот, давайте разберём как вообще с этим работать.
🔀 В основе идея, что нужно разделить приложения на части - одна для изменения данных (Write model) и другая для их чтения (Read model). Потому что назначение и требования к ним разные. И все клиенты (frontend, mobile) начинают работать именно с моделью чтения. Модель чтения состоит из так называемых view - наборов атрибутов одного или нескольких агрегатов. По сути денормализованных таблиц в базе данных, оптимизированных для быстрой выборки.
Многие элементы логики в моделях чтения и записи совпадают. Обе модели имеют снапшоты агрегатов и применяют события. Но есть критичные отличия: 💡 Модель чтения не проверяет инварианты. 💡 Вместо набора событий в ней хранится состояние агрегата, набор значений только тех его атрибутов, которые нужны клиентам. 💡 view может хранить данные нескольких агрегатов в одной таблице, чтобы обойтись без лишних join’ов.
⚖️ Проблемы и особенности подхода начинаются сильнее проявляться при масштабировании. В лучшем случае у вас один сервис на чтение и один на запись. В худшем - по сервису на каждый тип агрегатов и каждый view. Это оправдано, когда отличается нагрузка на каждый сервис, и их нужно масштабировать независимо.
🔄 Каждая view реагирует только на определенные события. Если модель чтения - единый сервис, то он инкапсулирует эту логику, получает все события и обновляет view внутри себя. Но если каждая view - это отдельный сервис, то начинается невероятно сложная хореография событий в системе.
📡 Также важно учитывать сетевые задержки. View обновляются не мгновенно и могут содержать информацию неактуальную по сравнению с моделью записи. Также View могут быть рассогласованы между собой.
⚙️ Один из подходов - полностью скрыть сложность внутри серверной части. Слои Middleware могут взять на себя маршрутизацию запросов, решать, в какой агрегат отправить command и кому адресовать query. Внешние клиенты видят простой и привычный RESTful API. Это удобный, но далеко не единственный вариант.
💻 На практике я столкнулся с другим подходом. Вместо того чтобы скрывать сложность, клиент может ее принять. Толстый клиент (thick client), который читает из read модели и параллельно получает события и применяет их к внутренней модели агрегата, чтобы сделать ее актуальной. Бизнес-логика дублируется на backend и frontend, это сложно реализовать и поддерживать. Но именно этот подход обеспечивает максимальную отзывчивость интерфейса.
🧩 Очередной архитектурный tradeoff. Один из той сотни, которые неочевидны до начала реализации. Внедрение CQRS облегчит масштабирование, но серьезно усложнит достижение консистентности системы. Надеюсь, в вашей команде есть люди c опытом применения этого подхода, которые смогут принимать грамотные решения, подходящие именно вашей системе.
· 24.06
А что там с транзакциями? 🤔
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
коммент удалён
· 24.06
Транзакции тут все внутри Write Model - команда либо позволяет обеспечить инварианты, либо нет. Если позволяет - транзакция закрывается сохранением события.
После того как событие сохранено - обновление всех остальных зависимых сервисов выполняется асинхронно. Они могут быть рассогласованы неограничено долго.
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
ответ удалён
· 24.06
И это прекрасно! Когда остальные сервисы ничего не знают о результатах транзакций write , тоесть сервис какой-то расчёт а баланса передал чего-то во write, , транзакция отменилась и сервис отображения баланса нифига об этом не знает?Это очень здравая идея. 💡:)
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
ответ удалён
· 24.06
Тут штука хитрее. Write - это ведь и есть счет, а баланс его атрибут. Не просто репозиторий, который в базу строчит, это еще и кусок бизнес логики, он сам проверяет свои инварианты, пытаешься списать то, чего нет - получаешь событие об отлупе) Отображаемое значение в интерфейсе это не изменит, разве что модалка с ошибкой на весь экран всплывет 🙃
Дальше вся сложность уходит наверх: сервис, который инициировал команду списания, сам решает, что делать с результатом. Отменить заказ, у клиента денег нет или кредит на него оформить))
А если с баланса все же что-то списалось, то да, какое-то время будем наблюдать рассинхрон. Таков путь. Когда все данные не влезают в одну базу данных приходится чем-то жертвовать.
0
ответить
коммент скрыт — часть юзеров считает его токсичным или некорректным
ответ удалён