CQRS и Event Sourcing не стоит применять, если в системе нет сложных правил изменения данных, нет требования хранить и пересобирать историю или нет людей, которые будут это поддерживать. В остальных случаях почти всегда хватает индексов, представлений в базе, таблицы истории и реплики для отчётов: они решают те же проблемы дешевле и без новых режимов отказа.
Статья не против подхода: у него есть честные применения, о них в предпоследнем разделе. Но в обсуждениях его слишком часто выбирают раньше, чем замерили проблему. Как устроены ступени от простого к сложному и как мы их внедряем, описано на странице «Внедрение CQRS и Event Sourcing», здесь разбираем, когда не надо.
Что именно вы покупаете
Чтобы взвешивать, нужно понимать цену. Разделение чтения и записи (CQRS) означает две модели данных вместо одной: у каждой свой код, свои тесты, своя схема. Когда чтение строится из отдельного хранилища, между записью и чтением появляется задержка. Пользователь нажал кнопку, обновил страницу и видит старое: это не сбой, а свойство схемы.
Журнал событий (Event Sourcing) добавляет больше. Состояние объекта не хранится, а собирается из ряда фактов. Отсюда четыре последствия: события нужно версионировать, потому что журнал живёт дольше кода; данные для экрана строятся проекциями, которые иногда приходится пересобирать целиком; ошибки в прошлом исправляются новыми событиями, а не правкой; отладка требует собственных инструментов, потому что в базе нет готового «текущего состояния».
Это не недостатки, а цена возможностей. Вопрос в том, нужны ли вам возможности. Историю изменений и состояние на дату можно получить дешевле, а настоящая выгода журнала появляется в узком классе задач.
Шесть признаков, что вам это не нужно
Если вы узнаёте свою систему в строках ниже, начните с дешёвой замены из правого столбца.
| Признак | Что за этим стоит | Что делать вместо |
|---|---|---|
| Система в основном «создать, прочитать, изменить, удалить» | Правила тривиальны, модель записи и модель чтения совпадают | Обычная модель с хорошими индексами. Разделение даст только двойной код |
| Нужна история «кто что менял» | Это требование аудита, а не логики | Таблица истории с полями «что, кто, когда, было, стало» и журнал действий |
| Тормозят несколько отчётов | Проблема в запросах или блокировках, а не в архитектуре | Индексы, переписанные запросы, материализованные представления, реплика базы для отчётов |
| Нужны остатки или суммы на дату | Срезы на конкретные даты, а не вся история | Снимки состояния по расписанию или записи с датами действия |
| Команда небольшая и никто не знает подход | Версии, пересборка и инструменты потребуют времени и дисциплины | Простая схема, которую команда сможет сопровождать после сдачи |
| Пользователи ждут результата сразу | Задержка чтения недопустима в основных сценариях | Единая модель в одной базе с транзакциями |
Если подходит хотя бы три строки, разделение и журнал почти наверняка лишние. Если подходит одна, остановитесь и измерьте, прежде чем принимать решение.
Простые замены, которые закрывают ту же потребность
Почти у каждого довода в пользу сложного подхода есть дешёвое решение. Ниже порядок, в котором стоит пробовать, начиная с самого простого.
- Индексы и разбор запросов. Одна из самых частых причин медленных отчётов — запрос без индекса или выборка всей таблицы. Починка занимает дни, а не месяцы.
- Материализованные представления. Готовые результаты тяжёлых запросов, обновляемые по расписанию или после изменений. Данные отстают на период обновления, и для большинства отчётов этого достаточно.
- Реплика базы для отчётов. Тяжёлые выборки идут на копию, основная база не страдает. Отставание копии — секунды.
- Таблица истории. Для каждой важной сущности записывается изменение с автором и временем. Её видит аудитор, из неё строится экран «история заявки».
- Снимки по расписанию. Для остатков и сумм: сохраняем состояние на конец дня и получаем срез на любую дату без пересборки.
- Раздельные таблицы чтения в той же базе. Если экраны требуют других форм данных, выделяем таблицы чтения, обновляемые в той же транзакции. Это и есть лёгкий вариант CQRS без задержки.
Дальше стоит идти, только когда предыдущий шаг не помог, и фиксировать это замерами. Тогда следующая ступень объяснима: вы знаете, какую именно проблему она решает.
Цена ошибки: как это выглядит через год
Возьмём условный пример: система обработки заявок, где вместо таблиц с текущим состоянием выбрали журнал событий «чтобы была история». Через год накопилось несколько изменений схемы событий, часть которых делали без версий. Пересборка отчёта занимает часы, потому что журнал длинный, а снимков нет. Двое из команды знают, как это устроено, один из них уходит. Для исправления опечатки в заявке нужно выпускать событие-исправление, и менеджеры жалуются, что «в базе не поправить».
Ничего из этого не катастрофа, но каждое неудобство стоит времени и нервов. История, ради которой всё затевалось, в итоге нужна редко: к ней обращаются раз в квартал, когда аудитор просит показать, кто согласовал заявку. Эту задачу решала таблица истории за несколько дней работы.
Вывод не в том, что журнал событий ошибочен. Вывод в том, что цена решения проявляется не на старте, а на второй-третий год. Поэтому вопрос «что мы будем делать с этим, когда автор уйдёт» нужно задавать в начале, а не в конце.
Когда подход оправдан
Честные применения существуют. Они сходятся в одном: на фактах и истории строится логика, а не только отчёт.
| Ситуация | Почему подходит | Что проверить до начала |
|---|---|---|
| Сложная предметная область с большим числом переходов | Каждый переход — событие с правилами, состояние выводится из фактов, правила проверяются тестами | Есть ли эксперты предметной области, готовые записать переходы |
| Нужно задним числом строить новые срезы | Проекцию можно пересобрать по всему журналу, пока исходные факты не потеряны | Как часто на самом деле меняются запросы к данным |
| Чтение сильно превышает запись и нагрузки разные | Чтение масштабируется отдельно от записи | Замеры: какая доля нагрузки приходится на чтение и что именно тормозит |
| Прошлое нельзя трогать | Неизменяемый журнал фиксирует факты без правок | Допустима ли задержка чтения и кто будет сопровождать журнал |
Даже в этих случаях стоит начинать с одного объекта, а не со всей системы. Переводим то, где история действительно нужна для логики, а справочники и вспомогательные сущности оставляем обычными таблицами.
Вопросы подрядчику, который предлагает CQRS или Event Sourcing
- Что именно вы измерили и какая проблема подтверждена замером?
- Почему не подходят индексы, представления, реплика и таблица истории?
- Какая задержка между записью и чтением получится и что увидит пользователь в этот момент?
- Как будут версионироваться события и кто проверяет совместимость?
- Сколько времени займёт полная пересборка проекций и как это измерено?
- Как исправляются ошибочные данные в прошлом и кто это делает?
- Как поступаем с персональными данными, которые нужно удалить?
- Кто на нашей стороне сможет сопровождать систему и какие инструменты вы передадите?
- Можно ли остановиться на более простой ступени без переписывания?
Хороший подрядчик ответит на эти вопросы цифрами и признает, что часть задач проще закрыть иначе. Общие вопросы к исполнителю собраны в статье «Как выбрать подрядчика на разработку ПО».
Как проверить до решения: небольшой набор замеров
- Профиль запросов. Журнал медленных запросов за неделю: какие операции тормозят, чтение или запись, в какие часы.
- Блокировки. Конкурируют ли отчёты с приёмом данных. Если да, часто помогает реплика.
- История, которая нужна на самом деле. Спросите аудитора и менеджеров, какие вопросы они задавали за год: «кто согласовал», «что было на дату», «почему изменился статус». Запишите их и подберите самое простое хранение, которое ответит.
- Допустимая задержка. Для каждого экрана выясните, можно ли показать данные с отставанием в несколько секунд.
- Состав команды. Кто будет сопровождать систему через два года и умеет ли он работать с версиями событий и пересборкой.
На выходе должен получиться документ, где проблема подтверждена числами, а выбор ступени объяснён. С ним можно идти и к нам, и к другому подрядчику. Если системой уже занимался другой исполнитель, помогает аудит кода и архитектуры.
Что делать, если уже внедрили и тяжело
Такое бывает. Упрощать можно по частям, не выбрасывая всё:
- Определите объекты, для которых история и пересборка действительно используются. Остальные переведите на обычные таблицы с историей.
- Где задержка чтения мешает, верните синхронное обновление таблиц чтения в той же транзакции.
- Введите версии для событий, если их нет, и снимите образцы реальных событий для тестов совместимости.
- Добавьте снимки состояния, чтобы пересборка укладывалась в приемлемое время, и измерьте её на копии.
- Передайте команде инструменты: просмотр потока событий по объекту, сравнение проекции с источником, скрипт пересборки.
Переход делаем рядом со старым путём и сравниваем результаты, как при любом переносе боевых данных.
Что делаем мы и с чего начать
Начинаем с обследования: 200 000 ₽ и 2–3 недели за один-два процесса, 350 000 ₽ и 3–4 недели, если затронуто несколько систем. На выходе замеры, рекомендация по ступени (в том числе «оставить как есть, добавить индексы»), вилка бюджета. Обследование вычитается из первого этапа, если продолжаете с нами. Цены по ступеням собраны на странице «Тарифы».
Дальше, если нужно, пилот на одном объекте и перевод остальных, и на любой ступени можно остановиться. Мы не внедряем подход «на всю систему», не дежурим круглосуточно, лицензии ФСТЭК у нас нет, юридических заключений о хранении данных не даём; исходный код и инструкции лежат в вашем репозитории с первого коммита. Подробности: «Внедрение CQRS и Event Sourcing».
Частые вопросы
Когда CQRS не нужен?
Когда система в основном создаёт, читает, изменяет и удаляет записи, нет сложных правил изменения данных, а тормозят единичные отчёты. Тогда помогают индексы, представления и реплика для отчётов, а разделение моделей удваивает код.
Когда Event Sourcing не нужен?
Когда история нужна для просмотра и аудита, а не для логики, когда команда небольшая и не знает подход, когда задержка чтения недопустима. Историю изменений даёт таблица истории, состояние на дату дают снимки.
В чём главный минус Event Sourcing?
Цена проявляется не на старте, а через год-два: версии событий, пересборка проекций, исправление ошибок новыми событиями и отладка без готового состояния. Поэтому его оправдывают только задачи, где история нужна для логики.
Что такое итоговая согласованность и почему о ней спорят?
Это задержка между записью и чтением, когда чтение строится из отдельного хранилища. Пользователь может увидеть старые данные сразу после действия. С этим работают в интерфейсе (показывают результат из ответа на команду), а если задержка недопустима, сценарий остаётся на единой модели.
Можно ли получить историю изменений без журнала событий?
Да. Таблица истории с полями «что, кто, когда, было, стало» закрывает большинство требований аудита. Журнал событий нужен, когда на истории строится логика или нужно задним числом строить новые срезы.
Можно ли начать с простого и перейти позже?
Можно, если границы модулей чёткие. Лёгкий вариант CQRS с таблицами чтения в той же базе потом превращается в отдельное хранилище чтения. Переход к журналу событий сложнее: он требует переноса истории, поэтому решение нужно взвешивать заранее.
Сколько стоит разобраться, нужен ли вам этот подход?
Обследование стоит 200 000 ₽ за один-два процесса и 350 000 ₽ за несколько систем, срок 2–3 и 3–4 недели. Если продолжаете с нами, сумма вычитается из первого этапа. Ответ «не нужно, добавьте индексы» тоже результат обследования.
Как быть с персональными данными в неизменяемом журнале?
Технически: хранить их отдельно и класть в событие ссылку либо шифровать ключом субъекта, который можно уничтожить. Какие данные и на каких основаниях удалять, решает юрист заказчика; мы закладываем возможность.