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.