Idempotência é uma decisão de negócio
Chamar duas vezes e ter o mesmo resultado parece técnico. Definir o que é "o mesmo" é onde o negócio assina.
Engenheiro fala de idempotência como propriedade técnica: chama duas vezes, recebe o mesmo resultado. É verdade, e esconde a parte importante. Decidir o que significa "o mesmo resultado" não é uma pergunta técnica. É uma pergunta sobre dinheiro, sobre promessas feitas ao cliente e sobre quem tem permissão para errar.
O retry que cobra duas vezes
O caso clássico. Um cliente chama "cobrar o cartão", a rede cai depois que a cobrança já passou, o cliente tenta de novo, o cartão é cobrado duas vezes. Todo mundo concorda que isso é ruim. A correção é a chave de idempotência: o cliente manda uma chave única junto com a requisição, o servidor guarda o resultado sob essa chave, e um retry com a mesma chave devolve o resultado guardado em vez de cobrar de novo.
Simples. Agora começam as perguntas, e nenhuma delas é técnica.
Perguntas que só o negócio pode responder
Por quanto tempo a gente lembra da chave? Vinte e quatro horas significa que um retry no segundo dia cobra de novo. Para sempre significa armazenamento e uma consulta a cada requisição. Um time de pagamentos pode dizer sete dias. Um sistema de ingressos pode dizer até o evento acontecer.
E se a mesma chave chegar com um valor diferente? Isso é erro, requisição nova ou fraude? A maioria das APIs de pagamento rejeita. Um carrinho de compras pode aceitar o valor mais recente. A resposta depende de os seus clientes serem serviços internos confiáveis ou terceiros.
Qual é a unidade de "mesmo"? Mesma chave. Mesmo cliente, mesmo produto, mesmo minuto. Mesmo número de fatura. A renovação de uma assinatura é idempotente por período de cobrança, não por requisição, e essa regra vem do financeiro, não do engenheiro.
E a que as pessoas esquecem: o que o retry enxerga? A resposta original, byte a byte? Ou o estado atual do recurso, que pode ter andado? Se a primeira requisição criou um pedido e o pedido já foi enviado, o retry devolve "criado" ou "enviado"? O suporte vai ter opinião sobre isso.
Onde a idempotência deve morar
A fronteira que importa é aquela em que uma ação vira irreversível ou cara: cobrar dinheiro, enviar um e-mail, chamar uma API de terceiro com cota, criar um registro ao qual outro sistema vai reagir. Tudo antes dessa fronteira pode ser repetido de graça. Tudo depois dela precisa de chave.
Minha regra: todo endpoint de escrita que um cliente pode repetir aceita chave de idempotência, e todo consumidor de fila trata cada mensagem como se ela fosse entregue duas vezes, porque vai ser. A chave é gravada na mesma transação que o efeito colateral. Se ficam em stores diferentes, existe uma janela em que o efeito aconteceu e a chave não foi gravada, e é exatamente dessa janela que saem as duplicatas.
O custo de fingir
Às vezes o time pula a idempotência porque "nossos clientes não fazem retry". Fazem. Rede móvel faz retry. Navegador faz retry quando a conexão é resetada. Load balancer faz retry em certos erros. A sua própria fila reentrega. Se uma requisição pode ser enviada, ela pode ser enviada duas vezes, e a única pergunta é se você decidiu o que acontece.
Já auditei sistemas em que a taxa de duplicata era menor do que uma em dez mil e ainda assim importava, porque as duplicatas eram estornos. Um bug raro multiplicado por um número grande o bastante é um evento com data marcada.
Como ter essa conversa
Leve para o product owner uma lista, não uma palestra. Para cada escrita do sistema: o que acontece se rodar duas vezes, quanto isso custa e o que a gente gostaria que acontecesse em vez disso. A maioria das linhas vai ser "inofensivo, ignora". Algumas vão ser "a gente perde dinheiro" ou "o cliente recebe dois de alguma coisa". São essas poucas linhas em que o negócio decide a chave, a janela e a regra de conflito. Aí, e só aí, o engenheiro implementa.
Idempotência não é uma biblioteca que você adiciona. É um conjunto de promessas que você faz, e o negócio precisa assinar embaixo.