Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

234 Dokumentationen verfügbar

Wissensdatenbank

Idempotenz API Design

Zuletzt aktualisiert: 20.01.2026 um 11:26 Uhr

Idempotenz in API Design

Idempotente APIs können sicher mehrfach aufgerufen werden ohne unerwünschte Nebeneffekte. Lernen Sie Idempotenz richtig zu implementieren für robuste und fehlertolerante APIs.

Was ist Idempotenz?

┌─────────────────────────────────────────────────────────────┐
│                     IDEMPOTENZ                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   "Eine Operation ist idempotent, wenn sie mehrfach        │
│    ausgeführt werden kann und das gleiche Ergebnis         │
│    liefert wie eine einzige Ausführung."                   │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                                                      │  │
│   │   f(x) = f(f(x)) = f(f(f(x))) = ...                │  │
│   │                                                      │  │
│   │   1 Aufruf = 2 Aufrufe = n Aufrufe                  │  │
│   │                                                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
│   BEISPIEL IDEMPOTENT:                                     │
│   PUT /users/123 { "name": "John" }                        │
│   → Ausführung 1x: User heißt John                        │
│   → Ausführung 3x: User heißt immer noch John ✅          │
│                                                             │
│   BEISPIEL NICHT IDEMPOTENT:                               │
│   POST /orders { "product": "Laptop" }                     │
│   → Ausführung 1x: 1 Bestellung                           │
│   → Ausführung 3x: 3 Bestellungen ❌                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

HTTP Methoden und Idempotenz

Methode Idempotent Safe Erklärung
GET ✅ Ja ✅ Ja Liest nur, ändert nichts
HEAD ✅ Ja ✅ Ja Wie GET ohne Body
PUT ✅ Ja ❌ Nein Ersetzt komplette Ressource
DELETE ✅ Ja ❌ Nein Ressource ist danach weg
POST ❌ Nein ❌ Nein Erstellt neue Ressource
PATCH ❌ Nein* ❌ Nein Teilweise Updates

* PATCH kann idempotent implementiert werden, ist es aber nicht per Definition.

Warum ist Idempotenz wichtig?

SZENARIO: Netzwerk-Timeout bei Zahlung

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Client                    Server                          │
│     │                         │                             │
│     │── POST /payments ──────►│                             │
│     │   { amount: 100 }       │                             │
│     │                         │ ┌──────────────────────┐   │
│     │                         │ │ Zahlung verarbeitet  │   │
│     │                         │ │ 100€ abgebucht       │   │
│     │                         │ └──────────────────────┘   │
│     │                         │                             │
│     │◄── TIMEOUT ────────────│  Response verloren!         │
│     │                         │                             │
│     │                         │                             │
│     │── POST /payments ──────►│  Client versucht erneut    │
│     │   { amount: 100 }       │                             │
│     │                         │ ┌──────────────────────┐   │
│     │                         │ │ NOCH MAL verarbeitet │   │
│     │                         │ │ 100€ ERNEUT abgebucht│   │
│     │                         │ └──────────────────────┘   │
│     │                         │                             │
│     │◄── 201 Created ────────│                             │
│                                                             │
│   RESULTAT: Kunde hat 200€ statt 100€ bezahlt! 💸          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Idempotency Key Pattern

// Client sendet eindeutigen Key mit Request
POST /api/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
    "amount": 100,
    "currency": "EUR",
    "customer_id": 123
}

// Server speichert Key + Response
// Bei wiederholtem Request: Cached Response zurückgeben
// PHP Implementation

class IdempotencyMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        // Nur für POST Requests
        if ($request->method() !== 'POST') {
            return $next($request);
        }

        $idempotencyKey = $request->header('Idempotency-Key');

        if (!$idempotencyKey) {
            return $next($request);  // Ohne Key normal verarbeiten
        }

        // Prüfen ob Request schon verarbeitet wurde
        $cached = Cache::get("idempotency:{$idempotencyKey}");

        if ($cached) {
            // Gleicher Request? Cached Response zurückgeben
            if ($this->requestMatches($request, $cached['request'])) {
                return response()->json(
                    $cached['response'],
                    $cached['status']
                )->header('Idempotent-Replayed', 'true');
            }

            // Anderer Request mit gleichem Key = Fehler
            return response()->json([
                'error' => 'Idempotency key already used for different request'
            ], 422);
        }

        // Request verarbeiten
        $response = $next($request);

        // Response cachen (24 Stunden)
        Cache::put("idempotency:{$idempotencyKey}", [
            'request' => $this->hashRequest($request),
            'response' => $response->getData(),
            'status' => $response->status()
        ], 86400);

        return $response;
    }

    private function hashRequest(Request $request): string
    {
        return hash('sha256', json_encode([
            'path' => $request->path(),
            'body' => $request->all()
        ]));
    }

    private function requestMatches(Request $request, string $hash): bool
    {
        return $this->hashRequest($request) === $hash;
    }
}

Idempotent POST mit Client-ID

// Alternative: Client generiert ID

POST /api/orders
Content-Type: application/json

{
    "id": "ord_550e8400-e29b-41d4-a716-446655440000",  // Client-generiert
    "product_id": 123,
    "quantity": 2
}

// Server: INSERT ... ON CONFLICT DO NOTHING
// oder: Prüfen ob ID existiert

// Response bei Wiederholung:
// 200 OK (existierende Bestellung)
// statt
// 201 Created (neue Bestellung)
// SQL mit UPSERT
INSERT INTO orders (id, product_id, quantity, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (id) DO NOTHING
RETURNING *;

// PHP Implementation
class OrderController
{
    public function store(Request $request): JsonResponse
    {
        $orderId = $request->input('id');

        // Prüfen ob Bestellung existiert
        $existingOrder = Order::find($orderId);

        if ($existingOrder) {
            // Idempotent: Existierende Bestellung zurückgeben
            return response()->json($existingOrder, 200)
                ->header('Idempotent-Replayed', 'true');
        }

        // Neue Bestellung erstellen
        $order = Order::create([
            'id' => $orderId,
            'product_id' => $request->input('product_id'),
            'quantity' => $request->input('quantity')
        ]);

        return response()->json($order, 201);
    }
}

Idempotentes DELETE

// DELETE ist per Definition idempotent
// ABER: Was wenn Ressource nicht existiert?

// Option 1: 404 Not Found
DELETE /api/users/999  →  404 Not Found (nicht idempotent!)

// Option 2: 204 No Content (auch wenn nicht existiert)
DELETE /api/users/999  →  204 No Content (idempotent!)

// Empfehlung: 204 No Content
// "Die Ressource existiert nicht (mehr)" = Erfolgreich
// PHP Implementation
class UserController
{
    public function destroy(int $id): Response
    {
        // Soft Delete oder Hard Delete
        $deleted = User::where('id', $id)->delete();

        // Immer 204, egal ob gelöscht oder nicht existiert
        return response()->noContent();  // 204

        // NICHT:
        // if (!$deleted) return response()->json(['error' => 'Not found'], 404);
    }
}

Idempotentes PATCH

// PATCH kann idempotent gemacht werden

// ❌ Nicht idempotent: Relative Änderung
PATCH /api/accounts/123
{ "balance_change": +100 }

1x ausführen: Balance = 1000 + 100 = 1100
2x ausführen: Balance = 1100 + 100 = 1200 ❌

// ✅ Idempotent: Absolute Änderung
PATCH /api/accounts/123
{ "balance": 1100 }

1x ausführen: Balance = 1100
2x ausführen: Balance = 1100 ✅
// Idempotentes Increment mit Version/ETag

PATCH /api/accounts/123
If-Match: "v42"
Content-Type: application/json

{ "balance": 1100 }

// Server prüft Version
// Wenn v42 nicht aktuell: 412 Precondition Failed
// Client muss neu laden und Änderung neu berechnen

Retry-Strategien

// Client-seitige Retry mit Idempotency Key

async function createPayment(data, maxRetries = 3) {
    const idempotencyKey = crypto.randomUUID();

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch('/api/payments', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Idempotency-Key': idempotencyKey
                },
                body: JSON.stringify(data)
            });

            if (response.ok) {
                return await response.json();
            }

            // Nicht retrybare Fehler
            if (response.status >= 400 && response.status < 500) {
                throw new Error(`Client error: ${response.status}`);
            }

            // Server-Fehler: Retry
        } catch (error) {
            if (attempt === maxRetries) throw error;

            // Exponential Backoff
            await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
        }
    }
}

Best Practices

1. IDEMPOTENCY KEY
   • UUID/ULID verwenden
   • Client generiert Key
   • 24-48 Stunden cachen
   • Bei Konflikt: 409 oder 422

2. RESPONSE CACHING
   • Request Hash speichern
   • Gleicher Key + anderer Request = Fehler
   • Header "Idempotent-Replayed: true"

3. DESIGN
   • PUT statt POST wo möglich
   • Client-generierte IDs
   • Absolute statt relative Änderungen
   • Versioning/ETags für Updates

4. FEHLERBEHANDLUNG
   • Retryable vs Non-retryable Errors
   • Exponential Backoff
   • Idempotent auch bei Fehlern
💡 Zusammenfassung: 1. Idempotency-Key Header für POST Requests
2. Client-generierte IDs als Alternative
3. DELETE immer 204, auch wenn nicht existiert
4. Absolute Werte statt Deltas bei Updates
5. Response cachen für Replay-Requests

Weitere Informationen