Pular para o conteúdo

Idempotência

A resposta se perde na rede e o cliente reenvia. Sem chave de idempotência, a cobrança acontece duas vezes.

GET, PUT e DELETE são idempotentes por definição: repetir dá o mesmo estado final. POST não é — cada um cria algo. E o problema real é de rede: o servidor cobrou, a resposta se perdeu, o cliente não sabe se deu certo, e reenvia.

O cliente sozinho não resolve: ele não tem como saber. Quem resolve é o servidor, reconhecendo o reenvio por uma chave que o cliente gera uma vez por intenção e manda em Idempotency-Key:

dataforge
adopt Arcane.Kiln as Kiln

cobrancas := []
app := Kiln.app()
Kiln.use(app, Kiln.idempotente())

action cobrar(req):
    cobrancas.append(req["body"]["valor"])
    yield Kiln.json({"cobranca": len(cobrancas)}, 201)

Kiln.post(app, "/cobrancas", cobrar)

chave := {"Idempotency-Key": "pedido-77-tentativa"}
primeira := Kiln.test(app, "POST", "/cobrancas", {"valor": 50}, chave)
reenvio := Kiln.test(app, "POST", "/cobrancas", {"valor": 50}, chave)

assert len(cobrancas) is 1                        // cobrou uma vez só
assert reenvio["body"] is primeira["body"]
assert reenvio["headers"]["Idempotent-Replay"] is "true"
  • A chave é por intenção, não por tentativa. Gerar uma nova a cada reenvio desliga a proteção.
  • Só resposta de sucesso fica guardada. Um 500 guardado faria o reenvio devolver o erro para sempre, quando o reenvio existe justamente para tentar de novo.
  • Fica em memória. Um processo reiniciado esquece as chaves, e várias réplicas não se enxergam. Para valer em produção, a chave vai para o banco — com restrição de unicidade.

Do lado de quem chama, Arcane.Malha já não repete POST sem chave: ver Chamadas entre serviços.