Ciência da computação

O que é idempotência e por que clicar duas vezes no botão de pagamento é seguro

Idempotência é a propriedade de produzir o mesmo resultado quando a mesma requisição é enviada várias vezes. Este artigo explica por que ela é necessária diante de falhas de rede e tentativas, a diferença entre GET, PUT, DELETE e POST e como implementar uma Idempotency-Key.

5 min de leitura
Imagem de capa de O que é idempotência e por que clicar duas vezes no botão de pagamento é seguro

Você clica no botão de pagamento, mas a tela fica travada por um bom tempo. Dá ansiedade. Você quer clicar de novo.

Mas clicar duas vezes não vai cobrar duas vezes?

Em um sistema bem projetado, não. A propriedade que garante isso tem nome.

Idempotência.

Esse conceito aparece em documentação de backend, especificações HTTP e guias de API de pagamento, mas a definição cabe em uma linha. O difícil é entender “por que isso é tão importante” e “como implementar”.

Este artigo responde às duas perguntas.

Veja o resumo principal.

  1. Idempotência: propriedade de obter o mesmo resultado ao enviar uma requisição uma ou várias vezes
  2. Por que importa: redes falham, falhas exigem tentativas novamente e elas só são seguras quando a operação é idempotente
  3. No HTTP, GET, PUT e DELETE são idempotentes; POST não é
  4. O mecanismo prático que torna POST seguro é a Idempotency-Key

Definição: fazer várias vezes equivale a fazer uma vez

Idempotência é um termo da matemática. Se uma operação f satisfaz f(f(x)) = f(x), ela é idempotente.

A função valor absoluto é um bom exemplo. |−5| = 5 e |5| = 5.

O resultado é o mesmo, seja aplicada uma vez ou cem vezes.

Aplicando isso a uma API, temos o seguinte.

O estado do servidor é o mesmo, seja a mesma requisição enviada 1 vez ou N vezes.

Atenção: a resposta não precisa ser igual; o estado do servidor é que precisa ser. Mesmo que a segunda requisição DELETE retorne 404, ela é idempotente porque o estado “o recurso não existe” permanece igual.

Uma analogia cotidiana é o botão do elevador. Mesmo pressionando cinco vezes o botão do quinto andar, o elevador vai ao quinto andar uma única vez.

Por outro lado, se fosse um botão de “subir mais cinco andares”, o resultado mudaria a cada clique. O primeiro é idempotente; o segundo, não idempotente.


Por que importa: redes inevitavelmente falham

A importância da idempotência pode ser explicada por um único cenário.

O cliente envia uma requisição de pagamento. O servidor processa o pagamento.

Mas a rede cai enquanto a resposta está voltando.

O cliente não tem como saber se a requisição falhou antes de chegar ao servidor (pagamento não realizado) ou se apenas a resposta foi perdida depois do processamento (pagamento realizado).

Nos dois casos, o resultado parece exatamente um timeout.

Diagrama de sequência mostrando um pagamento processado uma única vez ao tentar novamente com a mesma chave de idempotência
Quando a resposta é perdida, não há como saber se o pagamento foi concluído. Por isso a idempotência é necessária.

Só existem duas opções: desistir da tentativa novamente (o pagamento pode não ter acontecido) ou tentar novamente (se já aconteceu, haverá cobrança duplicada). As duas são péssimas.

A idempotência elimina esse dilema. Se a requisição for idempotente, “se não souber, envie de novo” será sempre uma estratégia segura.

Tentativas automáticas, entrega pelo menos uma vez (at-least-once) em filas de mensagens e atualizações repetidas no cliente deixam de ser motivo de preocupação.

É por isso que documentos de arquitetura de sistemas distribuídos repetem: “configure tentativas novamente apenas para operações idempotentes”.


Idempotência nos métodos HTTP

A especificação HTTP (RFC 9110) define a idempotência de cada método.

Método Idempotente? Motivo
GET O A consulta não altera o estado
PUT O “Substituir por este valor” produz o mesmo valor todas as vezes
DELETE O “Excluir” deixa o estado excluído todas as vezes
POST X “Criar um novo” aumenta um a cada chamada
PATCH Condicional “Definir este campo como X” é idempotente; “somar 1” não é

O contraste entre PUT e POST é essencial. PUT especifica o “estado resultante”, então é idempotente; POST especifica uma “ação”, então não é.

PATCH depende do conteúdo.

Quando o navegador volta a uma página POST pelo botão Voltar e pergunta “Deseja reenviar este formulário?”, é porque sabe que reenviar POST não é seguro.


O mecanismo prático: chaves de idempotência

Mas cadastro, criação de pedido e pagamento são essencialmente operações POST. Não é possível evitar operações não idempotentes.

Por isso surgiu um mecanismo para tornar requisições não idempotentes idempotentes: a Idempotency-Key.

O funcionamento é simples.

  1. O cliente gera uma chave exclusiva para cada requisição (geralmente um UUID) e a envia em um cabeçalho
  2. Se for a primeira vez que vê a chave, o servidor processa normalmente e armazena a resposta junto com ela
  3. Se a mesma chave chegar novamente, o servidor não processa e devolve exatamente a resposta armazenada

Seja uma tentativa novamente ou um clique duplo, o servidor processa apenas uma vez enquanto a mesma chave for usada. APIs de pagamento como Stripe e Toss Payments oferecem suporte a esse cabeçalho e o tratam como item obrigatório em seus guias de integração.

Um detalhe no cliente: “use a mesma chave ao tentar novamente”.

Se você gerar uma chave nova a cada tentativa, o servidor verá cada uma como uma requisição diferente. A chave deve ser criada por “requisição com a mesma intenção”.

Terminal de pagamento que processa apenas um de dois envelopes com a mesma chave e emite um único recibo
Quando recebe a mesma chave de idempotência, o servidor processa uma vez e devolve a resposta armazenada

Resumo

  • Idempotência é a propriedade de manter o mesmo estado do servidor, independentemente de quantas vezes a mesma requisição seja enviada (f(f(x)) = f(x))
  • Quando uma falha de rede deixa você sem saber se algo foi concluído, a tentativa novamente só é segura se a operação for idempotente
  • HTTP: GET, PUT e DELETE são idempotentes; POST não é; PATCH depende do conteúdo
  • PUT é idempotente porque especifica o estado resultante; POST não é porque indica uma ação
  • Torne um POST não idempotente idempotente com Idempotency-Key — a mesma chave é processada apenas uma vez
  • Ao tentar novamente, sempre reutilize a mesma chave. A chave representa uma “requisição com a mesma intenção”