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

Карта понятий событийной архитектуры: чем команда отличается от события, 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. «Сообщение» — про доставку, «команда/событие» — про смысл.

Ключевая разница команды и события — в направлении связности. Команда связывает отправителя с получателем («сделай это»). Событие развязывает: издатель не знает и не хочет знать, кто его слушает. Именно из этого свойства растёт слабая связанность 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) — транспорт. Доставляет сообщения от издателя к потребителям. Его задача — передать и (иногда) подержать сообщение, пока его не заберут. Брокер не хранит состояние вашего домена.
  • Event store (EventStoreDB, PostgreSQL как append-only) — хранилище событий как источника правды. Здесь события — это данные, из которых вычисляется состояние. Это сердце event sourcing, а не транспорт.
  • Workflow engine (Temporal, Cadence) — координатор длительных процессов. Хранит состояние выполнения многошаговых workflow и гарантирует, что они доедут до конца, переживая перезапуски.

Одна и та же система может использовать все три одновременно — и это нормально. Путаница начинается, когда брокер пытаются использовать как event store («у нас же 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. Event Sourcing — когда история важнее текущего состояния.
  3. CQRS — разделение моделей чтения и записи.
  4. Transactional Outbox/Inbox — как надёжно публиковать события без распределённой транзакции.
  5. Контракты событий — naming, версионирование, эволюция схем.
  6. Saga — распределённые транзакции без двухфазного коммита.
  7. Temporal — durable workflows вместо самодельных очередей.
  8. Матрица решений — когда какой паттерн выбрать.

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

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

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

  • Аналитику / продакту — эта карта, Event Sourcing (история как требование) и decision matrix. Достаточно, чтобы понимать язык и цену решений, не погружаясь в реализацию.
  • Backend-разработчику — основной маршрут: Outbox/Inbox, контракты, Saga, Temporal. Здесь живёт инженерная механика.
  • Архитектору — вся серия, с акцентом на границы и комбинации паттернов в матрице.
  • Тимлиду — карта, decision matrix и разделы «когда не надо» в каждой статье: они про цену внедрения и готовность команды, а не про код.

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

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

Комментарии