DEV Community

Denis Augusto
Denis Augusto

Posted on

A API de terceiro caiu e derrubou seu app junto

A API deles caiu. Por que o SEU app caiu junto?

O fornecedor de frete teve um problema. A API deles ficou lenta — respondendo em 40 segundos em vez de 200 milissegundos.

Do lado deles, um incidente. Do seu lado? Caos.

Cada requisição que seu app fez pra calcular o frete ficou pendurada esperando. Os workers da fila entupiram. As conexões do PHP-FPM esgotaram. Em poucos minutos, seu app inteiro parou de responder — não só o checkout, tudo.

Um problema que era deles virou seu problema. E o pior: era evitável com três linhas.

O código que confia demais no mundo lá fora

Chamada de API é um ato de fé. Você pede algo pra um servidor que não é seu e espera que ele responda rápido, sempre, sem erro. Spoiler: ele não vai.

// Confiança cega: espera pra sempre, e reza pra dar certo
$response = Http::get('https://api-frete.com/cotacao', [
    'cep' => $cep,
]);

return $response->json();
Enter fullscreen mode Exit fullscreen mode

O que tá faltando aqui?

  • Sem limite de espera. Se a API travar, sua requisição trava junto. Por padrão o HTTP Client espera até 30 segundos — uma eternidade quando isso acontece em cascata.
  • Sem tentar de novo. Uma falha de rede momentânea (acontece o tempo todo) já derruba a operação inteira.
  • Sem tratar erro. Se a API devolver 500, o $response->json() segue como se nada fosse, e você processa lixo achando que é cotação.

A blindagem: timeout, retry e throw

O HTTP Client do Laravel já traz o resgate embutido. Você encadeia e pronto:

$response = Http::timeout(5)   // desiste depois de 5s, não fica pendurado
    ->retry(3, 200)            // tenta 3x, esperando 200ms entre as tentativas
    ->get('https://api-frete.com/cotacao', ['cep' => $cep]);

$response->throw();            // erro 4xx/5xx? lança exceção, não segue com lixo

return $response->json();
Enter fullscreen mode Exit fullscreen mode

Três métodos, três problemas resolvidos:

  • timeout(5) — se em 5 segundos não respondeu, o Laravel desiste e lança uma ConnectionException. Sua requisição não fica travada arrastando o app junto.
  • retry(3, 200) — falha de rede? Ele tenta de novo, até 3 vezes, com 200ms de intervalo. A instabilidade passageira some sozinha.
  • throw() — se depois de tudo a resposta for erro, ele lança exceção. Você trata de propósito, em vez de processar dado inválido silenciosamente.

Como usar na prática

Alguns ajustes que valem a pena conhecer:

// Separar timeout de conexão do timeout de resposta
Http::connectTimeout(3)->timeout(10)->get(/* ... */);

// Só relançar em erro de servidor, não em 404 esperado
$response->throwIf($response->serverError());

// Ou garantir que veio exatamente 200
$response->throwUnlessStatus(200);
Enter fullscreen mode Exit fullscreen mode

connectTimeout é quanto você espera pra conectar (3s costuma bastar). timeout é o total até a resposta chegar. Separar os dois te dá controle fino: "conecta rápido, mas se conectou, dou mais um tempinho pra processar".

A pegadinha: retry cego pode cobrar o cartão duas vezes

Aqui mora o perigo que quase ninguém pensa. O retry é maravilhoso pra operações idempotentes — buscar uma cotação, consultar um CEP. Repetir não faz mal.

Mas e uma cobrança? Se a API processou o pagamento e a resposta se perdeu no caminho, o retry vai tentar de novo — e cobrar o cliente duas vezes. 😬

Pra esses casos, o retry aceita uma condição: só repete se for erro de conexão (a requisição nem chegou), nunca se o servidor já respondeu:

use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\PendingRequest;

Http::retry(3, 200, function ($exception, PendingRequest $request) {
    // só tenta de novo se NEM CHEGOU no servidor
    return $exception instanceof ConnectionException;
})->post('https://gateway.com/charges', $dados);
Enter fullscreen mode Exit fullscreen mode

A régua: repetir consulta, sempre. Repetir cobrança, só se tiver certeza de que nem chegou.

Bônus: e agora?

Trocar Guzzle na mão pelo Http:: do Laravel não é só sintaxe mais bonita. É timeout, retry, throw e — de brinde — a possibilidade de mockar tudo nos testes com Http::fake(), sem tocar na API real (isso rende um post inteiro à parte).

Se você tem uma integração externa crítica sem timeout, essa é sua lição de casa pra hoje. Uma API lenta lá fora não pode ser capaz de derrubar seu app aqui dentro.

Me conta: seu app já foi derrubado por uma API de terceiro? O que travou primeiro — a fila ou o FPM? 👀

Top comments (0)