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
Command API
Получает intent
Domain Aggregate
Валидирует и принимает решение
Event Store
Append-only запись событий
Broker/Log
Fan-out в потребителей
Consumers
Проекции, интеграции, side-effects
CQRS: write-path и read-path
Оба пути отображаются параллельно и запускаются независимо.
write-path (commands)
Client
POST/PUT command
Command Model
Инварианты и бизнес-правила
Event Stream
Факты домена
Projector
Обновляет read-модель
read-path (queries)
Client
GET query
Query API
Read-only endpoint
Read Model
Денормализованная проекция
Saga: coordination styles
Оркестратор централизованно решает, какой шаг выполнить следующим и когда запускать компенсации.
Все ключевые команды и решения проходят через один central coordinator.
OrderCreated
ReserveFunds
PaymentReserved
ReserveStock
InventoryReserved
CreateShipment
ShipmentCreated
OrderCompleted
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 и replay | Event Sourcing | История изменений - first-class data. |
| Отдельные read/write SLA | CQRS | Независимая оптимизация и масштабирование read/write-path. |
| Distributed transaction без 2PC | Saga | Локальные транзакции + компенсирующие действия. |
| Простая 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-2 критичных процессов, добавьте наблюдаемость и правила ретраев, и только после этого масштабируйте EDA на остальные bounded contexts.
