Событийная архитектура ценна не абстрактным обещанием слабой связности, а тем, что позволяет разнести принятие решения, публикацию факта и реакцию зависимых сервисов во времени.
Глава раскладывает Event Sourcing, CQRS и Saga на отдельные инженерные решения, чтобы было видно, где асинхронность действительно упрощает систему, а где она приносит эволюцию схем, повторное воспроизведение, лаги согласования и более дорогую эксплуатацию.
В инженерных обсуждениях это помогает спокойно разбирать оркестрацию и хореографию, границы событий, зависающие процессы и цену асинхронности без наивной идеи, что один брокер автоматически сделает систему гибче.
Практическая польза главы
Контракты событий
Проектируйте события как устойчивые бизнес-факты с совместимой эволюцией схем, а не как случайные транспортные пакеты.
Координация шагов
Осознанно выбирайте между оркестрацией и хореографией и заранее фиксируйте, где нужен явный управляющий контур.
Повторы и DLQ
Планируйте повторное воспроизведение, DLQ и идемпотентность обработчиков так, чтобы сбой не превращался в потерю данных.
Аргументация выбора
Объясняйте, когда события действительно снижают связанность, а когда они только добавляют лаги и операционную сложность.
Источник
Martin Fowler: Event Sourcing
Классический текст о том, зачем хранить историю состояния через события и где этот подход действительно полезен.
полезна не потому, что один брокер сам по себе автоматически делает систему гибкой, а потому, что позволяет разнести момент принятия решения и момент реакции на него. Эта свобода даёт масштабируемость и слабую связность, но требует аккуратно проектировать события, контракты и восстановление после сбоев.
Базовые принципы событийной архитектуры
В этой главе мы отдельно разберём , и . Это не обязательный комплект на все случаи, а три разных инструмента, которые по-разному помогают управлять историей изменений, нагрузкой на чтение и запись и длинными бизнес-процессами.
Для этого полезно заранее договориться, что такое , как устроены и , когда требуется и какую роль играют , и , и где действительно нужен . Если система живёт с , доставкой , , , и , то эти решения нужно принимать отдельно внутри каждого , а не размазывать один и тот же шаблон на всю систему.
Событие фиксирует факт
Событие описывает то, что уже произошло в домене, и не должно меняться задним числом.
Асинхронность разрывает связность
Производители и потребители могут развиваться отдельно, если контракт события остаётся стабильным.
Согласование приходит не мгновенно
Между записью и итоговым видом данных часто есть лаг, который продукт и API должны переживать предсказуемо.
Визуализация потоков данных
Ниже показаны три характерных сценария: базовый событийный поток, разделение чтения и записи в CQRS и координация шагов в паттерне Saga.
Анимации потоков данных
Каждую схему можно запускать отдельно кнопкой «Старт».
Базовый поток событий
API команд
Принимает бизнес-команду
Доменный агрегат
Проверяет инварианты и принимает решение
Хранилище событий
Последовательно записывает события
Брокер или журнал
Раздаёт события потребителям
Потребители
Строят проекции, интеграции и побочные действия
CQRS: путь записи и путь чтения
Обе ветки показаны параллельно и запускаются независимо.
Путь записи
Клиент
Отправляет команду POST или PUT
Модель команд
Применяет правила и инварианты
Поток событий
Фиксирует факты домена
Проектор
Обновляет модель чтения
Путь чтения
Клиент
Отправляет запрос GET
API чтения
Отдаёт только данные для чтения
Модель чтения
Хранит денормализованную проекцию
Saga: стили координации
Отдельный координатор решает, какой шаг запускать дальше и когда начинать компенсации.
Все ключевые команды и решения проходят через одного координатора.
OrderCreated
ReserveFunds
PaymentReserved
ReserveStock
InventoryReserved
CreateShipment
ShipmentCreated
OrderCompleted
Связанная глава
Microservice Patterns
Отдельная глава про интеграционные паттерны, Saga и границы распределённых транзакций.
Event Sourcing, CQRS и паттерн Saga
Event Sourcing
Состояние восстанавливается из последовательности событий, а не хранится только как последнее значение.
Когда применять
- Нужен полный журнал аудита и воспроизводимая история изменений.
- Важно пересчитывать проекции или строить новые представления данных по истории событий.
- Предметная область естественно описывается через факты, которые произошли во времени.
Компромиссы
- Сложнее эволюция схемы событий и миграции старой истории.
- Нужны снимки состояния и стратегия повторного воспроизведения, чтобы не замедлить систему.
CQRS
Путь записи и путь чтения разделяются, чтобы каждый из них оптимизировать под свою нагрузку и модель данных.
Когда применять
- Нагрузка на чтение и запись заметно различается.
- Нужны отдельные проекции, оптимизированные под быстрые чтения.
- Система выросла настолько, что пути чтения и записи выгодно масштабировать независимо.
Компромиссы
- Увеличиваются операционная сложность и число компонентов.
- Модель чтения часто сходится с путём записи не мгновенно, а с лагом.
Saga
Распределённая операция раскладывается на локальные шаги и компенсирующие действия вместо одной общей транзакции.
Когда применять
- Одна бизнес-операция затрагивает несколько сервисов или хранилищ.
- 2PC недоступен или слишком дорог по задержке и связанности.
- Нужно управляемо откатывать частично завершённый процесс через компенсации.
Компромиссы
- Каждый шаг и каждая компенсация должны переживать повторы без двойного эффекта.
- Отладка долгоживущих и частично завершённых процессов становится сложнее.
Связанная книга
Software Architecture: The Hard Parts
Подробный разбор инженерных компромиссов, распределённых процессов и практики применения Saga.
Saga: стили координации
делает процесс прозрачнее и централизует решения, а сильнее ослабляет связанность, но сложнее для трассировки и отладки.
Хореография
Сервисы подписываются на события и сами запускают следующий шаг без центрального координатора.
Плюсы
- Слабее связность между сервисами
- Нет единой управляющей точки, которая станет узким местом
Риски
- Сложнее проследить весь путь операции целиком
- Легко получить хаотичную сеть событий без явных границ
Оркестрация
Отдельный координатор явно решает, какой шаг выполнять дальше и когда запускать компенсации.
Плюсы
- Проще наблюдать процесс целиком
- Легче проверять бизнес-логику и правила переходов
Риски
- Появляется центральная точка сложности
- Контур оркестрации нужно отдельно масштабировать и защищать
Как выбирать паттерн
| Задача | Что выбрать | Почему |
|---|---|---|
| Нужна полная история изменений и восстановление состояния | Event Sourcing | История становится первичными данными, а не побочным журналом. |
| К чтению и записи предъявляются разные требования по уровню сервиса и профилю нагрузки | CQRS | Можно отдельно оптимизировать и масштабировать путь чтения и путь записи. |
| Одна операция охватывает несколько сервисов без общей транзакции | Saga | Локальные шаги и компенсации дают контролируемое завершение процесса. |
| Система остаётся простой CRUD-моделью без сложных интеграций | Не навязывать событийную архитектуру | Операционная сложность может оказаться выше реальной бизнес-пользы. |
Частые ошибки
- Внедрять Event Sourcing, CQRS и Saga одновременно без явной проблемы, которую нужно решить.
- Публиковать в события технический шум вместо фактов предметной области.
- Не проектировать идемпотентность обработчиков при доставке как минимум один раз.
- Оставлять эволюцию схемы событий без политики обратной совместимости.
- Не следить за DLQ, лагом обработки, скоростью роста дублей и временем завершения Saga.
Связанная глава
Паттерны отказоустойчивости
DLQ дополняет повторы, тайм-ауты и изоляцию сбоев в контурах асинхронной обработки.
Очередь ошибочных сообщений (DLQ)
DLQ нужна не для того, чтобы спрятать сбой, а чтобы безопасно изолировать проблемные сообщения, разобраться в причине и вернуть их в поток контролируемо. Она особенно важна там, где обработчик работает с внешними зависимостями, нестабильными данными или дорогими побочными эффектами.
Когда отправлять в DLQ
Когда сообщение исчерпало лимит повторов, нарушает контракт события или стабильно падает из-за данных.
Что сохранять
Идентификатор сообщения, число попыток, последнюю ошибку, источник, время сбоя и ссылку на полезную нагрузку.
Что делать дальше
Разобрать причину, исправить её и вернуть сообщения в основной поток с контролем скорости и идемпотентности.
Практический чеклист для DLQ
- Выделяйте отдельную DLQ для каждого критичного потока, чтобы не смешивать домены и приоритеты.
- Сохраняйте причину ошибки, число попыток, исходный топик или очередь и ссылку на полезную нагрузку для расследования.
- Разделяйте временные и постоянные ошибки: не все сообщения должны уходить в DLQ после одинакового числа повторов.
- Настройте процесс возврата сообщений в основной поток после исправления причины сбоя: вручную или автоматически.
- Следите за скоростью роста DLQ и договоритесь о целевом времени разбора проблемных сообщений.
Проверки перед внедрением
Практический подход: начните с одного-двух критичных процессов, сначала зафиксируйте контракт, наблюдаемость и правила повторов, и только потом масштабируйте событийную модель на остальные контуры системы.
Связанные главы
- Консистентность и идемпотентность - помогает проектировать обработку повторных событий, паттерны outbox и inbox и корректность при доставке как минимум один раз.
- Паттерны устойчивости: Circuit Breaker, Bulkhead, Retry - дополняет событийную архитектуру правилами повторов, тайм-аутами и управляемой деградацией обработчиков.
- Distributed Message Queue - показывает практическую архитектуру очередей, разбиение на партиции, гарантии порядка и пределы пропускной способности событийных потоков.
- Паттерны взаимодействия между сервисами - сравнивает синхронные и асинхронные интеграции и помогает понять, где события лучше прямого RPC-взаимодействия.
- Принципы проектирования масштабируемых систем - объясняет компромиссы между задержкой и пропускной способностью и показывает, как работать с обратным давлением в событийных системах.
- Kafka: The Definitive Guide, 2nd Edition (short summary) - даёт фундамент по журналам событий, партиционированию, сценариям доставки и эксплуатационным практикам вокруг Kafka.
- Microservice Patterns (short summary) - углубляет темы паттерна Saga, распределённых транзакций и интеграционных шаблонов для микросервисов.
