воскресенье, 31 мая 2026 г.

CQRS, Event Sourcing

CQRS, Event Sourcing

Совместное использование CQRS и Event Sourcing: подробное описание

CQRS (Command Query Responsibility Segregation) и Event Sourcing (источники событий) — два архитектурных паттерна, которые часто используют вместе для построения масштабируемых и гибких систем. Разберём их совместное применение детально.

Как они дополняют друг друга

  • CQRS разделяет операции чтения (Queries) и записи (Commands), позволяя оптимизировать каждую сторону под свои задачи.

  • Event Sourcing хранит все изменения состояния системы как последовательность неизменяемых событий.

Вместе они создают архитектуру, где:

  • команды обрабатываются через Event Sourcing — генерируют события и сохраняют их в Event Store;

  • запросы обслуживаются через оптимизированные проекции (Read Models), построенные на основе этих событий.

Компоненты совместной архитектуры

  1. Command Handler — принимает команды, валидирует их и передаёт агрегату.

  2. Aggregate — бизнес‑объект, который:

    • применяет бизнес‑правила;

    • генерирует события (OrderCreated, PaymentProcessed и т. д.);

    • восстанавливает своё состояние путём воспроизведения событий из Event Store.

  3. Event Store — специализированное хранилище для событий. Гарантирует:

    • неизменяемость событий;

    • упорядоченность по времени;

    • атомарную запись группы событий.

  4. Event Processor / Projector — читает новые события из Event Store и обновляет проекции.

  5. Read Models (проекции) — материализованные представления данных, оптимизированные под конкретные запросы. Могут быть:

    • реляционными таблицами;

    • JSON‑документами;

    • кэшами в памяти;

    • индексами поиска.

  6. Query Handler — обрабатывает запросы, обращаясь к готовым проекциям.


Пошаговый процесс работы системы

Сценарий: оформление заказа в интернет‑магазине

  1. Команда (Command): клиент отправляет команду CreateOrder с данными заказа.

  2. Обработка команды:

    • Command Handler получает команду;

    • передаёт её агрегату Order;

    • агрегат проверяет бизнес‑правила (достаточно ли товара на складе, корректны ли данные).

  3. Генерация событий: если команда валидна, агрегат генерирует события:

    • OrderCreated;

    • InventoryReserved (если товар есть в наличии).

  4. Сохранение событий: события записываются в Event Store в рамках одной транзакции.

  5. Обновление состояния агрегата: агрегат применяет события к своему внутреннему состоянию.

  6. Уведомление о событиях: система оповещает подписчиков о новых событиях.

  7. Обновление проекций:

    • Event Processor считывает новые события;

    • обновляет соответствующие Read Models:

      • таблицу «Активные заказы»;

      • JSON‑документ для карточки заказа;

      • индекс поиска по заказам.

  8. Запрос (Query): клиент запрашивает список своих заказов.

  9. Обработка запроса:

    • Query Handler обращается к оптимизированной проекции;

    • возвращает данные клиенту за миллисекунды.


Преимущества совместного использования

  1. Масштабируемость:

    • чтение и запись масштабируются независимо;

    • можно добавить больше серверов для обработки запросов или команд.

  2. Производительность:

    • запросы обслуживаются из оптимизированных проекций (быстрые чтения);

    • команды обрабатываются асинхронно, не блокируя запросы.

  3. Аудит и трассировка:

    • полная история изменений доступна через Event Store;

    • можно отследить, кто, когда и почему изменил данные.

  4. Гибкость:

    • новые проекции можно создавать без изменения бизнес‑логики;

    • разные представления данных для разных клиентов (веб, мобильное приложение, API для партнёров);

    • легко добавлять новые сервисы, подписывающиеся на события.

  5. Устойчивость к изменениям:

    • изменения схемы данных не требуют миграции старых событий;

    • новые поля можно добавлять в новые события.

  6. Интеграция:

    • события служат единой шиной данных для коммуникации между сервисами;

    • другие системы могут подписываться на события без изменения основной логики.

Недостатки и сложности

  1. Сложность архитектуры:

    • больше компонентов, чем в традиционном CRUD;

    • требуется глубокое понимание паттернов.

  2. Конечная согласованность (Eventual Consistency):

    • между записью события и обновлением проекции может быть задержка;

    • клиент может не увидеть последние изменения сразу.

  3. Производительность восстановления агрегатов:

    • для агрегатов с длинной историей событий восстановление состояния может быть медленным;

    • требуется механизм снимков (Snapshots) для ускорения.

  4. Управление версиями событий:

    • изменение структуры событий требует стратегий миграции проекций;

    • старые события могут иметь другую схему.

  5. Объём данных:

    • хранение всех событий приводит к быстрому росту объёма данных;

    • нужны стратегии архивирования и очистки.

  6. Отладка и тестирование:

    • сложнее отлаживать систему из‑за асинхронности;

    • тестирование требует симуляции потоков событий.


Практические рекомендации по реализации

  1. Используйте снимки (Snapshots):

    • сохраняйте периодические снимки состояния агрегатов;

    • при восстановлении загружайте последний снимок и применяйте только новые события после него.

  2. Идемпотентные обработчики:

    • проектируйте обработчики событий так, чтобы повторная обработка одного и того же события не приводила к ошибкам;

    • используйте уникальные идентификаторы событий для отслеживания.

  3. Компенсация ошибок:

    • реализуйте механизм компенсации (Compensating Events) для отмены некорректных действий;

    • например, событие OrderCancelled может компенсировать OrderCreated.

  4. Мониторинг и аудит:

    • логируйте все события и команды для отладки;

    • отслеживайте задержки между записью и чтением (Lag Monitoring).

  5. Стратегии обновления проекций:

    • синхронное обновление — для критически важных данных с низкой задержкой;

    • асинхронное обновление — для сложных проекций и высокой нагрузки.

  6. Разделение ответственности:

    • чётко разграничьте зоны ответственности между агрегатами;

    • избегайте слишком больших агрегатов с множеством событий.

  7. Тестирование:

    • тестируйте восстановление агрегатов из событий;

    • симулируйте задержки и сбои в обновлении проекций.


Когда использовать связку CQRS + Event Sourcing

Подходит для:

  • высоконагруженных систем с асимметричной нагрузкой на чтение/запись;

  • финансовых систем, где критичен аудит и трассировка изменений;

  • сложных бизнес‑процессов с множеством правил и согласований;

  • микросервисных архитектур с необходимостью интеграции между сервисами;

  • систем аналитики и мониторинга в реальном времени;

  • приложений с требованиями к гибкости и масштабируемости.

Не подходит для:

  • простых CRUD‑приложений (блоги, каталоги);

  • систем с жёсткими требованиями к мгновенной согласованности данных;

  • проектов с ограниченными ресурсами и сроками;

  • сценариев с частыми изменениями схемы данных без истории.


Пример: банковский счёт

Традиционный подход:

  • таблица Accounts с колонками id, balance;

  • операция «Пополнить на 100 руб.»UPDATE Accounts SET balance = balance + 100.

CQRS + Event Sourcing:

Сторона записи (Write Side):

  • команда DepositMoney (ID счёта, сумма);

  • агрегат Account генерирует событие MoneyDeposited (сумма, дата);

  • событие сохраняется в Event Store.

Сторона чтения (Read Side):

  • проекция AccountBalanceView обновляется при получении события MoneyDeposited;

  • запрос GetAccountBalance возвращает данные из AccountBalanceView.

Event Store:

  1. AccountOpened (сумма: 0 руб.)

  2. MoneyDeposited (сумма: 500 руб.)

  3. MoneyWithdrawn (сумма: 200 руб.)

  4. MoneyDeposited (сумма: 800 руб.)

Восстановление состояния:

  • начальное состояние: 0 руб.;

  • 0 + 500 = 500 руб.;

  • 500 − 200 = 300 руб.;

  • 300 + 800 = 1 100 руб.

Read Model (AccountBalanceView):

json
{
  "accountId": "123",
  "balance": 1100,
  "lastUpdated": "2023-10-05T10:00:00Z"
}

Комментариев нет:

Отправить комментарий