АрхитектураМикросервисы и интеграцииАрхитектор распределённых систем

Потребитель события вынужден синхронно запрашивать источник, чтобы узнать изменившиеся данные. Какой недост...

Потребитель события вынужден синхронно запрашивать источник, чтобы узнать изменившиеся данные. Какой недостаток интеграционного контракта это выявляет?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

В распределённых системах сначала часто используют события-уведомления: они сообщают, что что-то изменилось, а потребитель сам запрашивает подробности. Такой подход уменьшает размер сообщений и позволяет не дублировать состояние, но со временем создаёт скрытую зависимость от доступности и производительности источника.

Для снижения этой связанности появился подход event-carried state transfer — передача в событии тех данных, которые нужны потребителю для самостоятельной обработки. Он особенно полезен для построения локальных представлений и работы при временной недоступности источника.

Постановка проблемы

Предположим, сервис каталога публикует событие об изменении цены, содержащее только идентификатор товара. Сервис рекомендаций получает событие, но для пересчёта должен вызвать каталог и получить новую цену.

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

Подробное решение

Сначала определяют, какие данные нужны потребителю для его бизнес-операции. Если сервис рекомендаций должен пересчитать результат по новой цене, событие должно содержать как минимум идентификатор товара, новую цену, валюту и версию или время изменения. Потребитель сможет обработать событие без немедленного запроса к каталогу.

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

У события есть важная семантическая особенность: оно описывает изменение в определённый момент, а не обязательно текущее состояние источника. Если потребителю нужно именно актуальное состояние на момент чтения, синхронный запрос или отдельная проекция остаются оправданными.

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

Есть три распространённых варианта:

  • Тонкое уведомление с идентификатором — мало данных и простая схема, но сохраняется запрос к источнику.
  • Событие с необходимым снимком — меньше синхронной связанности, но появляется дублирование данных и ответственность за эволюцию схемы.
  • Событие как сигнал для локальной проекции — хорошая автономность чтения, но требуется поддерживать проекцию и обрабатывать временную рассогласованность.

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

Ситуация из практики

Сервис заказов публиковал событие об изменении статуса доставки, содержащее только идентификатор заказа. Сервис уведомлений после каждого события обращался к сервису заказов за адресом, способом доставки и новым статусом. При массовом обновлении статусов это создавало пик синхронных запросов и задерживало отправку уведомлений.

Рассматривались два варианта. Первый — оставить тонкое событие и увеличить ёмкость API сервиса заказов: это сохраняло единственный источник данных, но не устраняло синхронную связанность. Второй — передавать в событии все данные заказа: это уменьшало число запросов, но раскрывало сервису уведомлений лишние сведения и делало контракт чрезмерно зависимым от модели заказа.

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

В результате основной поток перестал зависеть от доступности сервиса заказов. Контракт стал предметным для уведомлений, а не копией внутренней структуры заказа; цена этого решения — необходимость версионировать и защищать передаваемые персональные данные.

Что кандидаты часто упускают

1. Когда тонкое событие всё же является правильным выбором?

Тонкое событие оправдано, если потребителю нужен только сам факт изменения, например для запуска фоновой проверки или инвалидирования локального кэша. Оно также подходит, когда данные чувствительны, быстро устаревают или потребитель обязан получать их только через контролируемый API.

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

2. Почему нельзя передавать в событии полный объект источника на всякий случай?

Полный объект увеличивает размер сообщений и часто раскрывает поля, которые потребителю не принадлежат. Любое изменение внутренней модели может начать выглядеть как изменение интеграционного контракта, что повышает стоимость независимого развития сервисов.

Кроме того, разные потребители могут требовать разные представления одной сущности. Лучше формировать контракт вокруг факта и сценария потребителя, чем публиковать внутреннюю структуру владельца данных.

3. Что делать, если потребителю нужны одновременно историческое изменение и самое новое состояние?

Эти требования нужно разделить. Событие должно содержать исторический факт, относящийся к моменту изменения, а актуальное состояние следует получать через API владельца или поддерживаемую локальную проекцию.

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