Интеграции

Надёжные интеграции: retry и fallback при работе с внешними API

Никита Литвяков
Никита Литвяков
Senior PHP / Laravel Backend Developer
Обложка статьи: Надёжные интеграции

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

Базовые проблемы

  • Сеть нестабильна — таймауты, обрывы соединений.
  • API провайдера падает или деплоится — 5xx ошибки.
  • Лимиты — 429 Too Many Requests.
  • Семантические ошибки — провайдер ответил «успех», но реально операция не прошла.

Хорошая интеграция строится не на «надеемся, что заработает», а на предположении, что внешний сервис ненадёжен.

Retry: повторные попытки

Простейший приём — повторить запрос, если он не удался. Но повторять нужно умно.

Что повторяем: сетевые ошибки, таймауты, 5xx, 429. Что НЕ повторяем: 4xx (кроме 408 и 429). Если API сказал «неверные данные» — повторение не поможет.

return retry(
    times: 3,
    callback: fn () => Http::timeout(5)->post($url, $payload),
    sleepMilliseconds: fn (int $attempt) => $attempt * 1000,
    when: fn (Throwable $e) => $e instanceof ConnectionException
        || ($e instanceof RequestException && $e->response->status() >= 500),
);

Важная деталь — экспоненциальный backoff. Если сервис упал, тысяча клиентов одновременно начнут долбить его при первой же попытке восстановления и положат снова. Backoff + jitter (случайное смещение) спасают.

Fallback: запасной путь

Если у вас несколько провайдеров одного сервиса (SMS, платежи, email) — на ошибке основного переключайтесь на резервный.

foreach ($this->providers as $provider) {
    try {
        return $provider->send($message);
    } catch (ProviderUnavailableException $e) {
        Log::warning('Provider failed, fallback', ['provider' => $provider->name()]);
        continue;
    }
}

throw new AllProvidersFailedException();

В реальном SMS-шлюзе это спасает: один провайдер начал тормозить — трафик автоматически идёт через второго.

Идемпотентность

Главный риск ретраев: операция могла выполниться, а ответ — потеряться. Повторив запрос, мы спишем деньги дважды.

Решение — идемпотентный ключ. Генерируем UUID на стороне клиента и шлём вместе с запросом. Сервер на этот ключ может выполнить операцию ровно один раз.

Если внешний API не поддерживает идемпотентность — храните «уже отправлено» у себя в БД и проверяйте перед каждым ретраем.

Что ещё делает интеграции живучими

  • Circuit breaker. После N подряд ошибок — временно перестаём бить провайдера, даём ему передохнуть.
  • Таймауты. На каждый внешний вызов. Без таймаута воркер зависает на 10 минут на одном запросе.
  • Метрики. Latency, error rate, retry count — в Prometheus или Grafana. Без метрик вы узнаете о проблеме от клиентов, а не от мониторинга.
  • Логирование запроса/ответа. При разборе инцидента это спасает часы времени.

Запомнить

  • Внешний сервис ВСЕГДА может упасть. Проектируйте интеграции из этого предположения.
  • Retry — только для повторяемых ошибок, обязательно с backoff.
  • Fallback между провайдерами — лучший способ повысить аптайм.
  • Идемпотентность — обязательна для денежных и других «несимметричных» операций.
  • Метрики и логи — глаза и уши интеграции.
#Laravel #Backend #API #Интеграции
Поделиться: