Внешний API отвечает нестабильно: timeout, retry и нормальная обработка ошибок
Внешний API может быть быстрым утром, медленным днём и недоступным вечером. Это нормально: чужой сервис не находится под вашим контролем. Ошибка начинается тогда, когда ваш сайт ждёт ответ бесконечно, падает на временной ошибке или повторяет запросы без ограничений.
Интеграция должна быть готова к нестабильности. Для этого нужны таймауты, понятная классификация ошибок, retry, очередь и логи.
Всегда ставить timeout
Запрос без timeout может повесить обработчик, cron или worker. Таймауты должны быть двумя: на соединение и на чтение ответа.
$client = new \GuzzleHttp\Client([
'connect_timeout' => 3,
'timeout' => 15,
]);
Значения зависят от операции. Для проверки статуса хватит пары секунд. Для тяжёлого отчёта можно дать больше, но не бесконечно.
Не все ошибки одинаковые
Ошибки API нужно делить на временные и постоянные.
- timeout — обычно можно повторить;
- 429 — можно повторить позже;
- 500, 502, 503, 504 — обычно временная проблема сервиса;
- 400 — чаще ошибка данных;
- 401 — проблема авторизации;
- 403 — нет прав;
- 404 — неверный метод или сущность не найдена.
Если повторять 400 с теми же данными, ничего не улучшится. Если не повторять timeout, можно потерять временно недоступную операцию.
Retry с ограничением
Повторные попытки должны иметь лимит. Иначе одна плохая заявка будет крутиться бесконечно.
$maxAttempts = 3;
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
return $api->send($payload);
} catch (TemporaryApiException $e) {
if ($attempt === $maxAttempts) {
throw $e;
}
sleep($attempt * 2);
}
}
Пауза между попытками снижает шанс повторить запрос в тот же момент перегрузки.
Очередь вместо ожидания пользователя
Если операция не обязана завершиться прямо в момент отправки формы, её лучше вынести в очередь. Пользователь получает быстрый ответ, а интеграция выполняется фоном.
Yii::$app->queue->push(new SendLeadToCrmJob([
'leadId' => $lead->id,
]));
Но очередь не отменяет логов и статусов. Нужно видеть, что задача ждёт, выполняется, завершилась или упала.
Статус операции в базе
Для важной интеграции полезно хранить статус отправки.
crm_status: new
crm_status: processing
crm_status: success
crm_status: retry
crm_status: failed
Тогда можно открыть заказ или заявку и понять, что произошло с передачей во внешний сервис.
Логи без секретов
В логах нужны метод, HTTP-код, длительность, ID локальной сущности и короткая ошибка.
[
'service' => 'crm',
'method' => 'lead.add',
'lead_id' => 123,
'http_code' => 503,
'duration' => 15.02,
'attempt' => 2,
]
Не нужно писать токен, полный заголовок Authorization и лишние персональные данные.
Fallback для критичных действий
Если CRM временно недоступна, заявка не должна пропасть. Минимально она должна сохраниться локально со статусом failed или retry. Потом её можно отправить повторно.
Плохой сценарий — форма показывает ошибку, но заявка нигде не сохранена. Пользователь ушёл, менеджер ничего не получил, диагностики нет.
Чек-лист
- Поставить connect timeout и read timeout.
- Разделить временные и постоянные ошибки.
- Повторять только те ошибки, где retry имеет смысл.
- Ограничить количество попыток.
- Вынести долгие операции в очередь.
- Хранить статус отправки в базе.
- Логировать метод, HTTP-код, длительность и attempt.
- Сохранять локальные данные даже при падении внешнего API.
Надёжная интеграция не требует, чтобы внешний API всегда работал идеально. Она спокойно переживает временные сбои, не подвешивает сайт и оставляет достаточно следов, чтобы понять, что случилось.
Комментарии (0)
Пока нет комментариев. Будьте первым!