ТестированиеТестирование API и интеграцийИнженер по автоматизации тестирования распределённых систем

В распределённой операции запрос проходит через несколько сервисов, но итоговый лог нельзя связать с исходн...

В распределённой операции запрос проходит через несколько сервисов, но итоговый лог нельзя связать с исходным запросом. Какой контракт передачи идентификатора корреляции должен проверять интеграционный тест?

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

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

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

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

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

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

Корреляция появилась как практический способ связать эти записи в одну причинно-следственную цепочку. Позднее для распределённой трассировки стали использовать стандартизированные контексты трассировки, например W3C Trace Context, но прикладной идентификатор корреляции и идентификатор трассировки не всегда являются одним и тем же значением.

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

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

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

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

Контракт должен описывать следующие свойства:

  • входной сервис принимает идентификатор корреляции в согласованном месте, например в заголовке HTTP или метаданных сообщения;
  • при вызове следующего сервиса он передаёт то же значение без изменения;
  • при публикации события идентификатор переносится в его метаданные, а не только в бизнес-тело;
  • обработчик события использует это значение в последующих вызовах и логах;
  • при отсутствии идентификатора применяется заранее определённое правило генерации нового значения;
  • значение проходит проверку формата и размера, поскольку входные заголовки нельзя безусловно считать доверенными.

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

Важно не смешивать идентификатор корреляции, идентификатор трассировки и идентификатор конкретного спана. Один trace ID может объединять распределённую трассу, тогда как span ID меняется на каждом участке; если контракт требует сквозную бизнес-корреляцию, тест должен проверять именно нужное поле и его семантику.

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

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

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

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

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

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

  1. Достаточно ли проверить, что идентификатор присутствует в логах каждого сервиса?

Нет. Логи могут самостоятельно создавать или подменять значение, поэтому наличие одинакового на вид поля ещё не доказывает передачу исходного идентификатора. Тест должен задать известное значение на входе и сравнить его с каждым значением на последующих границах.

  1. Должен ли идентификатор корреляции сохраняться при повторной доставке события?

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

  1. Что делать, если входящий клиент передал некорректный или слишком длинный идентификатор?

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