В распределённой операции запрос проходит через несколько сервисов, но итоговый лог нельзя связать с исходным запросом. Какой контракт передачи идентификатора корреляции должен проверять интеграционный тест?
Интеграционный тест должен проверять, что один идентификатор корреляции сохраняется через всю цепочку синхронных вызовов и передаётся в метаданных асинхронных событий. Сервис не должен без причины заменять или терять этот идентификатор, а каждый участник цепочки должен записывать его в свои логи.
Если входящий запрос не содержит идентификатора, контракт должен явно определить, кто и на каком этапе создаёт новый. Само наличие заголовка недостаточно: важно проверить неизменность значения, передачу через границы сервисов и доступность идентификатора для диагностики.
В монолитном приложении одну операцию обычно легко найти по одному серверному логу или идентификатору HTTP-запроса. В распределённой системе запрос порождает несколько внутренних вызовов и событий, поэтому записи оказываются в разных сервисах, журналах и системах доставки сообщений.
Корреляция появилась как практический способ связать эти записи в одну причинно-следственную цепочку. Позднее для распределённой трассировки стали использовать стандартизированные контексты трассировки, например W3C Trace Context, но прикладной идентификатор корреляции и идентификатор трассировки не всегда являются одним и тем же значением.
Клиент отправляет запрос в сервис заказов. Сервис вызывает сервис оплаты, тот публикует событие, а обработчик события обращается к сервису уведомлений. Если каждый компонент создаёт собственный идентификатор или не передаёт его дальше, оператор видит несколько несвязанных операций вместо одного процесса.
Это усложняет поиск причин ошибок, увеличивает время расследования и может привести к неверному выводу о том, какой запрос породил побочный эффект. Ошибка особенно опасна при повторной обработке событий: без корректной корреляции трудно отличить повторную доставку от новой бизнес-операции.
Контракт должен описывать следующие свойства:
Интеграционный тест должен пройти через реальную границу нескольких компонентов, передав известный идентификатор на входе и проверив его наличие в исходящем HTTP-вызове, опубликованном событии и следующем вызове. Такой тест проверяет именно интеграционный контракт, а не только внутреннюю функцию добавления заголовка.
Важно не смешивать идентификатор корреляции, идентификатор трассировки и идентификатор конкретного спана. Один trace ID может объединять распределённую трассу, тогда как span ID меняется на каждом участке; если контракт требует сквозную бизнес-корреляцию, тест должен проверять именно нужное поле и его семантику.
Есть компромисс между строгой проверкой и совместимостью. Старые потребители могут не поддерживать новый заголовок, поэтому добавление корреляции обычно не должно ломать обработку запроса. Однако для компонентов, критичных для диагностики, отсутствие обязательной передачи лучше считать нарушением контракта и делать видимым в тестах и мониторинге.
После ошибки при оплате команда видела идентификатор запроса в сервисе заказов, но не могла найти соответствующую запись в сервисе уведомлений. Выяснилось, что HTTP-шлюз передавал идентификатор дальше, а обработчик события создавал новый и не сохранял исходное значение в метаданных сообщения.
Рассматривались три варианта. Можно было искать операции по времени и идентификатору заказа, но это ненадёжно при параллельных заказах и повторной доставке. Можно было помещать идентификатор в бизнес-тело события, но это смешивает технические метаданные с доменной моделью и требует изменений всех схем. Выбран вариант с обязательным техническим полем в метаданных события и интеграционным тестом сквозной передачи.
В тесте использовали заранее известный идентификатор, запускали реальный путь от HTTP-запроса до обработчика события и проверяли его в исходящем вызове уведомлений. Это выявило потерю значения до выхода в промышленную среду и позволило связать логи всей операции без изменения бизнес-данных.
Нет. Логи могут самостоятельно создавать или подменять значение, поэтому наличие одинакового на вид поля ещё не доказывает передачу исходного идентификатора. Тест должен задать известное значение на входе и сравнить его с каждым значением на последующих границах.
Да, если повторная доставка относится к той же исходной операции. При этом идентификатор корреляции не заменяет уникальный идентификатор сообщения или ключ идемпотентности: повтор должен иметь тот же контекст операции, но может быть отдельной попыткой доставки. Поэтому контракт и тесты должны различать корреляцию, идентичность сообщения и номер попытки.
Контракт должен определить валидацию: допустимый формат, размер и поведение при нарушении. Обычно сервис отклоняет некорректное значение либо безопасно создаёт новое, но не должен без проверки помещать произвольные данные в логи и исходящие запросы. Интеграционный тест должен подтвердить выбранное поведение, чтобы защита от злоупотреблений не разрушала диагностическую цепочку.