DevGuideTK
← Trilha Backend

Idempotência

Como tornar seguras as operações repetidas quando a rede falha no meio do pedido.

As decisões de idempotência são cinco: quais operações precisam dela, onde guardar as chaves, por quanto tempo, como tratar pedidos simultâneos e como lidar com efeitos fora do banco.

Quais operações precisam

OperaçãoPrecisa de chave?
Pagamentos, reembolsos, transferênciasSim, sempre
Criação de pedidos, reservas, emissão de documentosSim
Envio de e-mails, SMS, notificaçõesSim, ou deduplicação pelo destinatário e assunto
PUT e DELETE bem implementadosNão: já são idempotentes
POST que só registram algo inofensivo de repetir, como um evento de visualizaçãoGeralmente não

Onde guardar as chaves

OndeVantagensCuidados
No banco principal, na mesma transação do efeitoA chave e o efeito são gravados juntos ou não são gravados; nenhum estado intermediárioA tabela cresce e precisa de limpeza
No RedisRápido, com expiração automáticaA chave e o efeito estão em sistemas diferentes: se o processo cair entre os dois, fica inconsistente
No middleware ou gatewayUma implementação para todas as rotasO gateway não sabe se o efeito no banco aconteceu

O banco principal, na mesma transação, é a escolha mais segura sempre que o efeito também está nele. Foi a abordagem testada nas cinco linguagens. O Redis faz sentido como camada extra para recusar repetições muito rápidas, mas não substitui a garantia do banco.

O que guardar

CampoPara quê
A chaveIdentificar a operação
Um hash do corpo do pedidoRecusar a mesma chave com conteúdo diferente
O código de status e o corpo da respostaDevolver exatamente a mesma resposta nas repetições
Quem enviou (usuário ou cliente da API)Evitar que a chave de um cliente colida com a de outro
Data de criaçãoLimpeza das chaves antigas

A chave deve ser única por cliente, e não globalmente: duas empresas que integram a mesma API podem gerar o mesmo valor por coincidência ou por um defeito. Uma restrição única em (cliente_id, chave) resolve.

Por quanto tempo

As chaves precisam viver mais que o maior intervalo em que um cliente pode tentar de novo. Um prazo de 24 horas é comum em APIs de pagamento. Depois disso, uma limpeza periódica remove as antigas:

DELETE FROM chaves_idempotencia WHERE criada_em < now() - interval '24 hours';

Pedidos simultâneos com a mesma chave

AbordagemComportamentoQuando usar
Esperar a primeira terminarO segundo pedido fica parado até o primeiro confirmar, e recebe a mesma respostaOperações rápidas, totalmente dentro do banco
Responder 409 enquanto processaO segundo pedido recebe 409 Conflict e tenta de novo mais tardeOperações demoradas, que não podem prender uma transação

A primeira é a do exemplo testado: o próprio banco faz a segunda transação esperar na inserção da chave. A segunda exige gravar a chave com um estado "em processamento" e confirmar antes de fazer o trabalho, o que leva ao próximo ponto.

Efeitos fora do banco

O exemplo testado cria o pagamento no próprio banco. Na prática, um pagamento envolve chamar um provedor externo, e essa chamada não cabe numa transação do banco: segurar uma transação aberta enquanto espera uma API externa prende conexões e travas, como alerta o tópico de transações.

EtapaO que fazer
1. Reservar a chaveGravar a chave com estado processando e confirmar
2. Chamar o provedorRepassar uma chave de idempotência ao provedor, derivada da sua
3. Gravar o resultadoAtualizar a chave com estado concluido e a resposta, junto com o registro do pagamento
Se o processo cair entre 2 e 3Uma nova tentativa encontra a chave em processando, chama o provedor de novo com a mesma chave dele e recebe o resultado da primeira cobrança, sem cobrar de novo

A segunda linha é o que torna o processo inteiro seguro: os provedores de pagamento sérios aceitam chaves de idempotência. Repassar uma chave derivada da sua garante que, mesmo se o seu sistema chamar o provedor duas vezes, a cobrança aconteça uma vez.

O lado do cliente

PráticaPor quê
Gerar a chave uma vez por operação, antes da primeira tentativaAs novas tentativas precisam da mesma chave
Guardar a chave até receber uma resposta definitivaSe o aplicativo for fechado e reaberto, a nova tentativa usa a mesma chave
Repetir só com chave, e com intervalo crescenteNovas tentativas sem chave em POST são cobranças duplas esperando acontecer
Tratar 409 como "tente de novo em instantes"A operação ainda está em andamento
Tratar 422 de chave reutilizada como erro do próprio clienteUma chave nova foi esquecida

Resumo da decisão

  1. Toda operação que cria ou cobra aceita uma chave de idempotência.
  2. Chave e efeito na mesma transação, com restrição única, nunca "conferir e depois gravar".
  3. Guardar a resposta e o hash do corpo; recusar a mesma chave com outro conteúdo.
  4. Chave por cliente, com validade de cerca de 24 horas e limpeza periódica.
  5. Efeitos externos: estado intermediário e chave repassada ao provedor.
  6. No cliente: uma chave por operação, mantida durante as novas tentativas.

O próximo nível mostra a implementação nas cinco linguagens, testada com 20 pedidos simultâneos.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Desenhar a idempotência da minha API

    Quero desenhar o suporte a chaves de idempotência na minha API.
    
    Pergunte qual linguagem, framework e banco eu uso, quais rotas criam ou cobram algo e se há chamadas a serviços externos, como provedores de pagamento.
    
    Ajude a decidir:
    1. Quais rotas exigem a chave.
    2. Onde guardar as chaves e o que guardar com elas.
    3. Por quanto tempo e como limpar.
    4. O comportamento com pedidos simultâneos: esperar ou responder 409.
    5. Como tratar efeitos externos com estado intermediário e chave repassada ao provedor.
    
    Termine com o desenho da tabela e o fluxo de cada caso.
  • Idempotência com um provedor de pagamento

    Minha API chama um provedor de pagamento externo e quero garantir que nenhuma falha cause cobrança dupla.
    
    Pergunte qual provedor eu uso e como a chamada é feita hoje.
    
    Explique:
    1. Por que não chamar o provedor dentro de uma transação do banco.
    2. O fluxo com estado intermediário: reservar a chave, chamar o provedor, gravar o resultado.
    3. Como derivar e repassar a chave de idempotência para o provedor.
    4. O que acontece em cada ponto de falha possível, e como a nova tentativa se recupera.
    5. Como reconciliar com os registros do provedor, para pegar qualquer caso que escape.
  • Clientes que repetem com segurança

    Quero que os clientes da minha API, ou os meus serviços que chamam outras APIs, repitam pedidos com segurança.
    
    Pergunte qual linguagem e biblioteca de HTTP eu uso.
    
    Explique, com código:
    1. Como gerar a chave uma vez por operação e mantê-la durante as novas tentativas.
    2. Como guardar a chave para sobreviver a um reinício do aplicativo.
    3. Quais respostas devem gerar nova tentativa (tempo esgotado, 409, 5xx) e quais não (4xx, exceto 409).
    4. O intervalo crescente entre tentativas.
    5. O erro de gerar uma chave nova a cada tentativa, e como um teste pega esse erro.