Прежде чем спорить про Event Sourcing и CQRS, событийная система упирается в куда более приземлённый вопрос: что вообще положить в событие? Два ответа. Тонкое уведомление (event notification) — «заказ 42 изменился», и всё; кому надо — сходит и запросит детали. Толстое событие с состоянием (event-carried state transfer) — «заказ 42: статус paid, сумма 1000, позиции […]»: консьюмер получает всё сразу и никуда не ходит. Выбор выглядит как деталь сериализации, а на деле определяет связанность сервисов, нагрузку на источник и то, насколько свежими будут данные у потребителя.
Эта статья закрывает базовый пробел серии: концептуальная карта разводит command/event/message и domain/integration, а ES и CQRS — это уже продвинутые паттерны из той же таксономии Фаулера; здесь — два оставшихся, самых частых в повседневной интеграции.
В статье
- Два паттерна
- Главный компромисс
- Обратная сторона notification: GET-storm
- Обратная сторона state transfer
- Версионирование и приватность payload
- Связь с ES и CDC
- Лунная база: телеметрия датчиков
- Когда что и почему гибрид
- Checklist: что кладём в событие
Два паттерна
Возьмём «заказ оплачен». Одно и то же событие можно оформить двумя способами.
Event notification (тонкое): событие несёт минимум — тип, ID, может пару полей.
OrderPaid { orderId: "42" }
→ консьюмер: "ага, заказ 42 оплачен; надо детали — схожу спрошу GET /orders/42"
Event-carried state transfer (толстое): событие несёт нужное состояние целиком.
OrderPaid { orderId: "42", status: "paid", amount: 1000,
currency: "RUB", items: [...], customer: {...} }
→ консьюмер: "всё уже есть, никуда идти не надо"
Технически оба — обычные сообщения в брокере. Разница — в объёме и в том, самодостаточен ли получатель.
Главный компромисс
| Ось | Notification (тонкое) | State transfer (толстое) |
|---|---|---|
| Размер события | минимальный | крупный |
| Связанность по данным | слабая (только ID) | сильная (схема состояния) |
| Автономность консьюмера | низкая (идёт за деталями) | высокая (всё в событии) |
| Свежесть данных | всегда актуальные (запросил сейчас) | снимок на момент публикации |
| Нагрузка на источник | высокая (GET-storm) | низкая |
| Устойчивость к недоступности источника | нет (не обработать без него) | да |
Ни одна колонка не выигрывает целиком. Это тот же водораздел «где живёт знание», что в хореографии vs оркестрации, только на уровне данных: notification оставляет знание в источнике (иди спроси), state transfer раздаёт его копией в каждое событие.
Обратная сторона notification: GET-storm
Тонкое событие выглядит дёшево — но переносит стоимость на потом. На каждое OrderPaid N подписчиков синхронно идут в источник за деталями: GET /orders/42. Одно событие → N запросов обратно. При всплеске событий это GET-storm: источник, который событиями хотели разгрузить, получает синхронную нагрузку от всех потребителей разом — и мы вернулись к той самой синхронной связанности, которую event-driven обещал убрать.
Хуже того — это временная связанность: если источник недоступен, консьюмер не может обработать событие (детали-то не забрать). Асинхронность на бумаге, синхронная зависимость на деле.
Обратная сторона state transfer
Толстое событие автономно, но платит четырьмя вещами:
- Размер. Полное состояние в каждом событии раздувает лог и трафик; на высокочастотных потоках это ощутимо.
- Staleness. Состояние в событии — снимок на момент публикации. Пока событие дошло и обработалось, данные могли устареть; потребитель действует по слепку прошлого.
- Связанность по схеме. Теперь все консьюмеры зависят от структуры состояния в событии — любое изменение формата задевает всех (прямой мостик к контрактам событий).
- Приватность. Рассылать полное состояние «на всякий случай» — значит разложить PII по всем подписчикам и логам брокера. Что не нужно потребителю — не кладём в событие (мостик к крипте и GDPRСкоро).
Версионирование и приватность payload
Как только событие несёт состояние, оно становится контрактом данных — и живёт по правилам эволюции схем: обратная/прямая совместимость, добавление полей, upcasting старых версий. Это ровно тема контрактов событий и schema evolution, и толстые события делают её обязательной, а не опциональной: у notification ломать почти нечего (там один ID), у state transfer — целая структура, от которой зависят все.
Приватность — вторая цена контракта: чем богаче payload, тем выше риск утечки персональных данных. Правило — класть в событие минимум, необходимый потребителям, а не «всё, что есть в записи».
Связь с ES и CDC
- Event Sourcing: лог событий внутри границы агрегата — по сути поток state transfer (события несут, что изменилось). Но наружу публикуют не сырой внутренний лог, а специально оформленные integration events — иначе внутренняя модель протекает во все сервисы.
- CDCготовится, с 6 сентября: захват изменений строк — это автоматически порождаемый state transfer (было/стало по строке). Мощно, но именно поэтому связывает потребителей со схемой таблиц источника — о чём часто забывают, включая Debezium «чтобы просто получать события».
Лунная база: телеметрия датчиков
Датчики модуля публикуют показатели. Два оформления — разная цена.
Notification: ModuleReadingsChanged { module: "A" }. Диспетчерская, аналитика и система оповещения на каждое событие идут в шлюз датчиков за деталями. В штатном режиме терпимо; во время нештатной ситуации, когда показатели меняются каждую секунду, три подписчика × частота = GET-storm ровно тогда, когда шлюз и так под нагрузкой. И если шлюз просел — оповещение не обработает событие, потому что не заберёт детали.
State transfer: ModuleReadings { module: "A", pressure: 98.2, temp: 21.4, o2: 20.9, ts: ... }. Каждый потребитель автономен, шлюз не дёргают. Цена — крупнее поток и риск staleness: если событие задержалось, оповещение сработает по устаревшему давлению.
Отсюда практический вывод — гибрид: критичную телеметрию слать state transfer (автономность важнее объёма), а редкие «что-то поменялось в конфигурации модуля» — notification.
Когда что и почему гибрид
Notification уместен, когда: много разнородных потребителей с разными потребностями в данных (каждый заберёт своё), детали тяжёлые и не всем нужны, критична слабая связанность по данным, а нагрузка на источник управляема.
State transfer уместен, когда: потребитель должен быть автономным и переживать недоступность источника, данные компактны, важна независимость от доступности источника, высокая частота обращений сделала бы GET-storm неподъёмным.
Гибрид — норма. Тонкое уведомление + ссылка/версия для тех, кому нужны детали; или state transfer для «горячих» полей, за которыми ходят все, + notification для остального. Как и с координацией, выбор делается на конкретный поток событий, а не на всю систему. Как это ложится на прочие событийные решения — в матрице решенийготовится, с 8 августа.
Checklist: что кладём в событие
- Кто потребители события и какие данные им реально нужны?
- Если notification — выдержит ли источник GET-storm от всех подписчиков на пике?
- Критична ли для потребителя автономность / устойчивость к недоступности источника? → склоняет к state transfer.
- Если state transfer — заложена ли эволюция схемы события (совместимость, версии)?
- Нет ли в payload PII, которого потребителям не нужно (приватность)?
- Не устареет ли состояние в событии к моменту обработки (staleness приемлем)?
- Решение принято на конкретный поток, а не на всю систему разом?
Демо и версии
- Demo:
digital-cookbook/architecture/event-payload— один поток «заказ оплачен» в двух формах: (1) notification (тип+ID, консьюмер идёт за деталями — видно всплеск запросов к источнику под нагрузкой); (2) state transfer (полное состояние в событии — консьюмер автономен, но payload крупнее и устаревает при задержке). Go/Java. - Версии брокера/библиотек пиновать при написании.
Документация и первоисточники
- Канон: Martin Fowler — What do you mean by “Event-Driven”? (определяет event notification, event-carried state transfer, event sourcing, CQRS), Gregor Hohpe — Enterprise Integration Patterns (Document Message vs Event Message), microservices.io — Domain event.
- Смежное на сайте: событийная архитектура — карта, контракты событий и schema evolution, Event Sourcing — концепция, CQRS, хореография vs оркестрация, CDC (Debezium)готовится, с 6 сентября, гарантии доставки и идемпотентность, взаимодействия sync/async, матрица решенийготовится, с 8 августа.
Комментарии