Idempotência: como lidar corretamente com chamadas duplicadas em APIs e backends

Avatar de Bernardo Lobato

Se você trabalha com backend, APIs ou sistemas distribuídos, existe uma verdade inevitável que cedo ou tarde aparece em produção:

👉 Chamadas duplicadas vão acontecer.

Elas surgem por retry automático, timeout de rede, instabilidade de internet, falhas intermediárias, consumidores paralelos ou, simplesmente, pelo famoso “dedo nervoso” do usuário clicando várias vezes no mesmo botão.

E o problema não é a duplicação em si — o problema é o que o seu sistema faz quando ela acontece.

Neste artigo, vamos entender:

O que é idempotência (de verdade, sem buzzword)

Por que chamadas duplicadas são a regra, não a exceção

Exemplos reais de problemas causados por falta de idempotência

Estratégias para tratar duplicidade corretamente

Quando idempotência é obrigatória, desejável ou overengineering

O problema das chamadas duplicadas

Vamos começar com alguns exemplos comuns do mundo real.

  1. Retry e timeout no frontend

Imagine um endpoint POST /pagamentos.

O fluxo é simples:

O cliente chama a API

O backend processa o pagamento

O banco é atualizado

O cliente recebe a resposta

Agora adicione uma variável muito comum: internet instável.

O pagamento é processado com sucesso, mas o cliente não recebe a resposta por causa de um timeout. O frontend, bem programado, aplica uma política de retry e dispara a mesma requisição novamente.

Resultado:

O sistema “funcionou”

Não houve erro técnico

Mas o usuário pode ser cobrado duas vezes

Tecnicamente resiliente. Arquiteturalmente frágil.

  1. Confirmação de e-mail e tokens inválidos

Outro exemplo clássico: POST /usuarios/confirmar-email.

O usuário clica no botão para receber o token.
Clica de novo.
Clica de novo.
Clica de novo.

Cada clique gera um novo token, invalidando o anterior.

Resultado:

O usuário recebe vários e-mails

Nenhum token funciona

A experiência degrada

O suporte recebe chamados

Tudo isso porque o sistema assumiu que a chamada aconteceria uma única vez.

  1. Geração de relatórios pesados

Agora imagine um endpoint que gera um relatório complexo:

Várias queries

Processamento pesado

Geração de PDF

Se múltiplas requisições iguais chegam ao mesmo tempo, você pode:

Saturar o banco

Travar o sistema

Gerar o mesmo relatório várias vezes desnecessariamente

O erro conceitual por trás de tudo isso

Esses exemplos têm algo em comum:

Assumem que a chamada acontece uma única vez

Geram efeitos colaterais indesejados quando isso não acontece

Em ambientes distribuídos, isso nunca é uma premissa válida.

É aqui que entra o conceito de idempotência.

O que é idempotência?

O termo vem da matemática, mais especificamente da álgebra abstrata.

Uma operação idempotente é aquela que:

não importa quantas vezes seja aplicada com as mesmas entradas, o resultado final é sempre o mesmo.

Exemplos do mundo real:

Botão de elevador

Botão de emergência

Botão de “parar” de uma máquina

A expectativa é simples:
👉 apertar uma vez ou dez vezes produz o mesmo efeito.

Em APIs e backends, deveria ser exatamente assim.

Idempotência em sistemas

Aplicar idempotência significa garantir que:

Chamadas duplicadas não quebram o sistema

Efeitos colaterais não se repetem

O estado só muda quando realmente precisa mudar

Gerar um token?
➡️ Apenas uma vez dentro da regra definida.

Criar um pagamento?
➡️ Apenas uma vez para aquela transação.

Gerar um relatório?
➡️ Apenas uma vez para aquele conjunto de parâmetros.

Como identificar chamadas duplicadas?

Antes de tratar duplicidade, precisamos responder uma pergunta fundamental:

Como saber que duas chamadas são, de fato, a mesma operação?

Existem várias estratégias, mas duas são as mais comuns.

  1. Identidade lógica (baseada no domínio)

Aqui, usamos dados do próprio negócio para garantir unicidade.

Exemplos:

E-mail

CPF

Data/hora

Tipo de operação

Estado atual

Exemplo prático:

Um token de confirmação só pode ser gerado a cada 5 minutos para o mesmo e-mail

Se uma nova requisição chega dentro desse intervalo, reutilizamos o token existente

Vantagens:

Simples de entender

Alinhada ao domínio

Pode usar índices únicos no banco

Pouca ou nenhuma mudança no payload

Desvantagens:

Regras precisam estar muito bem alinhadas entre sistemas

Mudanças exigem coordenação entre times

Acoplamento forte ao domínio

  1. Chave de idempotência

Aqui, o cliente gera um identificador único e envia junto com a requisição.

Essa chave:

Identifica a operação

Independe dos dados de negócio

É armazenada no backend junto com o estado da operação

Fluxo típico:

O cliente gera uma chave

Envia a requisição com essa chave

O backend verifica se ela já existe

Decide se processa, reutiliza ou ignora

Vantagens:

Menos acoplamento ao domínio

Muito comum em APIs públicas e pagamentos

Excelente para retrys automáticos

Cuidados:

A chave precisa ser persistida

O cliente precisa armazená-la corretamente

Exige storage, locks e controle de estado

Quando idempotência é obrigatória?

Idempotência não é “nice to have” em alguns cenários — é requisito de negócio.

Ela é obrigatória quando:

Há efeitos colaterais relevantes

Envolve dinheiro, contratos ou confiança

O sistema é distribuído

Existe mensageria, filas, eventos e consumidores paralelos

Se repetir a operação causa prejuízo ou caos, idempotência é obrigatória.

Quando idempotência é desejável?

Ela é altamente recomendada quando:

Existe retry automático

O sistema lida com redes instáveis

O estado final não pode ser repetido

A operação representa uma transição de estado

Exemplos:

Confirmação de e-mail

Ativação de conta

Finalização de pedido

Cancelamento de assinatura

Quando idempotência pode ser overengineering?

Nem tudo precisa ser idempotente.

Alguns exemplos:

Operações somente leitura

Geração de logs

Métricas

Auditoria

Eventos que devem ser repetidos

Se:

O impacto da duplicação é baixo

O custo de implementação é alto

Não há identidade clara da operação

Talvez o risco seja aceitável.

Arquitetura também é saber onde não colocar complexidade.

E quanto ao REST?

De forma geral:

GET → naturalmente idempotente

PUT → geralmente idempotente

POST → geralmente não idempotente

Mas isso não é regra escrita em pedra.

Tudo depende:

Do domínio

Dos efeitos colaterais

Das garantias que seu sistema precisa oferecer

Conclusão

Chamadas duplicadas não são exceção.
Elas são a regra em sistemas reais.

Retry, timeout, instabilidade, falhas intermediárias e comportamento humano fazem parte do jogo.

Nosso papel como arquitetos e desenvolvedores é:

Antecipar esse cenário

Tratar duplicidade corretamente

Proteger o estado do sistema

Evitar efeitos colaterais indesejados

Idempotência é uma decisão de arquitetura, não um detalhe de implementação.

Ela tem custo.
Ela tem complexidade.
E exatamente por isso não deve ser aplicada cegamente.

Garantir idempotência é garantir que o estado do sistema só muda quando realmente precisa mudar.