System Design Space

    Глава 18

    Обновлено: 15 февраля 2026 г. в 09:33

    Event-Driven Architecture: Event Sourcing, CQRS, Saga

    Прогресс части0/8

    Практический разбор event-driven подхода: как проектировать потоки событий, когда применять Event Sourcing и CQRS, и как реализовывать Saga для распределенных транзакций.

    Reference

    Martin Fowler: Event Sourcing

    Классическое объяснение идеи event sourcing и причин применения.

    Открыть материал

    Event-Driven Architecture (EDA) переносит фокус с синхронных вызовов на поток доменных событий. Это дает гибкость и масштабируемость, но требует дисциплины в контракте событий, идемпотентности и наблюдаемости. Практически EDA часто строится вокруг трех паттернов: Event Sourcing, CQRS и Saga.

    Event-Driven фундамент

    Событие - факт

    События описывают то, что уже произошло в домене, и должны быть неизменяемыми.

    Асинхронность по умолчанию

    Производитель и потребители слабо связаны, обмениваются через broker/log.

    Eventual consistency

    Между write и read-моделью появляется лаг, который нужно учитывать в UX и API.

    Анимированные потоки данных

    Ниже визуализация потоков для трех ключевых сценариев: базовый event pipeline, разделение read/write в CQRS и координация шагов в Saga.

    Data Flow Animations

    Каждая анимация запускается отдельно по кнопке `Старт`.

    Event-Driven Pipeline

    1

    Command API

    Получает intent

    2

    Domain Aggregate

    Валидирует и принимает решение

    3

    Event Store

    Append-only запись событий

    4

    Broker/Log

    Fan-out в потребителей

    5

    Consumers

    Проекции, интеграции, side-effects

    CQRS: write-path и read-path

    Оба пути отображаются параллельно и запускаются независимо.

    write-path (commands)

    1

    Client

    POST/PUT command

    2

    Command Model

    Инварианты и бизнес-правила

    3

    Event Stream

    Факты домена

    4

    Projector

    Обновляет read-модель

    read-path (queries)

    1

    Client

    GET query

    2

    Query API

    Read-only endpoint

    3

    Read Model

    Денормализованная проекция

    Saga: coordination styles

    Оркестратор централизованно решает, какой шаг выполнить следующим и когда запускать компенсации.

    Все ключевые команды и решения проходят через один central coordinator.

    Order Service

    OrderCreated

    Orchestrator
    Orchestrator

    ReserveFunds

    Payment Service
    Payment Service

    PaymentReserved

    Orchestrator
    Orchestrator

    ReserveStock

    Inventory Service
    Inventory Service

    InventoryReserved

    Orchestrator
    Orchestrator

    CreateShipment

    Shipping Service
    Shipping Service

    ShipmentCreated

    Orchestrator
    Orchestrator

    OrderCompleted

    Order Service
    Ключевая идея: один центральный orchestrator принимает решения и управляет вызовами сервисов.

    Related

    Microservice Patterns

    Отдельная глава про integration и распределенные транзакции.

    Открыть главу

    Event Sourcing + CQRS + Saga

    Event Sourcing

    State хранится как последовательность событий, а не как последнее значение.

    Когда применять

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

    Trade-offs

    • Сложнее миграции event schema и versioning.
    • Нужны snapshot и replay-стратегии для производительности.

    CQRS

    Разделение write-модели (commands) и read-модели (queries).

    Когда применять

    • Read/write профили сильно различаются.
    • Нужны отдельные read-оптимизированные проекции.
    • Система растет и требует независимого масштабирования путей.

    Trade-offs

    • Увеличивается операционная сложность и число компонентов.
    • Read-model часто eventual consistent относительно write-model.

    Saga

    Управление распределенной транзакцией через цепочку локальных шагов + компенсации.

    Когда применять

    • Операция затрагивает несколько сервисов/хранилищ.
    • 2PC недоступен или непрактичен.
    • Нужен контролируемый rollback через compensating actions.

    Trade-offs

    • Нужно проектировать идемпотентность и повторные доставки.
    • Сложнее отладка долгоживущих и частично завершенных процессов.

    Related Book

    Software Architecture: The Hard Parts

    Подробный разбор trade-offs, distributed workflows, orchestration/choreography и практики Saga.

    Открыть главу

    Saga: стили координации

    Choreography

    Сервисы подписываются на события и реагируют без центрального координатора.

    Плюсы

    • Слабая связность
    • Меньше центральных bottleneck

    Риски

    • Сложнее трассировка end-to-end
    • Риск «event spaghetti»

    Orchestration

    Оркестратор явно управляет шагами и компенсациями.

    Плюсы

    • Прозрачный flow и observability
    • Проще верифицировать бизнес-процесс

    Риски

    • Центральная точка сложности
    • Нужно масштабировать orchestration layer

    Матрица выбора

    ПотребностьРекомендацияПочему
    Полный audit trail и replayEvent SourcingИстория изменений - first-class data.
    Отдельные read/write SLACQRSНезависимая оптимизация и масштабирование read/write-path.
    Distributed transaction без 2PCSagaЛокальные транзакции + компенсирующие действия.
    Простая CRUD-система без высокой сложностиНе форсировать EDAОперационная сложность может быть выше бизнес-выгоды.

    Частые ошибки

    • Пытаться внедрить все паттерны сразу без явного SLA и bounded context.
    • Считать событие «фактом», но публиковать в него технический шум без бизнес-смысла.
    • Не делать идемпотентность consumer-ов при at-least-once доставке.
    • Игнорировать schema versioning и обратную совместимость событий.
    • Не отслеживать DLQ, lag, duplicate rate и время завершения saga.

    Related

    Паттерны отказоустойчивости

    DLQ дополняет retry/backoff и ограничивает каскадные сбои в consumer-пайплайнах.

    Открыть главу

    Dead Letter Queue (DLQ)

    DLQ - это карантин для сообщений, которые не удалось обработать после retry. Цель DLQ - не «спрятать» ошибку, а сохранить проблемные события для безопасного разбора и контролируемого повторного запуска.

    Когда отправлять в DLQ

    Когда message превышает лимит попыток, нарушает schema contract или consistently падает из-за data issues.

    Что сохранять

    `messageId`, `attempts`, `lastError`, `originalTopic`, `failedAt`, версию контракта и ссылку на payload.

    Что делать после

    Делать triage, фиксить root cause и запускать re-drive batch с контролем rate limit и idempotency.

    Практический checklist для DLQ

    • Выделяйте отдельный DLQ на каждый критичный поток, чтобы не смешивать домены и приоритеты.
    • Храните причину ошибки, число попыток, исходный topic/queue и payload reference для расследования.
    • Разделяйте transient и non-transient ошибки: не всё должно попадать в DLQ после одинакового числа retry.
    • Настройте re-drive процесс: ручной или автоматический возврат сообщений после исправления причины сбоя.
    • Добавьте алерты на скорость роста DLQ и SLA по времени разбора poison messages.

    Мини-чеклист внедрения

    1. Зафиксируйте event contract и policy versioning.
    2. Обеспечьте idempotency для producer/consumer.
    3. Настройте DLQ, retry policy и poison-message handling.
    4. Мониторьте lag, replay time и success-rate saga.

    Связанные главы

    Практический подход: сначала введите события для 1-2 критичных процессов, добавьте наблюдаемость и правила ретраев, и только после этого масштабируйте EDA на остальные bounded contexts.

    Сначала контракт и операционная дисциплинапотом масштабирование паттерна.