Idempotency

Microservices · lesson 9 of 33 · 4 min read

Make repeated requests safe so retries and at-least-once delivery stop being frightening.

Open this lesson in the learning hub

Key points

  • Idempotent means doing it twice has the same effect as doing it once. In a distributed system you will do it twice.
  • GET, PUT and DELETE are naturally idempotent. POST is not, and that is where all the work is.
  • Take an Idempotency-Key header. Store the key with its result; on a repeat, return that result instead of redoing the work.
  • Back it with a database unique constraint. A duplicate then fails loudly instead of silently creating a second order.
  • For message consumers, dedupe on the event id. A processed-ids table written in the same transaction as the work is cheap and reliable.

Example

@PostMapping("/api/payments")
ResponseEntity<Payment> pay(@RequestHeader("Idempotency-Key") String key,
                            @RequestBody PaymentRequest request) {

    // Seen this key before? Replay the original answer, charge nobody twice.
    Optional<Payment> existing = payments.findByIdempotencyKey(key);
    if (existing.isPresent()) {
        return ResponseEntity.ok(existing.get());
    }

    try {
        // Table has: UNIQUE (idempotency_key)
        Payment saved = payments.save(Payment.of(key, request));
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);

    } catch (DataIntegrityViolationException raced) {
        // Two identical requests landed at the same moment.
        // The loser returns the winner's result.
        return ResponseEntity.ok(payments.findByIdempotencyKey(key).orElseThrow());
    }
}

Assume every request arrives twice, then make the second one harmless.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Microservices course, and every lesson in it is listed on the Microservices contents page.