Event-Driven Architecture¶
Event-Driven Architecture (EDA) - стиль взаимодействия, где компоненты общаются, производя события и реагируя на них, а не вызывая друг друга напрямую через request/response.
Термин раскрутили аналитики Gartner (в первую очередь Roy Schulte) примерно в 2003 году. Корни глубже: publish/subscribe и message-driven системы 80-90-х. Отдельной статьи, которая «придумала» EDA, нет. Идея кодифицировалась постепенно, в том числе в Enterprise Integration Patterns (Hohpe и Woolf, 2003).
Note
EDA - про то, как части системы общаются, а не про то, на сколько deployable units она нарезана. Событиями можно
связать микросервисы, SOA и даже модули одного монолита (Spring ApplicationEvent, Spring Modulith). Поэтому EDA
живёт в срезе Communication, а не в топологии.
Как работает¶
- producer публикует событие («заказ оплачен») в broker и забывает про него
- producer не знает, кто его слушает и слушает ли вообще, связи с consumer-ами нет
- consumer-ы подписаны на нужные события и реагируют каждый по-своему, асинхронно
Broker посередине принимает события и раздаёт подписчикам: Kafka, RabbitMQ, NATS (см. брокеры).
+---------------------+
+--> | consumer: биллинг |
| +---------------------+
+------------+ order.paid +---------+ +---------------------+
| producer | -----------> | broker |->| consumer: склад |
| (checkout) | | (Kafka) | +---------------------+
+------------+ +---------+ +---------------------+
+--> | consumer: аналитика |
+---------------------+
producer не знает про этих троих. Добавить четвёртого - producer не трогаешь.
Низкая связность видна прямо на схеме: новый consumer подключается без изменений в producer. Это главный аргумент за EDA.
Что делает систему event-driven¶
Брокер - это транспорт, а EDA - модель взаимодействия. Система событийная, когда:
- сообщение - факт в прошедшем времени («заказ оплачен»), а не команда («спиши деньги»)
- producer не знает получателей и не ждёт от них ответа
- каждый consumer сам решает, как реагировать, а новые подключаются без правки producer
Если гонять через Kafka команды конкретному сервису или request/reply с ожиданием ответа, это messaging, но не EDA. И наоборот, EDA строят и без Kafka: на webhooks, на CDC из базы, на шине событий внутри процесса.
Плюсы и минусы¶
Плюсы:
- decoupling: producer и consumer не знают друг о друге и развиваются независимо
- масштабирование: consumer-ов масштабируешь отдельно, под свою нагрузку
- буфер нагрузки: broker гасит пики, consumer разгребает очередь в своём темпе (backpressure)
- расширяемость: новый потребитель события подключается без касания источника
Минусы:
- Eventual consistency. Состояние сходится не мгновенно, с этим живёшь, а не борешься.
- Отладка потоков. Одного стектрейса нет, путь события размазан по сервисам, нужны distributed tracing и correlation id.
- Event spaghetti. Когда событий много и неочевидно, кто на что реагирует, система превращается в неявный граф зависимостей. Это хуже монолита: связи не видно в коде.
- Дубли. Broker обычно даёт at-least-once delivery, одно событие может прийти дважды, поэтому consumer обязан быть идемпотентным (см. Данные и согласованность: outbox, idempotent consumer).
Грабли: thin или fat event. «Thin» event (только id и тип: «заказ X оплачен») заставляет consumer-а идти назад в producer за деталями, и связь появляется снова. «Fat» event (event-carried state transfer) несёт данные внутри, но раздувается и дублирует состояние. Выбор между ними - компромисс.
Broker и mediator topology¶
Mark Richards различает две схемы потока событий:
- Broker topology - центрального координатора нет. События идут через брокер от обработчика к обработчику, каждый публикует следующее событие. Это choreography
- Mediator topology - центральный event mediator знает шаги процесса и рассылает их обработчикам. Это orchestration
«Topology» здесь - схема потока событий, а не число deployable units.
Choreography и orchestration¶
Два способа организовать поток из нескольких шагов:
- Choreography - дирижёра нет. Каждый сервис слушает события и сам решает, что делать дальше, публикуя свои. Поток складывается из реакций. Развязка максимальная, но общую картину процесса нигде не видно: она живёт в головах и в логах
- Orchestration - есть явный координатор (orchestrator). Он знает шаги процесса и по очереди вызывает участников. Поток виден в одном месте, отлаживать проще, но координатор - точка связности и узкое место
Choreography хороша для простых независимых реакций, orchestration - когда процесс сложный и важно видеть его целиком. В одной системе обычно встречаются оба. Как обе формы ложатся на Saga, см. SAGA.
Saga: транзакции через события¶
Saga - длинная транзакция как последовательность локальных транзакций, где у каждого шага есть компенсирующая операция. Общей ACID-транзакции через несколько сервисов нет, 2PC блокирует и плохо масштабируется. Поэтому каждый сервис делает свой локальный commit, а при сбое запускаются компенсации в обратном порядке.
В EDA шаги саги связаны событиями (choreography) или их ведёт отдельный saga-orchestrator (orchestration). Компенсации и повторы при at-least-once должны быть идемпотентными.
См. SAGA.
В других срезах¶
- Модель обмена - Sync и async: когда событие, а когда вызов с ответом
- Брокеры - Communication & API Style, Kafka и RabbitMQ, устройство Kafka
- Данные - Event Sourcing: лог событий как источник истины. Outbox и idempotent consumer: как не потерять и не задвоить событие
- Код - CQRS: раздельные модели чтения и записи, read model часто обновляют событиями
- Согласованность - SAGA