CQRS, Event Sourcing
Совместное использование CQRS и Event Sourcing: подробное описание
CQRS (Command Query Responsibility Segregation) и Event Sourcing (источники событий) — два архитектурных паттерна, которые часто используют вместе для построения масштабируемых и гибких систем. Разберём их совместное применение детально.
Как они дополняют друг друга
CQRS разделяет операции чтения (
Queries) и записи (Commands), позволяя оптимизировать каждую сторону под свои задачи.Event Sourcing хранит все изменения состояния системы как последовательность неизменяемых событий.
Вместе они создают архитектуру, где:
команды обрабатываются через Event Sourcing — генерируют события и сохраняют их в Event Store;
запросы обслуживаются через оптимизированные проекции (Read Models), построенные на основе этих событий.
Компоненты совместной архитектуры
Command Handler — принимает команды, валидирует их и передаёт агрегату.
Aggregate — бизнес‑объект, который:
применяет бизнес‑правила;
генерирует события (
OrderCreated,PaymentProcessedи т. д.);восстанавливает своё состояние путём воспроизведения событий из Event Store.
Event Store — специализированное хранилище для событий. Гарантирует:
неизменяемость событий;
упорядоченность по времени;
атомарную запись группы событий.
Event Processor / Projector — читает новые события из Event Store и обновляет проекции.
Read Models (проекции) — материализованные представления данных, оптимизированные под конкретные запросы. Могут быть:
реляционными таблицами;
JSON‑документами;
кэшами в памяти;
индексами поиска.
Query Handler — обрабатывает запросы, обращаясь к готовым проекциям.
Пошаговый процесс работы системы
Сценарий: оформление заказа в интернет‑магазине
Команда (Command): клиент отправляет команду
CreateOrderс данными заказа.Обработка команды:
Command Handlerполучает команду;передаёт её агрегату
Order;агрегат проверяет бизнес‑правила (достаточно ли товара на складе, корректны ли данные).
Генерация событий: если команда валидна, агрегат генерирует события:
OrderCreated;InventoryReserved(если товар есть в наличии).
Сохранение событий: события записываются в Event Store в рамках одной транзакции.
Обновление состояния агрегата: агрегат применяет события к своему внутреннему состоянию.
Уведомление о событиях: система оповещает подписчиков о новых событиях.
Обновление проекций:
Event Processor считывает новые события;
обновляет соответствующие Read Models:
таблицу «Активные заказы»;
JSON‑документ для карточки заказа;
индекс поиска по заказам.
Запрос (Query): клиент запрашивает список своих заказов.
Обработка запроса:
Query Handlerобращается к оптимизированной проекции;возвращает данные клиенту за миллисекунды.
Преимущества совместного использования
Масштабируемость:
чтение и запись масштабируются независимо;
можно добавить больше серверов для обработки запросов или команд.
Производительность:
запросы обслуживаются из оптимизированных проекций (быстрые чтения);
команды обрабатываются асинхронно, не блокируя запросы.
Аудит и трассировка:
полная история изменений доступна через Event Store;
можно отследить, кто, когда и почему изменил данные.
Гибкость:
новые проекции можно создавать без изменения бизнес‑логики;
разные представления данных для разных клиентов (веб, мобильное приложение, API для партнёров);
легко добавлять новые сервисы, подписывающиеся на события.
Устойчивость к изменениям:
изменения схемы данных не требуют миграции старых событий;
новые поля можно добавлять в новые события.
Интеграция:
события служат единой шиной данных для коммуникации между сервисами;
другие системы могут подписываться на события без изменения основной логики.
Недостатки и сложности
Сложность архитектуры:
больше компонентов, чем в традиционном CRUD;
требуется глубокое понимание паттернов.
Конечная согласованность (Eventual Consistency):
между записью события и обновлением проекции может быть задержка;
клиент может не увидеть последние изменения сразу.
Производительность восстановления агрегатов:
для агрегатов с длинной историей событий восстановление состояния может быть медленным;
требуется механизм снимков (Snapshots) для ускорения.
Управление версиями событий:
изменение структуры событий требует стратегий миграции проекций;
старые события могут иметь другую схему.
Объём данных:
хранение всех событий приводит к быстрому росту объёма данных;
нужны стратегии архивирования и очистки.
Отладка и тестирование:
сложнее отлаживать систему из‑за асинхронности;
тестирование требует симуляции потоков событий.
Практические рекомендации по реализации
Используйте снимки (Snapshots):
сохраняйте периодические снимки состояния агрегатов;
при восстановлении загружайте последний снимок и применяйте только новые события после него.
Идемпотентные обработчики:
проектируйте обработчики событий так, чтобы повторная обработка одного и того же события не приводила к ошибкам;
используйте уникальные идентификаторы событий для отслеживания.
Компенсация ошибок:
реализуйте механизм компенсации (Compensating Events) для отмены некорректных действий;
например, событие
OrderCancelledможет компенсироватьOrderCreated.
Мониторинг и аудит:
логируйте все события и команды для отладки;
отслеживайте задержки между записью и чтением (Lag Monitoring).
Стратегии обновления проекций:
синхронное обновление — для критически важных данных с низкой задержкой;
асинхронное обновление — для сложных проекций и высокой нагрузки.
Разделение ответственности:
чётко разграничьте зоны ответственности между агрегатами;
избегайте слишком больших агрегатов с множеством событий.
Тестирование:
тестируйте восстановление агрегатов из событий;
симулируйте задержки и сбои в обновлении проекций.
Когда использовать связку 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:
AccountOpened(сумма: 0 руб.)MoneyDeposited(сумма: 500 руб.)MoneyWithdrawn(сумма: 200 руб.)MoneyDeposited(сумма: 800 руб.)
Восстановление состояния:
начальное состояние: 0 руб.;
0 + 500 = 500 руб.;
500 − 200 = 300 руб.;
300 + 800 = 1 100 руб.
Read Model (AccountBalanceView):
{
"accountId": "123",
"balance": 1100,
"lastUpdated": "2023-10-05T10:00:00Z"
}
Комментариев нет:
Отправить комментарий