Событийно-ориентированная архитектура (EDA)

Назначение: класс архитектур для асинхронного взаимодействия компонентов через события.

Аудитория: архитекторы, техлиды, backend-разработчики.

Статус: устоявшийся подход (pub/sub — Pat Helland, «Data on the Outside vs. Data on the Inside», 2005; EAI-паттерны — Gregor Hohpe и Bobby Woolf, «Enterprise Integration Patterns», 2003).

Не путать с: очередями сообщений (транспорт), CQRS (паттерн разделения моделей чтения и записи), Event Sourcing (паттерн хранения состояния).

Общее

Событийно-ориентированная архитектура (event-driven architecture, EDA) — архитектурный стиль, при котором компоненты системы взаимодействуют через события: значимые факты предметной области («заказ оформлен», «платёж отклонён», «товар распродан»), которые одни компоненты публикуют, а другие — потребляют, реагируя на них. Определяющее отличие от привычной модели вызовов: издатель события не знает своих потребителей, не ждёт результата их работы и вообще может не знать, существуют ли они. Компоненты не вызывают друг друга напрямую — они реагируют на события, и вся координация происходит через поток фактов, а не через цепочку запросов.

Противопоставление базовой модели взаимодействия в распределённых системах:

  • Request/response (синхронные вызовы). Вызывающий знает адрес вызываемого, формирует запрос, ожидает ответ в том же потоке выполнения и получает результат или ошибку немедленно. Это интуитивная модель программирования: запрос → обработка → ответ. Плата за неё — временна́я связность (temporal coupling): обе стороны должны быть доступны одновременно, а медленный или упавший вызываемый блокирует вызывающего, распространяя отказ по цепочке зависимостей вверх.
  • События (асинхронный обмен). Издатель фиксирует факт и продолжает работу; брокер доставляет событие подписчикам, когда те способны его принять; каждый потребитель обрабатывает событие независимо и в своё время. Ответ, если он вообще нужен, возвращается отдельным событием в обратную сторону. Связность падает сразу по нескольким осям: издатель не знает адресов, количества и даже существования потребителей (пространственная связность), не зависит от их доступности и скорости (временна́я), а интерфейс взаимодействия сводится к схеме события, а не к API каждого потребителя (платформенная).

Исторически стиль вырос из обмена сообщениями в корпоративной интеграции. Модель publish/subscribe была исследована ещё в конце 1980-х в системе Isis (Бирман и коллеги, Корнеллский университет, 1987); в 1990-е её коммерциализировала TIBCO с продуктом Rendezvous для рассылки рыночных котировок финансовым приложениям. Книга Грегора Хопе (Gregor Hohpe) и Бобби Вульфа (Bobby Woolf) Enterprise Integration Patterns (2003) систематизировала весь инструментарий обмена сообщениями — каналы, маршрутизаторы, агрегаторы, correlation identifier — и до сих пор остаётся каноническим справочником темы; публиковать события и потреблять их — это паттерны «Publication-Subscription» и «Message Endpoint» из этой книги. Термин «event-driven architecture» ввёл в оборот аналитик Gartner (Рой Шульте, начало 2000-х), а рабочее определение стиля дала Бренда Михельсон (Brenda Michelson) в обзоре Event-Driven Architecture Overview (2006): события инициируют сообщения, сообщения — обработку, обработка — новые события. Фундаментальную роль в осмыслении событий сыграла статья Пата Хелланда (Pat Helland) Data on the Outside vs. Data on the Inside (CIDR, 2005): данные, покидающие границы транзакции (а событие — ровно такой случай), — это неизменяемые, снабжённые идентичностью факты, а не разделяемое состояние.

Технологическая волна 2000–2010-х сделала стиль массовым: стандарт AMQP и брокер RabbitMQ (2007), корпоративные шины предыдущего десятилетия (см. SOA), а затем Apache Kafka (LinkedIn, 2011) — распределённый журнал, который превратил поток событий из ephemeral-рассылки в долговременно хранимую, перечитываемую запись. Параллельно сложился реактивный подход к проектированию (Reactive Manifesto, 2014) с «асинхронной передачей сообщений» как одним из столпов. Сегодня EDA — не экзотика, а де-факто стандартный инструмент: событиями связывают микросервисы, строят аналитику в реальном времени, синхронизируют данные между системами и отправляют уведомления пользователям.

Хронология ключевых вех стиля:

ГодВехаЗначение для стиля
1987Isis (Бирман, Корнелл)Исследование publish/subscribe в отказоустойчивых распределённых системах
1990-еTIBCO RendezvousПромышленная рассылка событий (финансовые рынки)
2001Java Message Service (JMS)Стандартизация обмена сообщениями для Java
2003Hohpe и Woolf, Enterprise Integration PatternsКаталог из 65 паттернов обмена сообщениями
2003–2006Gartner (Шульте); Михельсон, EDA OverviewТермин и определение стиля
2005Хелланд, Data on the Outside vs. Data on the InsideСобытие как неизменяемый факт с идентичностью
2007AMQP 0-9-1, RabbitMQОткрытый стандарт и open-source брокер
2011Apache KafkaЖурнал событий как долговременное хранилище потока
2014Reactive ManifestoАсинхронный обмен сообщениями как столп реактивных систем
2017Kafka 0.11 (KIP-98)«Ровно один раз» внутри границ Kafka: идемпотентный продюсер и транзакции

Терминологическая оговорка. EDA — стиль проектирования, а не конкретная технология: событиями можно обмениваться через Kafka и RabbitMQ, через Redis и облачные SNS/SQS, через MQTT в IoT и даже через вебхуки между компаниями. Обратное смешение тоже неверно: очередь сообщений — это транспорт, который может возить что угодно (команды, события, документы), и сам по себе не делает архитектуру событийной. Событийной её делает семантика обмена: компоненты публикуют факты и подписываются на факты, а не вызывают друг друга. Наконец, EDA редко существует в чистом виде: практически каждая реальная система гибридна — синхронные запросы там, где нужен немедленный ответ (просмотр каталога, авторизация), и события там, где нужна рассылка фактов (оформление заказа, смена статуса).

События и команды

Центральное решение в событийной системе — что именно летит в сообщении. Здесь проходит граница между событием (event) и командой (command), и смешение этих двух семантик — источник большинства проектных ошибок в EDA.

Событие — сообщение о факте, который уже произошёл. Отсюда два жёстких свойства. Во-первых, неизменяемость: событие нельзя отменить или «не допустить» — оно уже случилось; можно лишь отреагировать другим событием (компенсацией). Во-вторых, неадресность: издатель сообщает факт «в мир» (в топик), а не конкретному получателю — потребители подписываются сами. Соглашение об именовании отражает семантику: события называют в прошедшем времени — OrderPlaced («заказ был оформлен»), PaymentFailed («платёж не прошёл»), CustomerMoved. Формулировка в прошедшем времени — не стилистика, а тест на зрелость модели: «событие», названное в настоящем времени или повелительном наклонении (ProcessOrder, UpdateCustomer), почти всегда оказывается замаскированной командой.

Команда — сообщение-требование «что сделать», обращённое к конкретному получателю: ChargeCard, ReserveStock, SendEmail. Команда адресна (направляется одному исполнителю), выражена императивом и — принципиально — может быть отклонена: исполнитель вправе ответить «нет» (недостаточно средств, нет позиции на складе). Событие отклонить нельзя: глупо «отклонить» тот факт, что заказ оформлен, — факт уже свершился.

СвойствоСобытиеКоманда
Семантика«что произошло»«что сделать»
Время в названиипрошедшее (OrderPlaced)повелительное (ChargeCard)
Адресатне определён (все подписчики)конкретный исполнитель
Может быть отклоненонет — факт свершитсяда — исполнитель отвечает отказом
Издатель знает получателейнетда (логически)
Порождает связностьминимальнуюна исполнителя

Зачем строго различать. Команда создаёт зависимость от исполнителя: издатель должен знать, кто обязан обработать требование, и зависим от его судьбы — если исполнитель упал или отклонил команду, издателю приходится разбираться с последствиями. Событие освобождает издателя: он зафиксировал факт и забыл; ни один, ни пять, ни ноль потребителей не изменят его ответственности. Отсюда практическое правило: между командами — внутри процесса или сервиса; событиями — через границы сервисов. Нарушение (команды между сервисами через брокер) даёт классическую патологию: формально асинхронная, фактически жёстко связанная система, где «отказоустойчивость брокера» маскирует семантику удалённого вызова со всеми его проблемами.

Второе измерение — насыщенность события. Thin events («тонкие» события-уведомления) несут минимум данных: идентификатор и тип факта. Потребитель, которому нужны детали, вынужден вернуться к издателю за данными (callback через API) — это сохраняет единственный источник истины, но заново связывает компоненты синхронным вызовом и делает потребителя зависимым от доступности издателя. Event-carried state transfer (событие с переносом состояния; термин Мартина Фаулера, 2017) упаковывает в событие всё, что нужно потребителям. Потребители работают автономно, не обращаясь к издателю, — но платят дублированием данных, разрастанием схемы и жёсткостью контракта: любое поле в событии — это обязательство перед всеми подписчиками.

Одно и то же факт в двух вариантах насыщенности:

// Thin event: только уведомление, за деталями — callback в сервис «Заказы»
{
  "eventId": "b1c2...e9f0",
  "type": "OrderPlaced",
  "occurredAt": "2026-08-30T07:00:03+03:00",
  "orderId": 10427
}

// Event-carried state transfer: потребитель автономен, callback не нужен
{
  "eventId": "b1c2...e9f0",
  "type": "OrderPlaced",
  "occurredAt": "2026-08-30T07:00:03+03:00",
  "correlationId": "checkout-8f3a...c1",
  "orderId": 10427,
  "customerId": "C-42",
  "total": { "amount": 15990, "currency": "RUB" },
  "items": [
    { "sku": "SKU-0042", "quantity": 3, "price": 4330 },
    { "sku": "SKU-0117", "quantity": 1, "price": 2980 }
  ]
}

Критерий выбора — устойчивость данных: уведомление уместно, когда детали меняются быстрее, чем потребитель успевает их использовать; перенос состояния — когда факт самодостаточен и неизменен (именно поэтому event-carried state transfer естественен для аудита, аналитики и репликации).

Практические следствия для схемы события: у события обязаны быть идентичность (уникальный eventId — для дедупликации), тип и версия схемы (эволюция контракта без поломки потребителей) и контекст (correlationId цепочки, occurredAt момента факта). В высоконагруженных системах схемы формализуют контрактами — Avro или Protocol Buffers со Schema Registry (конвенция экосистемы Kafka), JSON Schema с реестром или просто строгими соглашениями в конспекте команды.

Топологии: broker и mediator

Компоненты событийной системы соединяются в две канонические топологии; классификация закреплена Марком Ричардсом (Mark Richards) в обзоре Software Architecture Patterns (O’Reilly, 2015). Различие между ними — в ответе на вопрос: кто управляет последовательностью обработки события.

Топология broker

Broker (брокер) — децентрализованная топология без центрального координатора. Издатели публикуют события в брокер (Kafka, RabbitMQ), брокер доставляет их подписчикам, а подписчики, завершив обработку, могут опубликовать свои события — продолжая цепочку. Никто не знает потока целиком; последовательность работ складывается из локальных решений каждого участника.

┌───────────┐  OrderPlaced  ┌──────────────────┐
│  Заказы   ├──────────────►│  Брокер (Kafka,  │
│ (Orders)  │               │  RabbitMQ)       │
└───────────┘               │  topic: orders   │
                            └──┬──────────┬──────┘
                   подписка    │          │    подписка
                               ▼          ▼
                     ┌──────────────┐  ┌──────────────────┐
                     │    Склад     │  │   Уведомления    │
                     │ (Inventory)  │  │ (Notifications)  │
                     └──────┬───────┘  └──────────────────┘
                            │ своё событие: StockReserved
                     ┌──────────────┐
                     │   Доставка   │
                     │  (Shipping)  │
                     └──────────────┘

  Поток: Orders ──OrderPlaced──► {Inventory, Notifications};
         Inventory ──StockReserved──► Shipping.
  Центрального координатора нет: каждый подписчик реагирует сам

Сила broker-топологии — в слабой связности и живучести: добавление нового потребителя (Аналитика может подписаться на OrderPlaced завтра, не трогая сервис «Заказы») не требует изменений у издателя; отказ одного участника не останавливает остальных. Слабость — в отсутствии центра: сквозной процесс «заказ → резерв → доставка» нигде целиком не описан, его приходится собирать в голове (и в системах мониторинга) из локальных подписок. Ошибку в середине цепочки заметит только тот, кто ждал результата; классический отказ — «зависшее» состояние, когда событие потерялось или обработчик молча упал.

Топология mediator

Mediator (посредник) — централизованная топология, в которой последовательностью шагов управляет специальный компонент — медиатор. Он принимает начальное событие, решает, какие шаги процесса выполнить, и направляет сервисам не события, а команды через каналы событий; сервисы отчитываются событиями обратно, медиатор продвигает процесс дальше. Медиатор не обязан быть отдельным продуктом — им бывает модуль оркестрации, workflow-движок (Temporal, Camunda, AWS Step Functions) или просто сервис-оркестратор.

                     ┌────────────────────────────────────┐
                     │              Mediator              │
                     │     (оркестратор процесса)         │
                     │                                    │
                     │  OrderPlaced:                      │
                     │    1. ReserveStock     — команда   │
                     │    2. ChargePayment    — команда   │
                     │    3. ScheduleShipping — команда   │
                     └──────┬──────────┬──────────┬───────┘
          команды            │          │          │    события-отчёты
                            ▼          ▼          ▼
                     ┌────────────┐ ┌───────────┐ ┌────────────┐
                     │   Склад    │ │ Платежи   │ │  Доставка  │
                     │(Inventory) │ │(Payments) │ │(Shipping)  │
                     └────────────┘ └───────────┘ └────────────┘
                            │             │           │
                            └─────────────┴───────────┘
                                          ▼  события о выполнении
                          (возвращаются медиатору: он ведёт состояние
                           процесса и запускает компенсации при отказах)

     Последовательность шагов централизована и видна целиком

Сила mediator-топологии — управляемость: процесс описан в одном месте, там же видны таймауты, повторные попытки и компенсации (шаг «вернуть платёж» при отказе склада — см. паттерн саги). Слабость — сам медиатор: он становится точкой связности (все участники процесса зависят от его контрактов) и кандидатом в узкое место. По сути, медиатор — это «маленькая умная шина» из SOA, и весь урок той эпохи о концентрации логики в посреднике здесь применим дословно.

Сравнение и выбор

ОсьBrokerMediator
Координациянет (хореография)центральный оркестратор
Знание о процессерассеяно по участникамсосредоточено в медиаторе
Связностьминимальнаячерез медиатора
Добавление участникаподписка, без правок издателяправка процесса у медиатора
Обработка отказовлокально каждым участникомцентрализованно (компенсации)
Наблюдаемость потокасложная (нужна корреляция)простая (лог процесса)
Единая точка отказасам брокер (транспорт)брокер + медиатор
Типичные инструментыKafka, RabbitMQTemporal, Camunda, Step Functions

Практическое правило выбора: простые рассылки фактов — broker; многошаговые процессы с ветвлениями и компенсациями — mediator. «Заказ оформлен → отправить письмо, обновить аналитику, пересчитать рекомендации» — независимые реакции без порядка и компенсаций: broker. «Оформить заказ: зарезервировать склад → списать оплату → назначить доставку, при любом отказе откатить сделанное» — процесс с порядком и компенсациями: mediator (оркестрированная сага). Реальные системы смешивают топологии: оркестратор координирует шаги процесса, а внутри шагов и вокруг них события расходятся по broker-подписчикам. Граница проходит не между системами, а между задачами: управление последовательностью — это работа оркестратора; оповещение о фактах — работа брокера.

Гарантии доставки и идемпотентность

Распределённая среда, в которой существует EDA, — это среда, где сеть теряет пакеты, процессы падают, а ответы теряются на обратном пути. Классическая иллюстрация — задача двух генералов: надёжно договориться по ненадёжному каналу невозможно без риска дублирования. Поэтому любой выбор семантики доставки — компромисс между потерями и повторами.

СемантикаПоведениеРискГде встречается
At-most-once (не более одного раза)Отправили и забыли, без повторовСообщение может потерятьсяМетрики, логи, некритичные телеметрия и уведомления
At-least-once (как минимум один раз)Повторы до подтверждения обработкиСообщение может прийти дваждыДефолт большинства брокеров; практически весь бизнес-обмен
Exactly-once (ровно один раз)Ни потерь, ни повторовЗамкнутые системы (Kafka→Kafka); сквозь произвольные системы — недостижимо

At-most-once подходит там, где потеря единичного сообщения дешевле, чем гарантия доставки: метрики, при которых терять один из тысячи замеров допустимо, телеметрия, «мягкие» уведомления. At-least-once — рабочая семантика почти всего бизнес-обмена: брокер подтверждает обработку, неподтверждённые сообщения доставляются повторно (после рестарта потребителя, обрыва соединения, таймаута), и это же делает повтор не аварией, а нормой жизни.

Exactly-once заслуживает отдельного разговора, потому что вокруг него много маркетинга. Сквозная доставка «ровно один раз» через произвольные сети и системы недостижима в принципе: получатель, обработав сообщение и упав до подтверждения, при перезапуске получит его снова — различить «не обрабатывал» и «обработал, но не подтвердил» невозможно. Достижима ограниченная версия: внутри одной системы, контролирующей и запись, и чтение, и хранение смещений. Так работает Kafka с включёнными идемпотентным продюсером и транзакциями (KIP-98, 2017): ровно один раз — от продюсера до консьюмер-группы, пока поток не покидает Kafka. Как только обработка затрагивает внешнюю систему (базу данных, HTTP-вызов, отправку письма), guarantee заканчивается — и система возвращается к at-least-once. Поэтому зрелая формулировка звучит так: exactly-once processing = at-least-once delivery + идемпотентная обработка. Именно идемпотентность, а не магия транспорта, закрывает проблему дублей.

Идемпотентность обработчика — способность повторного выполнения операции с тем же сообщением не менять результат. Приёмы, от простых к тяжёлым:

  • Естественная идемпотентность. Операция «установить статус заказа = confirmed» при повторе не меняет ничего. Там, где можно спроектировать обработчики как присваивания целевого состояния (а не приращений), это делается сознательно.
  • Дедупликация по ключу. Обработчик хранит идентификаторы обработанных сообщений (processed_messages(event_id)) в той же транзакции, что и результат; повтор с известным eventId пропускается.
  • Условные приращения. Если операция неидемпотентна по природе (balance += 100), её снабжают уникальностью: уникальный индекс на (event_id) в таблице операций не даст применить одно событие дважды.

Пример идемпотентного обработчика с дедупликацией (Python, псевдокод уровня production-паттерна):

def handle_order_placed(event, db):
    # Та же транзакция, что и результат: либо всё, либо ничего
    with db.transaction():
        already = db.insert_if_absent(
            "processed_messages",
            event_id=event.event_id,      # уникальный ключ дедупликации
            processed_at=db.now(),
        )
        if not already:                   # дубль — тихо пропускаем
            return
        order = db.load_order(event.order_id)
        order.status = "confirmed"        # присваивание, не приращение
        db.save(order)

Outbox pattern решает смежную проблему — двойной записи (dual-write). Сервису нужно и сохранить заказ в своей базе, и опубликовать событие; запись в две системы без общей транзакции ломает атомарность: база сохранилась, публикация потерялась (или наоборот). Outbox устраняет разрыв: событие пишется в ту же локальную транзакцию, что и данные, а отдельный процесс-ретранслятор вычитывает таблицу outbox и публикует события в брокер — с повторами, пока брокер не подтвердит.

  Транзакция в БД сервиса                Ретранслятор (poller / CDC)
 ┌──────────────────────────┐          ┌────────────────────────────┐
 │ BEGIN;                   │          │  outbox ───► broker        │
 │   INSERT orders ...;     │          │  (Debezium или поллер;     │
 │   INSERT outbox(event);  │ ───────► │   повторы до подтверждения,│
 │ COMMIT;                  │          │   идемпотентная запись)    │
 └──────────────────────────┘          └────────────────────────────┘
    заказ и событие атомарны             событие доберётся до брокера
                                         рано или поздно (at-least-once)

Цена паттерна — дополнительные компонент и задержка (миллисекунды у CDC, больше у поллера); выгода — атомарность «данные + событие» без распределённых транзакций. Ретранслятором обычно служит поллер по таблице или инкрементальный захват изменений (Debezium поверх WAL PostgreSQL или binlog MySQL).

Dead letter queue (DLQ, очередь «мёртвых писем») — куда попадают сообщения, которые не удалось обработать после всех повторов. Политика повторов задаётся на потребителе; исчерпавший попытки poison message — событие, валидное по формату, но невосприимчивое к обработке (например, ссылается на удалённую запись) — выводится из основного потока в DLQ, чтобы не блокировать остальных. Типовая политика повторов с экспоненциальной задержкой:

ПопыткаЗадержкаКомментарий
1сразуразовый сбой (таймаут, рестарт инстанса) проходит незамеченным
25 странзиентная ошибка внешней зависимости
330 срастущая задержка — шанс, что упавший dependency поднимется
42 минпоследняя попытка перед эскалацией
5→ DLQалерт, ручной разбор, исправление, replay из DLQ

DLQ — не мусорка, а операционный инструмент: сообщения в ней алертятся, разбираются вручную или автотестами, а накопление DLQ — сигнал о баге или расхождении контрактов. Без DLQ событийный поток уязвим к самой незаметной аварии: один необрабатываемый poison message тихо останавливает партицию и, вместе с ней, весь бизнес-процесс.

EDA в микросервисах; связь с CQRS и Event Sourcing

В микросервисной архитектуре, где каждый сервис владеет своими данными и не даёт другим лезть в свою базу, события — основной способ интеграции. Сервис «Заказы» публикует OrderPlaced; «Склад», «Платежи», «Уведомления», «Аналитика» подписываются, каждый хранит у себя нужную проекцию факта и не обращается к «Заказам» за данными. Это соответствует принципу «умные конечные точки, глухие каналы»: брокер остаётся пассивным транспортом, вся логика — в сервисах. Прямое следствие — распараллеливание команд: пока в sync-мире интеграция с новым потребителем означает правки у всех владельцев API, здесь новый сервис просто подписывается на топик, не тронув ни строчки в издателе.

  producer                  broker                        consumers
┌───────────┐  publishes ┌──────────┐   delivers    ┌────────────────────┐
│  Заказы   ├───────────►│  topic:  ├──────────────►│ Склад              │
│ (Orders)  │ OrderPlaced│  orders  │               │ проекция резерва   │
└───────────┘            │          │               └────────────────────┘
                         │          ├──────────────►┌────────────────────┐
                         │          │               │ Платежи            │
                         │          │               │ списание, баланс   │
                         │          │               └────────────────────┘
                         │          ├──────────────►┌────────────────────┐
                         │          │               │ Уведомления        │
                         │          │               │ письмо клиенту     │
                         │          │               └────────────────────┘
                         │          ├──────────────►┌────────────────────┐
                         │          │               │ Аналитика          │
                         │          │               │ витрина данных     │
                         └──────────┘               └────────────────────┘

  Потребители не знают ни издателя, ни друг друга; каждый хранит свою
  проекцию факта и масштабируется независимо

Цена этой свободы — итоговая согласованность (eventual consistency). После публикации OrderPlaced сервис «Склад» узнает о заказе не мгновенно, а через миллисекунды или секунды; в этот промежуток данные разных сервисов расходятся. Это осознанный компромисс, а не дефект: в терминах CAP-теоремы событийные системы выбирают доступность и устойчивость к разделению в ущерб строгой согласованности — при разрыве сети публикация и потребление продолжаются, а расхождение сходится после восстановления. Подход BASE фиксирует ту же идею с другой стороны: базовая согласованность вместо строгой, «в конечном счёте сошлось» вместо «всегда одинаково». Практический вывод для проектировщика: если сценарий требует читать «свою последнюю запись немедленно» (баланс после платежа, остаток после бронирования), событийная репликация этого не даёт — либо синхронный запрос к владельцу данных, либо маршрутизация записи и чтения в один сервис.

Отдельный пласт практик вырос из понимания, что поток событий — это ещё и источник истины. CQRS (Command Query Responsibility Segregation; Грег Янг, 2010) разделяет модель записи, обрабатывающую команды, и модели чтения — проекции, построенные из событий и заточенные под конкретные запросы (поиск, лента, отчёт). Event Sourcing (Мартин Фаулер, 2005) доводит идею до хранения: состояние агрегата — не строка в таблице, а последовательность событий его жизни; текущее состояние получается воспроизведением журнала. Обе практики естественно опираются на EDA-инфраструктуру (брокер как канал доменных событий), но не сводятся к ней: CQRS — про разделение моделей, Event Sourcing — про способ хранения; им посвящена отдельная статья Базы знаний — «CQRS и Event Sourcing». Важно не путать и уровни событий: доменные события описывают смену состояния агрегата внутри сервиса, события интеграции публикуются наружу как публичный контракт — у них разные схемы, версии и аудитории.

Зрелость событийной интеграции в микросервисах измеряется теми же вопросами, что и любой публичный API: события — это контракты, их эволюция (добавление полей допустимо, смена семантики — нет) должна быть управляемой, а потребители — изолированными от темпа изменений издателя. Отдельная статья Базы знаний, посвящённая очередям сообщений и брокерам как инфраструктуре событийного обмена (Kafka, RabbitMQ, их компромиссы), выйдет позже; здесь брокер рассматривался только как транспортный элемент архитектуры.

Плюсы

Слабая связность. Издатель не знает потребителей: их адреса, количество, существование. Добавление, удаление и отказ подписчика не касаются издателя — это единственный стиль, дающий такую изоляцию по умолчанию, без дополнительных прослоек.

Масштабируемость. Потребители масштабируются независимо друг от друга и от издателей: медленная аналитика добавляет инстансы, не трогая быстрые платежи. Брокер сглаживает пики — всплеск публикаций не обрушивает потребителей, а расходится по ним с естественным backpressure.

Отзывчивость. Издатель не блокируется: зафиксировал факт за миллисекунды и продолжил основной сценарий. Тяжёлые сопутствующие работы (уведомления, аналитика, репликация) уходят с критического пути пользователя.

Устойчивость к отказам отдельных компонентов. Упавший потребитель не роняет систему: события ждут его в брокере и будут обработаны после восстановления. Временная недоступность любого участника становится задержкой, а не аварией, — временная связность снята по построению.

Естественная асинхронность. Модель «факт → реакции» совпадает с реальной формой многих бизнес-процессов, где сопутствующие эффекты (письма, отчёты, рекомендации) не входят в транзакцию основного действия и не должны её удлинять.

Журнал фактов как побочный продукт. Поток событий — это уже готовый аудит: что произошло, когда и в каком порядке. На нём строятся аналитика, воспроизведение состояния (Event Sourcing) и интеграция с внешними системами — без отдельной системы логирования бизнес-операций.

Открытость к расширению. Новый потребитель подключается подпиской — без правок у издателя и координации релизов. Это «открытость для расширения» на уровне системы целиком: EDA даёт её без изменения опубликованного кода.

Минусы

Сложность отладки и трассировки. Логика потока рассеяна (scattered logic): чтобы понять, почему заказ «завис», приходится собирать цепочку из обработчиков разных сервисов, каждый из которых видел лишь своё событие. Стек-трейса не существует по построению — есть только последовательность фактов.

Итоговая согласованность как цена. Расхождение данных между сервисами — не баг, а режим работы, и клиент это ощущает: письмо о заказе приходит раньше, чем страница заказа обновилась. Команда обязана проектировать UX и инварианты под запаздывание, а не вопреки ему.

Проблемы упорядоченности событий. Порядок доставки — отдельное инженерное обязательство: партиционирование по ключу даёт порядок внутри ключа (но не между ключами), параллельные потребители меняют порядок обработки, а события из разных топиков не упорядочены вовсе. Ошибки порядка («отмена пришла раньше заказа») — отдельный класс трудноуловимых багов.

Сложность мониторинга. Синхронную систему мониторят по латентности и кодам ответов; событийную — по лагам потребителей, глубинам DLQ, сквозным цепочкам. Нужна корреляция по correlationId/eventId, протаскиваемая через все топики и сервисы, и дашборды, показывающие поток, а не узлы.

«Чёрный ящик» потока. В broker-топологии никто не видит процесс целиком — ни человек, ни система: список подписок на топик — это не карта потока. Ответ на вопрос «что произойдёт, если опубликовать это событие» требует ревизии всех потребителей, а забытый подписчик обнаруживается в проде.

Незримый рост инфраструктуры. Брокер, реестры схем, ретрансляторы outbox, DLQ-политики, мониторинг лагов — каждый элемент по отдельности оправдан, вместе они образуют собственную платформу с отдельной командой. Для системы из трёх сервисов эта цена непропорциональна выгоде (см. монолит как стартовую точку).

Связанные статьи

  • Микросервисы — события как основной способ интеграции автономных сервисов; «умные конечные точки, глухие каналы»; саги и управление данными.
  • SOA — исторический сосед: событийный обмен в корпоративной интеграции вырос из тех же EAI-паттернов, что и шина ESB.
  • CAP-теорема — почему событийные системы выбирают доступность в ущерб строгой согласованности; модель PACELC и уровни согласованности.
  • BASE — итоговая согласованность как принцип проектирования, делающий расхождение данных управляемым.
  • Монолитная архитектура — точка отсчёта: когда внутри одного процесса события избыточны, а вызовы методов дешевле и надёжнее.
  • DDD — Domain-Driven Design — доменные события и ограниченные контексты: как выбирать, какие факты предметной области достойны публикации наружу.
  • System Design — событийный обмен в контексте масштабируемости, надёжности и производительности распределённых систем.