Module 3 · API design

Lesson 14 — Idempotency and versioning

Backwards compatibility and the Idempotency-Key that avoids charging twice.

Published
In this lesson
  1. Exercise 1 — The double charge, reproduced
  2. Exercise 2 — A real Idempotency-Key
  3. Exercise 3 — Idempotent by design
  4. Exercise 4 — Idempotent webhook
  5. Exercise 5 — Versioning without breaking
  6. Submit

DRF + your DB. Do not look at solutions.md before submitting.

Exercise 1 — The double charge, reproduced

  1. Simulate the retry: in the shell, call the payment creation for the same reservation twice (or curl twice). Two payments? (the idempotency_key UNIQUE should prevent it if you generated the key the same way... or are you generating a uuid4 on every POST? Note what happens).
  2. Discuss: where is the semantic bug — the DB rejects the second one, but with what status does your API answer today?

Exercise 2 — A real Idempotency-Key

  1. Create the model IdempotencyKey(key PK, request_hash, status, body JSONB, created_at) + a safe migration (11).
  2. Implement the mechanism (decorator or helper on the payments view): 1st time processes and stores; 2nd time with the same body returns the stored one; same key + different body → 422.
  3. Demonstrate with curl: the same POST twice with the same Idempotency-Key → the same response and ONE Payment in the DB.

Exercise 3 — Idempotent by design

  1. Make DELETE /reservations/{uuid}/ idempotent: cancelling twice → 204 both times (00b's cancel() already returns False; connect the dots).
  2. PUT /api/events/{uuid}/ with full replacement: demonstrate that sending the same PUT twice leaves the same state (and that PATCH with {"price": "+2"} twice is NOT idempotent).

Exercise 4 — Idempotent webhook

  1. In your (simulated) webhook handler: look up the payment by the gateway's event_id before creating; if it exists, answer 200 without reprocessing. Demonstrate with the same payload three times.
  2. What status do you answer to the gateway on the duplicated retry, and why NEVER 4xx there?

Exercise 5 — Versioning without breaking

  1. Enable URLPathVersioning (or manual /api/v1/ routes) and duplicate one endpoint with a breaking change (e.g. v2 returns starts_at ISO with a zone and v1 naive).
  2. Document the deprecation: a Deprecation: true header on v1 once there is usage, and write the metrics query that would measure client migration v1→v2 (what it would count: per endpoint and per day).

Submit

Paste code and curl outputs. Next: Lesson 15 — Validation and contracts with OpenAPI.