Событийная архитектура без магии: карта понятий

Карта понятий событийной архитектуры: чем команда отличается от события, domain event от integration event, где брокер, где event store, где workflow engine — и как читать остальную серию

«Событийная архитектура» — это зонтичный термин, под которым прячется десяток разных вещей: брокеры сообщений, event sourcing, CQRS, saga, workflow-движки. Их часто валят в одну кучу и продают как единое решение «сделаем всё асинхронным — и станет хорошо». Не станет: это разные инструменты для разных задач, с разной ценой.

Эта статья — карта. Она не учит ни одному паттерну в деталях, а расставляет понятия по местам: что такое событие, чем оно отличается от команды и сообщения, где в системе живёт брокер, где event store, а где workflow engine. Остальные выпуски серии разбирают каждый кусок отдельно — здесь даётся рамка, чтобы они складывались в картину, а не в список модных слов.

В статье

Команда, событие, сообщение: три разных понятия

Три слова, которые постоянно путают:

  • Команда (command) — намерение что-то изменить. Адресная, может быть отклонена. Глагол в повелительном наклонении: AllocateReserve, ConsumeOxygen. У команды есть конкретный получатель, который решает, выполнять её или нет.
  • Событие (event) — факт, который уже произошёл. Неизменяемо, отклонить нельзя. Прошедшее время: ReserveAllocated, OxygenConsumed. У события нет адресата — оно просто сообщает миру, что случилось; подписчиков может быть ноль, один или много.
  • Сообщение (message) — транспортная обёртка. И команда, и событие путешествуют по системе как сообщения через брокер или HTTP. «Сообщение» — про доставку, «команда/событие» — про смысл.
Команда Событие Сообщение
Смысл намерение (сделай) факт (уже произошло) транспортная обёртка
Время повелительное (ConsumeOxygen) прошедшее (OxygenConsumed)
Адресат один, конкретный нет (0…N подписчиков)
Можно отклонить да нет (факт свершился)
Про что связывает отправителя с получателем развязывает про доставку

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

Domain event vs integration event

Не все события одинаковы. Полезно различать два уровня:

  • Domain event — внутреннее событие одного сервиса/контекста. Описывает факт в терминах домена, живёт внутри границы сервиса, потребляется его же кодом (например, проекциями в event sourcing). Формат может меняться относительно свободно — у него один владелец.
  • Integration event — событие, которое сервис публикует наружу, для других сервисов. Это публичный контракт. Его формат менять опасно: на той стороне есть потребители, которых вы не контролируете.

Смешивать их — частая ошибка. Если выставить наружу сырые domain event, любое внутреннее изменение модели ломает потребителей. Поэтому на границе обычно стоит трансляция: внутренние domain event → аккуратно версионированные integration event. Подробнее о том, как проектировать и эволюционировать публичные контракты, — в статье про контракты событий.

Где что живёт: брокер, event store, workflow engine

Три инфраструктурных компонента, которые легко перепутать, потому что все «про события»:

  • Брокер сообщений (RabbitMQ, Kafka, NATS) — транспорт. Доставляет сообщения от издателя к потребителям. Его задача — передать и (иногда) подержать сообщение, пока его не заберут. В этой роли брокер не хранит состояние вашего домена (важная оговорка про Kafka — ниже).
  • Event store (EventStoreDB, PostgreSQL как append-only) — хранилище событий как источника правды. Здесь события — это данные, из которых вычисляется состояние. Это сердце event sourcing, а не транспорт.
  • Workflow engine (Temporal, Cadence) — координатор длительных процессов. Сохраняет состояние выполнения многошаговых workflow, восстанавливает его после перезапусков и выполняет шаги по заданным правилам retry и timeout. Это не обещание «довести до конца» в бизнес-смысле: workflow может завершиться отменой, таймаутом или окончательной ошибкой. Гарантия — в другом: процесс не потеряется и продолжится (или завершится) детерминированно по правилам, а не «повиснет» после сбоя.
Компонент Роль События — это… Примеры
Брокер транспорт сообщения в пути Kafka, RabbitMQ, NATS
Event store источник правды данные (состояние вычисляется) EventStoreDB, Postgres append-only
Workflow engine координатор процессов шаги durable-выполнения Temporal, Cadence

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

Здесь важна оговорка про Kafka. В отличие от классического брокера, Kafka — это durable-лог, и при осознанном проектировании (долгий или бесконечный retention, log compaction, продуманная модель replay и версионирования событий) она вполне может выступать и как event store, и как основа event sourcing — это описано и в документации самой Kafka. Категорично исключать её из этой роли неверно; ключ в том, чтобы хранение и воспроизведение событий были спроектированы намеренно, а не получились случайно как побочный эффект транспорта. Зеркальная ошибка — с другой стороны: самодельный state machine на таблице в БД вслепую дотягивают до возможностей полноценного workflow engine.

Event-driven ≠ event sourcing

Самая частая терминологическая ловушка серии, поэтому вынесем её отдельно:

  • Event-driven architecture — про взаимодействие. Компоненты общаются через события вместо прямых вызовов. Это про связи между сервисами.
  • Event sourcing — про хранение состояния. Состояние сервиса хранится как последовательность событий вместо текущего снимка. Это про то, как один сервис хранит свои данные.

Можно иметь event-driven систему без единого event-sourced сервиса (сервисы с обычными таблицами общаются через брокер). Можно иметь event-sourced сервис, который ни с кем не общается событиями. Это ортогональные вещи, которые удачно сочетаются, но не обязаны идти вместе.

Eventual consistency как данность, а не баг

Как только данные размазаны по нескольким хранилищам и синхронизируются через события, появляется задержка: read-модель отстаёт от write-модели, один сервис уже знает о факте, другой ещё нет. Это eventual consistency — не дефект реализации, а фундаментальное свойство распределённых систем.

С ней не «борются», её проектируют: где допустимо отставание в секунды, а где нужна строгая согласованность; где поможет «read your own writes»; как мониторить лаг. Эта тема — сквозная для всей серии и подробно разобрана в статье про согласованность данных.

Карта серии: что читать дальше

Серия движется тематическими блоками: от основ (что такое событие и как оно доставляется) — через надёжную доставку и паттерны состояния — к координации длинных процессов — синтезу — и практическим углублениям.

Основы:

  1. Эта статья — карта понятий.
  2. Что кладём в событие — notification vs event-carried state transfer.
  3. Гарантии доставки и идемпотентность — at-least-once + идемпотентность = effectively-once.

Надёжная доставка:

  1. Transactional Outbox/Inbox — как надёжно публиковать события без распределённой транзакции.
  2. Контракты событий — naming, версионирование, эволюция схем.

Паттерны состояния:

  1. Event Sourcing — когда история важнее текущего состояния.
  2. CQRS — разделение моделей чтения и записи.

Координация:

  1. Saga — распределённые транзакции без двухфазного коммита.
  2. Хореография vs оркестрация — где живёт знание о процессе.
  3. Temporal — durable workflows вместо самодельных очередей.

Синтез:

  1. Матрица решений — когда какой паттерн выбрать.

Практика (глубже, advanced):

  1. Event Sourcing на практике — event store, проекции, снапшоты, replay.
  2. CQRS на практике — проекции, rebuild, консистентность.
  3. Saga на практике — компенсации, изоляция, идемпотентность.

Читать подряд необязательно: если вас интересует конкретная задача, начните с матрицы выбора и идите к нужному выпуску. Но если событийная архитектура для вас в новинку — порядок выше выстроен так, чтобы каждый следующий паттерн опирался на предыдущие. Практический блок (12–14) — продолжения концептуальных статей для тех, кто уже внедряет.

Как читать серию по ролям

Серия не чисто кодовая — у разных ролей разный интерес:

Документация и первоисточники

Обсуждение в Telegram

Присоединиться →

Комментарии