Практическая ситуация: HTTP-сервис ждёт ответ внешней зависимости дольше тайм-аута клиентского запроса, а затем продолжает эту операцию в фоне. Какой контракт времени должен проверить интеграционный тест?
Интеграционный тест должен проверять сквозной дедлайн операции: оставшееся время запроса передаётся внешней зависимости, а после истечения дедлайна обработка прекращается либо явно переводится в заранее определённое асинхронное состояние. Нельзя допускать, чтобы клиент уже получил тайм-аут, а сервис продолжал синхронную операцию без ограничений.
В распределённых системах один пользовательский запрос часто проходит через несколько сервисов. Если каждый из них независимо ждёт зависимость свой полный тайм-аут, суммарная задержка растёт, а накопившиеся запросы могут исчерпать рабочие потоки и соединения.
Идея дедлайна появилась как способ ограничить всё дерево вызовов общим бюджетом времени, а не задавать несвязанные тайм-ауты на каждом уровне. Это снижает риск каскадных задержек и делает поведение системы предсказуемее при деградации внешних сервисов.
Предположим, клиент допускает ожидание ответа не более двух секунд. Сервис обращается к платёжной системе с локальным тайм-аутом в десять секунд и после клиентского разрыва продолжает операцию в фоне.
Такое поведение может привести к позднему изменению состояния, повторному списанию, исчерпанию ресурсов или появлению результата, о котором клиент уже не узнает. Сам факт ответа HTTP с ошибкой не доказывает, что внутренняя операция остановлена.
Интеграционный тест должен управляемо задержать внешнюю зависимость, проверить передачу ограниченного времени и убедиться, что после истечения дедлайна сервис не продолжает синхронный вызов бесконечно. Отдельно важно определить, допускается ли переход операции в фоновый режим: это уже другой, явно описанный контракт.
Контракт времени должен описывать общий дедлайн или бюджет, правила его передачи зависимостям и результат при его истечении. Внешний вызов должен получить не новый полный тайм-аут, а время, оставшееся до завершения исходной операции, с учётом сетевого запаса.
Тест может использовать контролируемый стенд внешней зависимости, который не отвечает до заданного момента. Затем проверяется, что сервис возвращает предусмотренный результат, например ошибку временной недоступности, и прекращает ожидание зависимости в пределах общего бюджета.
Проверять только длительность HTTP-ответа недостаточно: сервис способен быстро вернуть ошибку и продолжить работу в фоне. Поэтому тесту нужны наблюдаемые признаки завершения или отмены внешнего вызова: журнал, метрика, состояние стенда или отсутствие последующего побочного эффекта.
Дедлайн не заменяет идемпотентность. Если отмена произошла после того, как внешняя система приняла команду, но до получения ответа, повтор может создать дубликат. Для таких операций дополнительно нужен идентификатор идемпотентности или другой контракт безопасного повтора.
Есть и ограничение: отмена локального ожидания не гарантирует физическую отмену уже принятой операции во внешней системе. Поэтому контракт должен различать прекращение ожидания ответа, прекращение обработки в сервисе и фактическую отмену бизнес-операции.
Сервис оформления доставки обращается к внешнему тарифному API. При его задержке команда клиента иногда получает ошибку через три секунды, но тарифный запрос продолжает занимать поток ещё минуту, из-за чего при нагрузке растёт очередь.
Рассматривались три варианта. Увеличить локальный тайм-аут было проще всего, но это усиливало накопление запросов. Жёстко обрывать соединение с внешним API уменьшало ожидание, однако не решало вопрос уже принятой команды. Перевести расчёт в асинхронный режим было надёжнее, но требовало отдельного контракта статуса операции.
Выбрали общий дедлайн для синхронного пути и явный асинхронный сценарий для операций, которые могли быть приняты внешней системой до истечения времени. Интеграционный тест задерживал тарифный API, проверял передачу остатка времени и отсутствие бесконтрольного фонового вызова. В результате сервис перестал занимать ресурсы после завершения клиентского запроса, а неопределённые операции стали видны через статус обработки.
Нет. Быстрый ответ не доказывает прекращение внутренней работы. Сервис может вернуть ошибку клиенту, но оставить внешний вызов активным, продолжить запись в базу или выполнить побочный эффект позже.
Проверка должна включать наблюдение за зависимостью и состоянием сервиса после ответа. Нужно установить, был ли внешний вызов отменён, завершён контролируемо или переведён в явно предусмотренный асинхронный процесс.
Одинаковый тайм-аут задаёт независимый максимум для каждого вызова. Если запрос проходит через несколько уровней, каждый уровень может ожидать полный интервал, хотя общий пользовательский бюджет уже почти исчерпан.
Дедлайн представляет момент, к которому исходная операция должна завершиться. Каждый следующий вызов получает только оставшееся время, поэтому внутренние компоненты учитывают уже потраченную задержку и не создают новый полный бюджет.
Тест должен проверить не только ответ сервиса, но и согласованность состояния после неопределённого результата. В частности, важно убедиться, что повтор операции не создаёт дубликат и что результат можно безопасно узнать или сопоставить с исходной командой.
Для этого применяют идентификатор идемпотентности, проверку статуса операции или другой механизм согласования. Само закрытие соединения и возврат ошибки не позволяют сделать вывод, что внешняя команда не была выполнена.