Перейти к содержанию

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