3.12

Смотреть на английском

3.12 Событийно-ориентированная архитектура и обмен сообщениями

Обзор и мотивация

Событийно-ориентированная архитектура (EDA) это стиль, в котором компоненты общаются, порождая события и реагируя на них, а не вызывая друг друга напрямую. Событие это факт: нечто, что уже произошло, например «OrderPlaced» («Заказ размещён») или «PaymentCaptured» («Платёж проведён»). Производитель объявляет факт и идёт дальше, а любое число потребителей реагирует в своём темпе, не заставляя производителя знать, кто слушает. Это иная поза, чем вызовы «запрос-ответ» из главы 2.3, где вызывающий просит конкретный сервис что-то сделать и ждёт ответа.

Для большой организации привлекательность в разделении сцепления в масштабе. Когда у вас десятки команд и сотни сервисов, соединение всего прямыми вызовами «точка-точка» порождает хрупкую паутину, где изменение одной команды ломает другую и никто не может проследить почему. События позволяют командам интегрироваться через общий поток фактов, а не через внутренности друг друга, а новый потребитель присоединяется подпиской, без изменения производителем хоть строки кода. Это свойство, больше чем сырая пропускная способность, и есть причина, по которой событийно-ориентированные подходы продолжают распространяться по корпорациям, заменяя запутанные интеграции, и по государствам, объединяющим ведомства, каждое из которых владеет своими системами.

Государственный сектор получает вторую выгоду, которую легко недооценить: долговечная упорядоченная запись произошедшего это актив аудита и прозрачности. Когда гражданин спрашивает, почему решение о пособии вышло так, а не иначе, неизменяемый журнал событий, приведших к нему, отвечает прямо. Но событийно-ориентированный проект не бесплатен и не всегда верен: асинхронные потоки труднее прослеживать, труднее осмыслять и легко применять сверх меры. Эта глава имеет мнение о том, когда разделение сцепления и масштаб окупают добавленную сложность, а когда простой синхронный вызов послужил бы лучше. Она опирается на реалии распределённых систем из главы 3.3, так что прочитайте её сначала, если ещё не читали.

Ключевые принципы

  • События это факты, а не инструкции. Событие говорит, что произошло; команда просит, чтобы что-то произошло. Держите их раздельно и называйте события в прошедшем времени.
  • Разделение сцепления это суть. Производители не должны знать или заботиться о том, кто потребляет их события. Если заботятся, у вас сцепление в костюме обмена сообщениями.
  • Проектируйте под доставку «как минимум один раз». Доставка «ровно один раз» миф. Делайте каждого потребителя идемпотентным, чтобы дубликаты были безвредны.
  • Порядок это гарантия, за которую вы платите. Вы получаете порядок внутри раздела, а не по всему топику. Выбирайте ключи разделов намеренно.
  • Схема это контракт. Форма события это публичный интерфейс; развивайте её с заботой, с какой развивали бы опубликованный API.
  • Асинхронный не значит ненаблюдаемый. Если вы не можете проследить сообщение от конца до конца, вы не можете эксплуатировать систему.
  • Сложность нужно заслужить. Источник событий (event sourcing), CQRS и саги мощны и дороги; тянитесь к ним, когда этого требует задача, а не по умолчанию.

Рекомендации

Различайте события, команды и сообщения, прежде чем что-либо строить

Эти три слова используют взаимозаменяемо, и путаница вызывает реальные проектные ошибки. Команда это просьба что-то сделать («CapturePayment»), адресованная одному обработчику, и её можно отклонить. Событие это уведомление о том, что нечто уже произошло («PaymentCaptured»), рассылаемое всем заинтересованным, и его нельзя отклонить, потому что факт уже истинен. Сообщение это нейтральный конверт, несущий то или другое по проводу. Различие формирует сцепление: команды сцепляют отправителя с конкретным получателем и исходом, тогда как события отказываются от контроля над тем, что происходит дальше. Называйте события в прошедшем времени, а когда ловите себя на публикации «события», которое на деле значит «пожалуйста, иди сделай вот эту конкретную вещь», вы написали замаскированную команду.

Выбирайте очереди, журналы и публикацию/подписку намеренно

Не весь обмен сообщениями одной формы, и выбор не той частая ранняя ошибка. Очередь сообщений доставляет каждое сообщение одному потребителю и обычно удаляет его после обработки, что подходит для распределения работы: многие исполнители берут задачи, каждая выполняется один раз. Долговечный журнал событий (поток) хранит события по порядку и позволяет многим независимым потребителям читать в своём темпе, проигрывая историю с любой точки, что подходит для рассылки событий и аудита. Публикация/подписка означает, что производители публикуют в топик, а несколько подписчиков получают каждый свою копию. Практическое правило: если сообщение это задача, которую должен выполнить один исполнитель, тянитесь к очереди; если это факт, который может интересовать многих сейчас или позже, тянитесь к долговечному журналу, дающему и проигрывание для восстановления и подключения новых потребителей. О том, как эти выборы взаимодействуют с вашей стратегией хранения данных, см. главу 3.4.

Предпочитайте хореографию для автономии, оркестровку для контроля

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

Тянитесь к источнику событий и CQRS, только когда они оправдывают себя

Источник событий (event sourcing) хранит состояние как только дополняемую последовательность событий, а не текущий снимок, который вы перезаписываете, и вы восстанавливаете текущее состояние, проигрывая их. Плюс идеальный след аудита, возможность реконструировать любое прошлое состояние и временные запросы; цена в том, что вы навсегда версионируете схемы событий, обрабатываете проигрывание и снимки и несёте ментальную модель, которой большинство разработчиков никогда не пользовалось. CQRS (Command Query Responsibility Segregation, разделение ответственности команд и запросов) отделяет модель записи от одной или нескольких моделей чтения, так что чтение и запись масштабируются и развиваются независимо; она естественно сочетается с источником событий, но не требует его. Оба блестят в областях с подлинными потребностями аудита, соответствия или сложных запросов, поэтому регулируемые финансы и государство находят их стоящими хлопот. Для простого сервиса «создать-прочитать-обновить-удалить» это случайная сложность, о которой вы пожалеете, поэтому применяйте их к срезу вашей области, которому они нужны, а не ко всей системе рефлекторно.

Управляйте распределёнными транзакциями сагами, а не двухфазной фиксацией

Обычно нельзя обернуть одну атомарную транзакцию вокруг нескольких сервисов и баз данных. Распределённая двухфазная фиксация держит блокировки по сети, режет доступность и плохо масштабируется, поэтому редко подходит событийно-ориентированной системе. Шаблон саги заменяет её: смоделируйте транзакцию как последовательность локальных транзакций, каждая из которых порождает событие, запускающее следующую, и дайте каждому шагу компенсирующее действие, отменяющее его, если сбоит более поздний шаг. Если «зарезервировать товар» удался, но «списать с карты» сбоит, компенсация освобождает товар. Саги могут быть хореографическими или оркестрируемыми, и оркестровка обычно побеждает для всего, что вы должны мониторить. Поскольку саги принимают согласованность в конечном счёте, система проходит через промежуточные состояния («зарезервировано, но не оплачено») перед сходимостью, поэтому проектируйте свой пользовательский опыт и след аудита так, чтобы честно показывать состояния «в процессе» и «скомпенсировано». Глава 3.3 охватывает ту же почву с угла распределённых систем.

Проектируйте под доставку «как минимум один раз» и делайте потребителей идемпотентными

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

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

Порядок не глобален и не бесплатен; он локален и оплачен. Поток разбивается на разделы (partitions), и вы получаете порядок внутри раздела, а не по всему топику. События направляются в раздел по ключу раздела, поэтому выбор этого ключа это то, как вы управляете тем, что остаётся упорядоченным: ключ по идентификатору клиента держит события одного клиента в порядке относительно друг друга, тогда как разные клиенты обрабатываются параллельно. Группы потребителей позволяют набору исполнителей делить разделы топика, каждый раздел обрабатывается одним исполнителем, и так вы масштабируете пропускную способность, сохраняя порядок по разделам; здесь масштабируемость встречается с корректностью, связываясь с главой 3.5. Выбирайте ключ раздела, отражающий ваше реальное требование порядка и равномерно распределяющий нагрузку, потому что ключ, направляющий большую часть трафика в один раздел, создаёт горячую точку, которую не снимет никакое число исполнителей.

Воспринимайте схемы как контракты с реестром и правилами эволюции

Структура события это опубликованный интерфейс, потребляемый командами, которых вы можете никогда не встретить, поэтому небрежное изменение ломает их на расстоянии. Помещайте схемы событий в реестр схем, общий каталог, хранящий каждую схему и принуждающий к правилам совместимости, когда производитель пытается изменить одну. Примите явную политику: обратно совместимые изменения (добавление необязательного поля) разрешены; ломающие изменения (удаление поля, смена типа, переименование) требуют новой версии схемы и плана миграции. Это позволяет производителям развиваться без синхронизированного развёртывания по каждому потребителю, а в этом вся причина, по которой вы выбрали события. Та же дисциплина версионирования интерфейсов из главы 2.3 применима, потому что схема события это API под другим именем.

Гарантируйте доставку транзакционным «исходящим ящиком» и обрабатывайте отказы явно

Классическая ошибка: ваш сервис пишет в свою базу данных, а затем публикует событие и падает между ними, так что база данных изменилась, а событие так и не вышло. Транзакционный исходящий ящик (outbox) исправляет это записью события в таблицу исходящего ящика в той же транзакции базы данных, что и изменение состояния, так что они фиксируются или откатываются вместе; отдельный ретранслятор затем читает исходящий ящик и публикует в брокер, часто используя захват изменений данных для чтения журнала базы данных. Для отказов потребления очередь недоставленных сообщений хранит сообщения, раз за разом дающие сбой, чтобы отравленное сообщение (то, что никогда не удастся, возможно, потому что оно искажено) не блокировало очередь за ним навсегда. Добавьте противодавление (backpressure), чтобы быстрый производитель не мог переполнить медленного потребителя: ограничивайте очереди и замедляйте или сбрасывайте нагрузку, когда они заполняются, вместо исчерпания памяти. Эти четыре механизма отделяют демонстрацию от системы, которую можно эксплуатировать в три часа ночи.

Делайте асинхронные потоки наблюдаемыми от конца до конца

Труднейшая цена перехода на события в том, что одно бизнес-действие теперь рассыпается по производителям, брокерам и потребителям без стека вызовов, связывающего их. Передавайте идентификатор корреляции через каждое событие, чтобы проследить один логический поток через каждый переход, та же дисциплина, которую глава 3.3 предписывает для синхронных вызовов. Отслеживайте отставание потребителя (consumer lag) (насколько позади реального времени читает каждый потребитель) как первоклассную метрику, потому что растущее отставание ваше самое раннее предупреждение о беде, и мониторьте глубину очереди недоставленных сообщений, задержку обработки и доли повторной доставки. Без этого событие, тихо не потреблённое, становится невидимой ошибкой, проявляющейся днями позже как недостающие данные.

Компромиссы: плюсы и минусы

ПодходПлюсыМинусы / цена
Синхронный запрос/ответПросто осмыслять, немедленный результат, лёгкая трассировкаЖёсткое временное сцепление, каскадные отказы, ограниченный масштаб
Событийно-ориентированный (pub/sub поверх журнала)Разделение сцепления, независимое масштабирование, проигрывание, след аудитаСогласованность в конечном счёте, труднее трассировка, больше подвижных частей
Очередь сообщений (распределение работы)Выравнивание нагрузки, буферизация, дружественность противодавлениюОдин потребитель на сообщение, хуже подходит для рассылки
Источник событий + CQRSПолная история, временные запросы, независимое масштабирование чтения/записиВерсионирование схем навсегда, сложность проигрывания, крутая кривая обучения
Сага (вместо двухфазной фиксации)Масштабируемо, доступно, нет распределённых блокировокСогласованность в конечном счёте, логика компенсации, труднее осмыслять

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

Вопросы для обсуждения с командой

  1. Нужно ли нам для этого конкретного взаимодействия на самом деле событие или синхронный вызов был бы яснее и безопаснее? Пропуск этого вопроса то, как система обрастает случайной сложностью. Честная проверка: нужен ли производителю результат прямо сейчас (вызов) или он объявляет факт, на который другие могут реагировать в своё время (событие). Принесите конкретное взаимодействие, а не общее предпочтение, и спросите, какое разделение сцепления вы получаете и какую ясность трассировки отдаёте. Если вызывающий блокируется в ожидании обработки «события», вы построили медленный, трудно отлаживаемый синхронный вызов и заплатили сверху за привилегию. Вариантом по умолчанию для внутренних взаимодействий внутри команды, где ответ нужен сейчас, должен быть прямой вызов; оставляйте события для мест, где слабое сцепление оправдывает свою цену.

  2. Что происходит, когда потребитель получает одно и то же событие дважды, и мы действительно это тестировали? Доставка «как минимум один раз» гарантирует, что дубликаты будут, поэтому каждый потребитель должен быть идемпотентным, но идемпотентность легко заявить и легко реализовать неверно. Пройдите по настоящему потребителю и проследите, как именно вторая доставка распознаётся и нейтрализуется, ключом идемпотентности, журналом обработанных событий или естественно идемпотентной операцией. Принесите результаты настоящего теста, где вы повторно доставляете пакет и подтверждаете отсутствие двойных списаний, дубликатов записей или повторных уведомлений. Особое внимание уделите побочным эффектам, покидающим вашу базу данных, таким как письма, платежи и вызовы сторонних сервисов, потому что именно там неидемпотентные ошибки бьют по клиентам напрямую. Если ваша команда не может указать на тест, доказывающий защиту от дубликатов, считайте, что её нет.

  3. Когда поток событий ломается в эксплуатации, через сколько мы это заметим и можем ли проследить одно сообщение от конца до конца? Асинхронные отказы тихи, поэтому потребитель, молча переставший обрабатывать, может остаться незамеченным, пока недостающие данные не станут жалобой клиента или пробелом аудита. Спросите, каков ваш самый ранний сигнал и мониторите ли вы отставание потребителей и глубину очереди недоставленных сообщений как оповещающие метрики, а не панели, на которые никто не смотрит. Принесите настоящий инцидент или учение и засеките, сколько времени занимает прослеживание одного идентификатора корреляции через производителя, брокер и каждого потребителя. Если ответ «мы grep-аем несколько сервисов и гадаем», ваша наблюдаемость не готова к сложности, которую вы взяли на себя. В регулируемых секторах возможность реконструировать, как именно двигалось сообщение, нередко требование соответствия, а не любезность.

  4. Как мы будем развивать схему события, которую уже потребляют многие команды, не сломав ни одну? Форма события это публичный контракт, и когда от него зависят десятки потребителей, безобидное на вид изменение может сломать системы, о которых вы не слышали, на расстоянии, без компилятора, предупреждающего вас. Напряжение реально: производители хотят двигаться быстро и чистить свои события, а каждый потребитель хочет заморозить форму навсегда, поэтому заранее договоритесь, какие изменения безопасны (добавление необязательного поля), а какие требуют новой версии и окна миграции (удаление поля, смена типа, переименование). Принесите настоящий список потребителей по топику, принуждает ли реестр схем правила совместимости сегодня или формы меняются по неформальному соглашению и как долго две версии могут работать параллельно во время миграции. В большой корпорации или государственной договорённости об обмене данными между ведомствами молча сломанная схема может испортить записи в системах, которыми вы не владеете, и всплыть позже как провал аудита, поэтому воспринимайте принуждение к совместимости как управление, а не вежливость.

  5. Для нашей важнейшей транзакции через несколько сервисов, что на самом деле отменяет каждое компенсирующее действие и какие промежуточные состояния увидят пользователи и аудиторы? Саги меняют утешительную иллюзию одной атомарной транзакции на последовательность локальных шагов, каждый из которых может сбоить, так что система действительно проходит через состояния вроде «зарезервировано, но не оплачено» и «списано, но не отправлено» до сходимости, а притворяться иначе то, как вы поставляете сагу, утекающую деньги или оставляющую сиротские записи. Пройдите настоящий процесс от конца до конца, назовите компенсацию для каждого шага (что освобождает товар, что возвращает деньги на карту) и решите, лучше ли оркестрируемая сага, которую можно мониторить, эмерджентной хореографии, которую не описывает ни одно место. Принесите случаи отказа, которые вы реально тестировали, а не счастливый путь, и подтвердите, что пользовательский опыт и след аудита честно показывают состояния «в процессе» и «скомпенсировано», а не скрывают их. В финансовых системах, системах пособий или налогов регулятор спросит, как выглядела запись в каждый промежуточный момент и кто отвечал за компенсацию, поэтому состояния саги сами артефакт соответствия.

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

Взгляд по секторам

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

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

Крупная организация. Ваша проблема стоимость интеграции по многим командам, поэтому отдача управляемая общая платформа: долговечный журнал событий, реестр схем с принудительной политикой совместимости, ясное владение топиками, а также стандартные шаблоны идемпотентности и исходящего ящика, чтобы каждая команда не изобретала их заново. Именно здесь замена хрупкой корпоративной сервисной шины или паутины связей «точка-точка» складывается в реальную экономию и именно здесь оркестрируемые саги с видимым статусом позволяют операционному персоналу управлять многошаговыми процессами. Бюджетируйте вложения в наблюдаемость и управление схемами явно, потому что в этом масштабе молчаливый отказ потребителя становится недостающими данными по десяткам систем.

Государственный сектор. Долговечный упорядоченный журнал событий актив аудита и прозрачности: он отвечает на «почему это решение вышло именно так» неизменяемой последовательностью фактов, поэтому налегайте на источник событий там, где подотчётность этого требует. Межведомственный обмен данными нуждается в доставке «как минимум один раз» с дедупликацией по стабильному идентификатору события, чтобы повторно доставленная запись никогда не создавала дубликат дела, а закупки должны взвешивать суверенитет данных, раскрытие ограничений брокера и переносимость против привязки. Публикуйте, где уместно, как работает поток и как гражданин может оспорить автоматизированный исход, и держите журнал событий как защищаемую запись, которую захотят проверить надзорные органы.

Примеры

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

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

Государственный сектор. Национальная налоговая служба должна дать гражданам и аудиторам защищаемый ответ на «почему моё начисление вышло именно так». Она моделирует область начислений источником событий, так что каждое изменение это неизменяемое событие в упорядоченном журнале, а текущее начисление это проигрывание этих событий; когда гражданин оспаривает цифру, сотрудник реконструирует точное состояние на любую прошлую дату и показывает последовательность фактов, его породившую. Межведомственный обмен данными идёт по долговечным топикам с доставкой «как минимум один раз», а потребитель каждого ведомства дедуплицирует по идентификатору события, так что повторно доставленная запись никогда не создаёт дубликат дела. Журнал событий служит и следом аудита, которого требуют надзорные органы, превращая обязательство соответствия в побочный продукт проекта.

Бизнес-обоснование: мотивы, ROI и TCO

Отдача от событийно-ориентированной архитектуры определяется одним: стоимостью интеграции во времени. Интеграция «точка-точка» заставляет эту стоимость расти с числом соединений, которое растёт быстрее числа систем, так что интеграция становится налогом, съедающим вашу мощность поставки. События выравнивают это, потому что команды интегрируются через общий поток фактов, новый потребитель присоединяется подпиской, а производители развиваются за версионированной схемой, так что предельная стоимость следующей интеграции резко падает. Это и есть основная история ROI для руководства: не сырая производительность, а накапливающееся снижение стоимости изменений по многим командам.

Называйте совокупную стоимость владения честно, чтобы вам верили. Вы берёте на себя инфраструктуру брокера для эксплуатации, управление схемами для сопровождения и более крутую эксплуатационную кривую обучения, потому что асинхронные системы действительно труднее отлаживать, поэтому заранее закладывайте вложения в наблюдаемость. Взвесьте это против стоимости непринятия событий там, где они подходят: окостеневшего слоя интеграции, где каждое изменение многокомандный координационный проект, унаследованной ESB, ставшей узким местом, которого никто не смеет трогать, и неспособности добавлять возможности, не тревожа старые. Для корпорации, заменяющей хрупкую проводку «точка-точка», и государства, строящего долговечные следы аудита, отдача сильнее всего там, где потребности аудита и разделения сцепления реальны. Там, где этих потребностей нет, честный ответ в том, что более простой синхронный проект имеет меньшее TCO, и вам стоит об этом сказать.

Антипаттерны и ловушки

  • Распределённый монолит в маскировке. Сервисы, которые все должны разворачиваться вместе и зависят от внутренних событий друг друга. Вы добавили брокер, но сохранили сцепление, так что у вас недостатки обоих стилей.
  • События как команды. Публикация «событий», которые на деле значат «пожалуйста, сделай для меня вот эту конкретную вещь», что воссоздаёт жёсткое сцепление с дополнительной задержкой и худшей прослеживаемостью.
  • Допущение доставки «ровно один раз». Потребители, которые ломаются, дважды списывают или дублируют записи, когда брокер неизбежно повторно доставляет сообщение.
  • Источник событий для всего. Применение источника событий и CQRS к простым областям «создать-прочитать-обновить-удалить», которым никогда не нужна была история, покупка крутой сложности без выгоды.
  • Отсутствие управления схемами. Производители свободно меняют формы событий, ломая нижестоящих потребителей на расстоянии без проверок совместимости.
  • Игнорирование исходящего ящика. Запись в базу данных и публикация события как два отдельных шага, так что падение между ними молча теряет события или испускает фантомные.
  • Нет обработки недоставленных сообщений. Единственное отравленное сообщение блокирует раздел, или сбойные сообщения исчезают без очереди, чтобы поймать и изучить их.
  • Невидимые потоки. Асинхронная обработка без идентификаторов корреляции, оповещений об отставании потребителей и трассировки, так что отказы остаются молчаливыми, пока не станут недостающими данными.

Модель зрелости

  • Уровень 1, Начальный: Интеграция стихийные вызовы «точка-точка», или брокер существует, но используется как синхронный запрос/ответ. Дубликаты ломают потребителей, нет ни дисциплины схем, ни сквозной трассировки между переходами, а сбойные сообщения исчезают молча.
  • Уровень 2, Развитие: Брокер или журнал в реальном использовании для некоторых потоков, и у нескольких команд базовые практики: потребители становятся идемпотентными, а очереди недоставленных сообщений ловят часть отказов. Но подход непоследователен между командами, события и команды всё ещё перепутаны, схемы меняются по неформальному соглашению, а наблюдаемость скудна.
  • Уровень 3, Стандартизация: События, команды и сообщения намеренно различаются по организации. Потребители идемпотентны по стандарту, схемы живут в реестре с документированной и принудительной политикой совместимости, а транзакционный исходящий ящик гарантирует доставку. Саги с компенсациями обрабатывают транзакции через несколько сервисов, идентификаторы корреляции передаются через каждый переход, а эти шаблоны записаны и применяются последовательно, а не оставлены каждой команде.
  • Уровень 4, Управление: Событийная платформа измеряется и контролируется данными относительно базовых значений. Отставание потребителей, глубина очереди недоставленных сообщений, доли повторной доставки и задержка обработки отслеживаются как оповещающие метрики с согласованными порогами, а не панели, на которые никто не смотрит; совместимость схем проверяется автоматически, прежде чем производитель сможет выпустить изменение; проигрывание, обработка отказов и идемпотентность тестируются с установленной периодичностью, а не ожидаются. Должно ли данное взаимодействие быть событием или синхронным вызовом, решается по свидетельствам, а мощность, стоимость хранения и надёжность каждого брокера пересматриваются относительно целей.
  • Уровень 5, Оркестрация: Событийно-ориентированный и синхронный стили выбираются по каждому взаимодействию как вторая натура, а источник событий и CQRS применяются точно там, где потребности аудита и запросов это оправдывают, и нигде более. Асинхронные потоки так же наблюдаемы, как синхронные, платформа непрерывно улучшается по мере сдвига потребностей (вывод мёртвых топиков, эволюция управления схемами, перебалансировка разделов), а обмен сообщениями интегрирован с более широкой архитектурой и стратегией аудита, так что организация адаптирует свой событийный проект по мере изменения бизнеса и его обязательств.

Идеи для обсуждения

  1. Какие из ваших нынешних «событий» тайно команды и какое сцепление вы убрали бы, смоделировав их честно?
  2. Если бы завтра вы проиграли полный день событий через своих потребителей, что сломалось бы и что это говорит о вашей идемпотентности и безопасности проигрывания?
  3. Каким частям вашей области действительно нужен след аудита источника событий, а какие просто простое состояние, которое источник событий лишь усложнил бы?
  4. Как вы мигрировали бы с унаследованной корпоративной сервисной шины или паутины интеграций «точка-точка» без рискованного одномоментного переключения?
  5. Для вашего важнейшего процесса через несколько сервисов это настоящая оркестрируемая сага с компенсациями или эмерджентная хореография, которой не описывает ни одно место?

Главные выводы

  • Событийно-ориентированная архитектура покупает разделение сцепления, независимое масштабирование, проигрывание и следы аудита, а стоит она понятности и эксплуатационной сложности, поэтому выбирайте её по каждому взаимодействию, где выгода реальна.
  • Держите события (факты, которые произошли), команды (просьбы действовать) и сообщения (конверт) раздельно, потому что путаница вызывает реальные ошибки сцепления.
  • Проектируйте под доставку «как минимум один раз» и делайте каждого потребителя идемпотентным; доставка «ровно один раз» миф, а эффекты «ровно один раз» инженерное достижение.
  • Порядок действует по разделам, схемы это контракты, место которых в реестре, а исходящий ящик, очереди недоставленных сообщений и противодавление это сантехника, делающая обмен сообщениями готовым к эксплуатации.
  • Используйте саги с компенсациями вместо двухфазной фиксации и тянитесь к источнику событий и CQRS только там, где потребности аудита и запросов оправдывают их крутую цену.
  • Асинхронные потоки молчаливы, когда отказывают, поэтому идентификаторы корреляции, мониторинг отставания потребителей и сквозная трассировка разница между управляемой системой и невидимой.

Источники и дополнительное чтение

  • Martin Kleppmann, Designing Data-Intensive Applications
  • Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns
  • Chris Richardson, Microservices Patterns (sagas, transactional outbox, CQRS)
  • Sam Newman, Building Microservices
  • Ben Stopford, Designing Event-Driven Systems
  • Adam Bellemare, Building Event-Driven Microservices
  • Vaughn Vernon, Implementing Domain-Driven Design (event sourcing and CQRS)
  • Martin Fowler, “Event Sourcing” and “CQRS” (martinfowler.com articles)
  • Hector Garcia-Molina and Kenneth Salem, “Sagas” (1987)