
CQRS в NestJS: практическое руководство о настоящих компромиссах
- NestJS CQRS
- nestjs
- architecture
- development

CQRS — разделение ответственности команд и запросов — начинается с простой мысли: модель для изменения состояния не обязана быть моделью для его чтения. Команда может передавать намерение бизнеса и защищать неизменные правила, а запрос — возвращать именно ту форму данных, которая нужна экрану, отчёту или интеграции. NestJS предоставляет шины, обработчики, события и саги, облегчающие такое разделение, но само удобство его не оправдывает.
Подход окупает свою цену, когда правила записи сложны, чтению нужна существенно иная форма или масштаб, нескольким потребителям требуются производные представления либо единая модель вынуждает обе стороны к вредным компромиссам. Он может прояснить ответственность в зрелой предметной области и позволить оптимизировать пути чтения без ослабления целостности записи. Вместе с тем он добавляет понятия, промежуточные уровни, способы отказа, испытания, телеметрию и эксплуатационную работу.
Рассматривайте CQRS как локальное проектное решение, а не как признак всей системы. Одному ограниченному контексту могут потребоваться модель команд и проекции, тогда как остальное приложение останется обычным CRUD. Начинайте с измеренного ограничения, критического пути, ответственного владельца и определения успеха. Если команда не может назвать проблему, которую устраняет разделение, дополнительная архитектура, скорее всего, лишь переместит сложность.
Какие проблемы действительно решает CQRS
Общая модель CRUD часто перегружается узнаваемым образом. Запись при оформлении заказа должна проверить запасы, кредит, скидки и правила противодействия мошенничеству, а странице клиента нужен быстрый денормализованный итог заказа. Рабочая панель объединяет множество агрегатов и часто меняется, тогда как транзакционная модель должна оставаться консервативной. Аудиторам нужна устойчивая история, поиску — отдельный индекс, мобильным клиентам — компактные представления. Попытка обслужить все потребности одним графом объектов порождает чрезмерные хранилища, случайные записи, дорогие соединения таблиц и трудные для понимания разрешения.
CQRS разделяет это давление. Сторона записи принимает намерение, загружает достоверное состояние, применяет неизменные правила, фиксирует результат и записывает необходимую дальнейшую работу. Сторона чтения открывает специализированные представления, которые можно денормализовать, кешировать, индексировать или хранить отдельно. Такое разделение способно улучшить понятность и производительность, но создаёт промежуток несогласованности: успешная запись может появиться не в каждой проекции сразу.
Определите этот промежуток на языке продукта. Должен ли экран подтверждения после оформления заказа сразу видеть новую запись? Может ли аналитическая панель отставать на минуту? Разрешено ли службе поддержки действовать по проекции десятиминутной давности? Установите требования к свежести, времени восстановления проекции, источнику истины и поведению для пользователя во время отставания. До перепроектирования измерьте стоимость запросов, конкуренцию за запись, связывание выпусков и характер происшествий. CQRS имеет смысл, когда новый бюджет отказов лучше прежнего, а не просто когда классы чтения и записи получили разные названия.
Команды и запросы требуют разной семантики
Команда — это указание со смыслом для бизнеса: ApproveInvoice, ReserveStock, ChangeCustomerLimit. Называйте её по намерению, а не способу передачи. Обработчик аутентифицирует контекст, авторизует действие, проверяет текущее состояние, выполняет одну транзакцию своего владельца и возвращает минимальный необходимый вызывающей стороне результат. Команды, которые могут повториться на ненадёжной границе, требуют стратегии идемпотентности. Избегайте универсальной команды с произвольным набором изменений: она скрывает неизменные правила и делает авторизацию зависимой от меняющегося набора полей.
Запрос просит сведения и не должен вызывать побочные действия в бизнесе. Проектируйте его вокруг решения потребителя: GetCheckoutSummary, SearchOpenDisputes, ListAccountsRequiringReview. Обработчики запросов могут использовать оптимизированный SQL, представления, реплики, кеши или поисковые индексы, не восстанавливая агрегат записи. Однако им всё равно нужны авторизация, разбиение на страницы, ограниченные фильтры, правила свежести и защита от слишком дорогих форм ответа.
CommandBus и QueryBus в NestJS обеспечивают последовательную передачу и регистрацию обработчиков. Они не заменяют обычные вызовы методов автоматически. Шины внутри процесса добавляют промежуточный уровень, поэтому используйте их там, где именованные запросы, конвейеры, измерения или архитектурные границы оправдывают эту цену. Обработчик должен быть достаточно кратким, чтобы показывать управление сценарием, и достаточно содержательным, чтобы отвечать за него. Если каждый обработчик лишь передаёт вызов одноимённой службе, команда, возможно, добавила церемонию без настоящего разделения.
Если не хватает исполнителей, команда разработки на NestJS должна реализовать один ограниченный срез с доказательствами приемлемости и передачей знаний, а не разнести шины по всей кодовой базе за один раз.
События создают обязательства по надёжности
Предметное событие сообщает о факте, который модель-владелец уже приняла: OrderPlaced, TransferSettled, SubscriptionSuspended. Оно сформулировано в прошедшем времени и неизменно. Событие может обновить проекцию, вызвать оповещение, начать рабочий процесс или сообщить другому ограниченному контексту. Такое разделение полезно лишь тогда, когда правила доставки определены явно.
EventBus внутри процесса не становится автоматически надёжным посредником сообщений. Если транзакция базы данных завершится, а процесс остановится до публикации, дальнейшая работа может исчезнуть. Если посредник доставит сообщение повторно, потребители могут выполниться дважды. Когда изменение состояния и сообщение должны вместе пережить отказ, используйте транзакционную исходящую очередь или другой проверенный атомарный способ передачи. Дайте сообщениям устойчивые идентификаторы и версионированные схемы; сделайте потребителей идемпотентными; определите область упорядочивания, пределы повторов, задержку между ними, обработку недоставленных сообщений, срок хранения, повторное воспроизведение и ответственность.
Не скрывайте обязательное синхронное правило за асинхронным событием. Если заказ нельзя подтвердить без резервирования товара, определите место этого правила и то, что увидит клиент в период неопределённости. Саги могут координировать длительные реакции, но не являются распределёнными транзакциями. Для каждого шага нужны предельное время, идемпотентность, наблюдаемое состояние и путь компенсации или сверки.
При эксплуатации измеряйте задержку публикации, отставание потребителей, возраст самого старого сообщения, повторы, сообщения, постоянно вызывающие сбой, свежесть проекций и расхождения в деловых данных. Идентификаторы связывания и причинности должны соединять исходную команду с её событиями и последствиями. Без этих сигналов мнимая изящность обработчиков превращается в происшествие, рассеянное по невидимым очередям.
CQRS не требует хранить состояние как последовательность событий
CQRS и событийное хранение состояния решают разные задачи. CQRS разделяет модели записи и чтения. При событийном хранении принятые изменения агрегата записываются как неизменная последовательность событий, а текущее состояние восстанавливается их повторным применением. Система может использовать команды, обычную реляционную модель записи, исходящую очередь и одну или несколько проекций, не превращая события в главный источник истины.
Такой более простой вариант часто является ответственным первым шагом. Транзакция базы сохраняет неизменные правила записи; исходящая очередь надёжно фиксирует публикацию; исполнители проекций строят модели чтения; сверка выявляет пробелы. Системе всё равно нужны развитие схем, идемпотентность, процедуры восстановления, допустимое отставание и явные правила источника истины, но она не привязывает всю предметную область к потокам событий.
Событийное хранение становится привлекательным, когда история во времени имеет центральное значение, прошлое состояние необходимо восстанавливать, решения бизнеса требуют долговечной объясняющей записи или альтернативные проекции из полной истории дают существенную ценность. Цена высока: версии событий, преобразование старых событий к современной форме, стратегия снимков, побочные действия повторного воспроизведения, требования к конфиденциальности и удалению, отладка во времени и восстановление больших потоков. Таблица аудита не обязательно является событийным хранилищем, а событийное хранилище не становится автоматически решением для аудита.
Принимайте эти два решения отдельно. Сначала выясните, устраняет ли измеренную проблему разделение моделей команд и запросов. Затем проверьте, является ли журнал событий лучшей достоверной моделью хранения именно для этого агрегата. Привычка объединять оба подхода умножает сложность раньше, чем доказана польза любого из них.
Когда CQRS становится невыгодным компромиссом
Не внедряйте CQRS лишь потому, что для него существует пакет программной платформы, в требованиях к приложению написано «корпоративное» или в будущем может возникнуть проблема масштаба. Небольшой команде с простой предметной областью, умеренной нагрузкой, похожими формами чтения и записи и одной базой данных обычно легче создавать и эксплуатировать явные прикладные службы и целевые запросы. Хорошо спроектированная система CRUD не является незрелой системой CQRS.
Тревожные признаки — отдельные классы команд и запросов для каждой мелкой точки API, обработчики, лишь переименовывающие методы служб, события в роли синхронных обратных вызовов, проекции без требований к свежести и посредник сообщений без ответственного. Архитектура также подозрительна, если локальная отладка требует прослеживать пять абстракций, вхождение новых разработчиков замедляется, выпуски остаются тесно связанными или команда не умеет безопасно восстановить модель чтения.
До разделения моделей попробуйте более дешёвое средство, соответствующее проблеме: улучшите индексы и SQL, создайте представление базы, добавьте хранилище чтения, изолируйте предметный модуль, кешируйте один дорогой результат, перенесите медленное действие в надёжное задание или исправьте нечёткий контракт API. Эти изменения могут дать большую часть пользы без новой модели согласованности.
Позднее CQRS может оправдать себя в одном узком месте. Сохраните обратимость решения: не допускайте проникновения шин программной платформы в предметные правила, задокументируйте источник истины и правила согласованности и измеряйте, улучшает ли выбранный срез задержку, правильность или независимость выпусков. Уберите подход, если его эксплуатационная цена превышает проблему, которую он должен был устранить.
Проверяйте согласованность, дублирование и восстановление
Проверяйте обработчики команд на неизменных правилах бизнеса: разрешённых переходах состояния, авторизации, границах транзакций, конфликтах одновременности, идемпотентности и внешних отказах. Модульное испытание может объяснить правило, а интеграционные должны доказать ограничения базы данных, блокировки или оптимистическое управление одновременностью, атомарность исходящей очереди и поведение адаптеров. Не подменяйте имитациями механизмы с наибольшим риском.
Проверяйте запросы на авторизацию, фильтры, разбиение на страницы, сортировку, планы выполнения, пределы размера данных, правила кеша и устаревшие данные. Контрактные испытания должны защищать схемы, видимые клиентам. Проекции нужно проверять на дублированных, отсутствующих, задержанных, переставленных событиях и сообщениях, вызывающих сбой; восстановление должно давать то же представление из той же принятой истории. Проверьте, как новая версия проекции работает рядом со старой, догоняет её, сравнивается, принимает запросы и откатывается.
Сквозные сценарии должны пересекать промежуток несогласованности. Выполните команду, подтвердите достоверный результат, проследите продвижение проекции и проверьте опыт пользователя до и после согласования. Искусственно вызывайте отказы посредника, базы данных и потребителя. Проверьте сверку при прерванной публикации или обработке. Для агрегатов с событийным хранением дополнительно проверьте развитие событий, снимки, изоляцию повторного воспроизведения и средства, не позволяющие ему повторить внешние побочные действия.
Уверенность в рабочей системе зависит от диагностики. У каждой команды должны быть идентичность и контекст связывания; обработчики должны сообщать длительность и результат без утечки чувствительных данных; проекции — показывать отставание и контрольные точки; сверка — выявлять расхождения. У каждого оповещения должны быть ответственный и эксплуатационная инструкция. Успешные ответы HTTP ничего не значат, если принятые команды незаметно накапливаются в исходящей очереди.
Переносите по одному ценному сквозному срезу
Не начинайте перенос на CQRS с перестройки всех точек API. Выберите один болезненный ограниченный путь с измеримым результатом, например медленную рабочую панель, связанную со стабильным процессом заказов. Зафиксируйте исходные показатели задержки, частоты ошибок, нагрузки на базу данных, времени от начала изменения до выпуска и усилий на происшествия. Определите правила согласованности и отката до написания первого обработчика.
Сначала сделайте существующий путь записи явным и защитите его неизменные правила. Добавьте именованную команду на прикладной границе, не меняя хранение. Затем опубликуйте надёжную запись изменения, постройте одну проекцию рядом с текущим путём чтения и заполните её историческими данными из надёжного источника. Постоянно сравнивайте старые и новые результаты, включая разрешения и граничные случаи. Направьте небольшую группу пользователей на новый запрос, следите за свежестью и использованием ресурсов, а затем расширяйте охват.
Пока старые и новые потребители работают вместе, применяйте версионированные события и совместимые дополнительные изменения схем. Двойная запись из прикладного кода хрупка; предпочитайте атомарную исходящую очередь, захват изменений данных с хорошо понятными правилами или другой управляемый способ передачи. Откат проекции может означать возврат чтения к старой модели, тогда как откат записи иногда требует совместимости, а не отмены уже принятых действий бизнеса.
После стабилизации среза проверьте, превысила ли измеренная польза стоимость создания и эксплуатации. Задокументируйте подходы, которые стоит повторять, исключения и модель ответственности. Переносите следующий срез лишь при наличии доказательств. Такой постепенный подход связывает архитектуру с ценностью продукта и оставляет команде несколько безопасных точек остановки.
Когда полезна независимая проверка CQRS
Независимая проверка полезна, когда CQRS распространился без измеримой пользы, проекции расходятся с достоверными данными, происшествия теряются между базой и посредником, контракты событий блокируют выпуски или команда рассматривает событийное хранение прежде всего потому, что уже использует события. Она также помогает перед выделением ценного ограниченного контекста или переносом критического рабочего процесса.
Качественная оценка прослеживает настоящие команды и запросы, отображает неизменные правила и источники истины, измеряет поведение запросов и проекций, исследует границы транзакций и публикации, воспроизводит дубликаты и отказы, проверяет авторизацию между моделями и сравнивает более простые варианты. Результат должен упорядочить риски, определить наименьший оправданный объём, установить доказательства согласованности и восстановления, назначить эксплуатационную ответственность и оставить поэтапный план внедрения или упрощения.
Внешние знания должны уменьшать зависимость от внешних знаний. Требуйте записей решений, совместной работы, заметок проверки, схем, соответствующих коду, испытанных эксплуатационных инструкций и упражнения по передаче, в котором команда-владелец восстанавливает проекцию, диагностирует отставание и выполняет откат. Результат ценен, когда команда может объяснять и эксплуатировать компромиссы без присутствия проверяющего.
- Проблема и граница: до разделения моделей назовите ограниченный контекст, болезненный путь, исходные и целевые показатели, источник истины, ответственного и более простые альтернативы.
- Семантика команд и запросов: определите намерение, неизменные правила, авторизацию, идемпотентность, границы транзакции, форму чтения, разбиение на страницы, свежесть и ограничения стоимости.
- Надёжность событий: выберите надёжный способ передачи, версионируйте схемы, сделайте потребителей идемпотентными и назначьте ответственных за порядок, повторы, недоставленные сообщения, повторное воспроизведение, сверку и отставание.
- Решение о хранении: оценивайте CQRS и событийное хранение отдельно; задокументируйте достоверное состояние, потребность в истории, обязанности по конфиденциальности, стратегию восстановления и развитие событий.
- Бюджет сложности: докажите, почему целевой SQL, представление, кеш, хранилище чтения, надёжное задание или более чёткий модуль не могут дешевле устранить ограничение.
- Доказательства и выпуск: на одном срезе, приближённом к рабочей системе, проверьте дубликаты, задержку, изменение порядка, отказ, восстановление, авторизацию, перенос, откат, наблюдаемость и инструкции команды.
Какую проблему должен решать CQRS в приложении на NestJS?
Должна ли каждая точка API NestJS использовать CommandBus или QueryBus?
Гарантирует ли NestJS EventBus надёжную доставку событий?
Можно ли применять CQRS без событийного хранения состояния?
Как внедрять CQRS безопасно и постепенно?
Предоставьте ограниченный контекст, критические команды и запросы, текущую модель данных, требования к согласованности, движение событий, нагрузку, происшествия и эксплуатационные ограничения. Мы сравним более простые варианты, определим наименьший оправданный срез CQRS и зафиксируем доказательства переноса, испытаний, наблюдаемости, отката и ответственности.
Наши исследования
Исследование и разработка решений на основе искусственного интеллекта для оптимизации бизнес-процессов и повышения эффективности принятия решений.
Анализ моделей машинного обучения для прогнозной аналитики в сфере финансов, электронной коммерции и SaaS-платформ.
Исследование технологий обработки естественного языка и компьютерного зрения для усиления автоматизации, персонализации и поддержки клиентов.


