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

Карта понятий событийной архитектуры: чем команда отличается от события, 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

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

Комментарии