По какой схеме проектируется HTTP взаимодействие между сервисами?
Что проверяют
Отличаете ли вы вызов чужого сервиса от вызова функции. Внутри процесса вызов либо выполнился, либо бросил исключение. По сети он может зависнуть, выполниться дважды или вернуть ответ, который уже никому не нужен. Ждут схему, а не перечисление методов HTTP.
Как отвечать
Разложите ответ по слоям.
Контракт: описание в OpenAPI, схемы запроса и ответа на pydantic, версионирование в пути, правило совместимости (поля добавлять можно, удалять и менять смысл нельзя).
Семантика: GET безопасен и кэшируем, PUT и DELETE идемпотентны, POST нет. Отсюда практическое правило: автоматически повторять можно только идемпотентные вызовы, для остальных нужен ключ идемпотентности в заголовке, по которому приёмник узнаёт повтор.
Отказы: таймаут обязателен на каждый вызов, отдельно на подключение и на чтение; повторы с растущей паузой и случайным разбросом; ограничение числа попыток; предохранитель, который перестаёт долбить упавший сервис; заранее решённое поведение при отказе.
На Python это httpx или aiohttp с общим клиентом и пулом соединений, повторы через tenacity, приёмная сторона на FastAPI. Клиент создаётся один на приложение: новый клиент на каждый запрос убивает пул и упирается в исчерпание сокетов.
Отдельно назовите сквозной идентификатор запроса в заголовке и трассировку, иначе разбирать инцидент будет нечем.
Где спотыкаются
Ходят в чужой сервис без таймаута. По умолчанию во многих клиентах ожидание не ограничено, один зависший вызов держит воркер, и очередь наверху складывается целиком.
Второе: повторяют всё подряд, включая POST. Заказ или платёж создаётся дважды, причём именно в тот момент, когда ответ потерялся, а запрос до сервиса дошёл.
Третье: возвращают 200 с полем ошибки внутри тела. Такой ответ ломает всё, что построено на кодах: балансировщик считает вызов успешным, повторы на клиенте не срабатывают, а метрики рисуют безоблачную картину при сломанном сервисе.