open atlas
↑ К треку
Архитектурные паттерны ARCH · 07 · 03

Когда CQRS избыточен

CQRS покупает структурное разделение за реальную цену: две модели, eventual consistency, инфраструктура projection. Оправдан только там, где асимметрия записи и чтения реальна. Большинство CRUD-экранов не подходит. CQRS и event sourcing — независимые паттерны.

ARCH Senior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

После того как команда биллинговой платформы внедрила CQRS для домена заказов, паттерн стал стандартным ответом команды. Модуль управления аккаунтами нуждался в экране редактирования данных компании — название, адрес, контакт по выставлению счетов. Разработчик предложил разбить на write model CompanyAggregate и read model CompanyProfileView с async projection. Технический лид возразил: «Какой инвариант охраняет write model? Какая асимметрия записи и чтения оправдывает projection?» Разработчик задумался. Экран редактирования компании не имел сложных инвариантов. У компании было название, адрес и контакт по выставлению счетов — три поля, одна таблица, одна форма. Записям и чтениям нужны были в точности одни и те же данные. Не было никаких проблем с производительностью при чтении нормализованной строки напрямую. Projection добавила бы асинхронный worker, доменное событие, отдельную таблицу read model и eventual consistency — для экрана, который пользователь редактирует раз в месяц. Вердикт технического лида: это CRUD. CQRS здесь — случайная сложность, а не структурная ясность. У той же команды была противоположная проблема в рабочем процессе одобрения заказов: там не было CQRS, была одна нормализованная модель Order на всё, и дашборд одобрения работал мучительно медленно из-за join по семи таблицам для каждой строки. Правильный ответ — не «всегда CQRS» и не «никогда CQRS». Это распознавание тех частей системы, где асимметрия записи и чтения реальна, и применение паттерна только там.

Реальная стоимость CQRS

CQRS не бесплатен. Прежде чем применять его, команда должна чётко понимать, что он вводит:

Две модели для проектирования, построения и поддержки. Каждое изменение write-стороны должно быть распространено на одну или несколько read models. Каждая новая read model — это новая projection для написания, тестирования и эксплуатации. Каждое изменение write-схемы должно оцениваться на предмет влияния на каждую projection.

Eventual consistency там, где её не было. Async projection означает, что read model может устареть. Это вопрос корректности, о котором нужно явно сообщать, управлять и проектировать. Пользователи видят не самые актуальные данные. Некоторые queries, которые раньше были строго согласованными, теперь eventually consistent.

Инфраструктура projection. Async projection требует механизма наблюдения за изменениями write-стороны: message bus с доменными событиями, change-data capture или паттерн outbox. Каждый из них имеет операционные накладные расходы — развёртывания, мониторинг, обработку сбоев, очереди недоставленных сообщений.

Более сложная отладка. Когда пользователь спрашивает «почему мой дашборд показывает неверный статус», ответ может затрагивать write model, pipeline projection, read model и окно eventual consistency. Цепочка причинности длиннее и сложнее для трассировки.

Эти затраты стоят того, когда структурная выгода реальна. Они — потери, когда применяются к проблемам, не имеющим подлинной асимметрии записи и чтения.

Большинство CRUD не требует CQRS

Значительная часть экранов приложений симметрична: те же поля, записанные в базу данных, отображаются обратно. Форма адреса записывает название, улицу, город и почтовый индекс. Экран редактирования читает те же четыре поля. Экран списка показывает те же поля с фильтром. Никакой инвариант поля адреса не охраняет. Никакой сложный join не нужен. Никакой структурной несовместимости записи и чтения не существует.

CQRS для этого экрана добавляет две модели, projection и eventual consistency за ноль структурной выгоды. Write model была бы CompanyAddress. Read model — CompanyAddressView. Projection копировала бы четыре поля из write-таблицы в read-таблицу. Это не архитектура — это церемония.

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

Для биллинговой платформы:

  • Редактирование профиля компании (название, адрес, контакт) — CRUD, без CQRS
  • Настройки аккаунта пользователя (email, часовой пояс, уведомления) — CRUD, без CQRS
  • Дашборд одобрения заказов (join по семи таблицам, агрегация статусов, флаги просрочки) — реальная асимметрия, CQRS оправдан
  • Подача заказа (инвариант кредитного лимита, инварианты позиций, переходы статусов) — сложные write-инварианты, CQRS оправдан
Why this works

Вопрос, который нужно задать перед применением CQRS, не «можем ли мы разделить это на commands и queries?» Ответ на него всегда да — любой домен можно разделить. Вопрос: «имеют ли забота о записях и забота о чтениях структурно несовместимые требования, оправдывающие поддержку двух отдельных моделей?» Если ответ да — CQRS разрешает реальное структурное противоречие. Если нет — CQRS это архитектурное позолачение.

Частичный и локальный CQRS

CQRS не обязан применяться ко всему приложению. Правильная область — bounded context или доменная область, где асимметрия записи и чтения действительно существует.

На биллинговой платформе домен управления заказами имеет реальную асимметрию: write-сторона имеет сложные инварианты (кредитный лимит, рабочий процесс одобрения, валидация позиций), а read-сторона имеет сложные queries (дашборд, пайплайн менеджера, финансовый отчёт). CQRS там оправдан.

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

Правильный ответ: применять CQRS локально к домену управления заказами. Дать домену управления аккаунтами использовать простую нормализованную модель с прямыми чтениями. Двум доменам не нужно быть архитектурно согласованными друг с другом — им нужно соответствовать своим собственным силам.

Это называется локальный CQRS или частичный CQRS: CQRS применяется избирательно там, где присутствует структурная выгода, а не равномерно по всей системе.

CQRS — это не event sourcing

Самое распространённое заблуждение о CQRS — путать его с event sourcing. Это независимые паттерны, которые хорошо дополняют друг друга. Их путаница удваивает стоимость внедрения.

CQRS разделяет write model и read model. Write-сторона хранит текущее состояние в нормализованной реляционной таблице. Read-сторона хранит денормализованные views. Projection читает write-таблицу напрямую или наблюдает доменные события.

Event sourcing (рассматривается в юните 08) хранит состояние как append-only последовательность событий, а не как текущее состояние. Текущее состояние вычисляется воспроизведением журнала событий. Event sourcing — это другая стратегия хранения write-стороны, независимая от того, как обслуживаются чтения.

Можно иметь CQRS без event sourcing: write-сторона — нормализованная реляционная таблица, read-сторона — денормализованная проекция этой таблицы. Это наиболее распространённая форма CQRS в production.

Можно иметь event sourcing без CQRS: сервис использует журнал событий как write-хранилище, но обслуживает чтения напрямую из журнала воспроизведением по запросу — без отдельных read models.

Можно иметь оба: write-сторона — журнал событий; read models строятся путём проецирования журнала событий. Эта комбинация мощная, но несёт стоимость обоих паттернов.

lesson.inset.note

Команды, впервые изучающие CQRS, часто встречают его в контексте event sourcing и предполагают, что они всегда идут вместе. Затем они решают, что не могут принять CQRS без принятия event sourcing, что поднимает стоимость внедрения до точки, где ни один не принимается. Реальность: CQRS с реляционной write model и projection, читающей write-таблицу, — это полностью валидная, малозатратная стартовая точка. Event sourcing — это необязательное и значительное дополнительное обязательство.

Викторина

Команда строит систему с тремя доменами: Order (сложный рабочий процесс одобрения, инварианты кредитного лимита, multi-table дашборд), Product Catalog (название, описание, цена, изображение — простой CRUD, отображается как хранится), и Notifications (пользовательские предпочтения для email/SMS, переключаются на странице настроек). Для каких доменов следует использовать CQRS?

Викторина

Команда хочет принять CQRS для домена заказов. Инженер говорит: «Мы не можем принять CQRS без event sourcing — они идут вместе». Верно ли это?

Викторина

Спустя шесть месяцев после применения CQRS к домену заказов, команда обнаружила, что новый инженер потратил неделю на отладку бага, при котором дашборд заказов показывал неверный статус. Корневая причина: в projection был баг, и для некоторых заказов read model содержала устаревшие данные. Технический лид спрашивает: «Был ли CQRS неправильным выбором?» Как следует это оценивать?

Вспомните перед уходом
  1. 01
    Каковы основные затраты, которые вводит CQRS, и когда они окупаются?
  2. 02
    Что такое частичный/локальный CQRS и почему он предпочтительнее равномерного применения CQRS?
  3. 03
    CQRS и event sourcing — это один паттерн? Можно ли иметь один без другого?
Итог

В вступительной истории была правильная интуиция — технический лид задал правильные вопросы: какой инвариант охраняет write model, и какая асимметрия оправдывает projection? Для экрана редактирования адреса компании ответами были «никакой» и «никакой». CQRS там был церемонией, а не архитектурой.

Затраты CQRS реальны: две модели, eventual consistency, инфраструктура projection и более длинные цепочки отладки. Эти затраты покупают реальную структурную выгоду — чистую write model, охраняющую инварианты, read model, сформированную именно для каждого query, независимую развиваемость обеих сторон. Компромисс оправдан там, где асимметрия реальна.

Применяй CQRS локально к доменным областям, где он оправдан. Пусть другие домены остаются на простых нормализованных моделях. Архитектурные паттерны должны следовать структурным силам, а не применяться ради согласованности.

CQRS и event sourcing независимы. Сегодня можно применить CQRS с реляционной write model без обязательства к журналу событий. Event sourcing (юнит 08) — это отдельное, значительное архитектурное решение о стратегии хранения write-стороны, дополняющее CQRS, но не требуемое им.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.