Unit 03.03: Idempotency, and which methods need it
Clients retry, whether or not your endpoint is safe to retry.
Which methods survive a repeat
Five scenarios, four of them naturally idempotent and one made so.
The code lists them.
SCENARIOS = [
("GET /invoices/1", "idempotent", "retry freely"),
("PUT /invoices/1", "idempotent", "retry freely"),
("DELETE /invoices/1", "idempotent", "retry freely; second call gets 404"),
("POST /invoices", "NOT idempotent", "retry creates a second invoice"),
("POST /invoices with an Idempotency-Key header",
"made idempotent", "retry returns the first result"),
]
print(f"{'request':50} {'kind':16} on retry")
for request, kind, on_retry in SCENARIOS:
print(f"{request:50} {kind:16} {on_retry}")
print("""
Clients retry. Networks time out, proxies give up, users press the button
twice -- so a POST that creates something needs a key the client supplies and
you store, or duplicates are inevitable rather than unlucky.
""")
The bare POST is the problem: a timeout, a proxy giving up or a user pressing the button twice each produce a second invoice. Nothing about HTTP prevents it.
The fix is a key the client supplies and you store. The second request with the same key returns the first result rather than doing the work again, which turns an unavoidable retry into a safe one.
The mistake this prevents
The mistake is assuming clients only send each request once. Networks time out after the server has already acted, so the client cannot know whether to retry - and it will.
Takeaway
Any POST that creates something needs an idempotency key the client supplies. Retries are not optional behaviour; they are what networks force.
