Playbook

Idempotenz jenseits von API (Application Programming Interface)-Anfragen: eine mehrschichtige Verteidigung (Idempotenz Jenseits Von API Anfragen)

Idempotenz ist kein einziger Header. Sie ist eine Verteidigungs-Kette, die getrennt auf fünf Ebenen aufgebaut werden muss — vom API-Key bis zum Schritt-Marker.

Verteilte Payment Engine (Distributed Payment Engine)

Teil 5 von 22

Serie zur verteilten Payment-Architektur — die Lücke zwischen Capture und Complete.

Distributed payment engine architecture diagram

Idempotenz ist eine Kette, kein Header

Die meisten Teams lernen Idempotenz als „einen Idempotency-Key-Header zur API-Anfrage hinzufügen“ und hören dort auf. In Zahlungssystemen ist das nur die sichtbare Spitze des Eisbergs. Dieselbe Operation kann Ihr System von mindestens fünf verschiedenen Stellen als „Wiederholung“ durchlaufen: Retry des Clients, erneute Zustellung des PSP-Webhooks, At-least-once-Zustellung des Message-Brokers, erneutes Abholen des Jobs durch den Worker, erneutes Laufen des Saga-Schritts.

Client-Retry → API-Idempotenzschlüssel
PSP-Retry   → Gateway-Event-ID
Broker-Retry → Inbox-Eintrag
Worker-Retry → Job-Eindeutigkeit
Saga-Retry  → Schritt-Marker

Dieser Teil erklärt, wie Sie Idempotenz getrennt auf jeder dieser fünf Ebenen aufbauen — nicht nur an einer Stelle.

Begriffe, dort definiert, wo sie zuerst auftauchen

📦 API Idempotency Key
Ein vom Client gesendeter eindeutiger Schlüssel, der garantiert, dass eine wiederholt gesendete Anfrage dasselbe Ergebnis liefert.

📦 Gateway Event ID
Eine eindeutige Kennung, die der PSP jedem Webhook oder jeder Benachrichtigung anfügt; unterscheidet mehrere Zustellungen desselben Ereignisses.

📦 Inbox
Eine dauerhafte Tabelle, in der ein eingehendes Ereignis vor der Verarbeitung erfasst wird und Duplikate herausfiltert.

📦 Job Uniqueness
Eine Einschränkung, die verhindert, dass ein Hintergrundjob unter demselben Job-Schlüssel ein zweites Mal eingereiht wird.

📦 Schritt-Marker
Ein dauerhafter Datensatz, der einen Saga-Schritt als abgeschlossen markiert und ein erneutes Laufen verhindert.

Diese fünf Begriffe ersetzen sich nicht gegenseitig. Ein API-Idempotenzschlüssel verhindert Duplizierung zwischen Client und Ihrer API — er hat keine Wirkung auf den Webhook des PSP, die Zustellung Ihres Brokers oder den Job Ihres Workers.

Ebene 1: die API-Anfrage

Klickt ein Kunde zweimal auf „Bezahlen“ (Netzwerkverzögerung, Doppelklick), sendet der Client die Anfrage mit demselben Idempotency-Key. Hat der Server diesen Schlüssel schon gesehen, führt er die Operation nicht erneut aus — er gibt das Ergebnis des ersten Laufs zurück. Diese Ebene ist rein ein Vertrag zwischen Ihrer API und dem Client.

POST /payments  Idempotency-Key: abc123
  → erster Aufruf: Operation läuft, Ergebnis wird gespeichert
  → zweiter Aufruf, gleicher Key: gespeichertes Ergebnis wird zurückgegeben, Operation läuft nicht erneut

Ebene 2: das Ereignis vom PSP

Der PSP kann dasselbe Ereignis (etwa „Capture erfolgreich“) aufgrund von Netzwerkproblemen oder seiner eigenen Retry-Richtlinie mehrfach senden. Jedes dieser Ereignisse trägt eine eindeutige Event-ID vom PSP. Haben Sie diese ID bereits gesehen, sollten Sie das Ereignis nicht erneut verarbeiten — müssen es dem PSP aber trotzdem explizit bestätigen (ACK).

Webhook #1: event_id=evt_001, type=payment.captured
Webhook #2: event_id=evt_001, type=payment.captured  (erneute Zustellung)
  → wurde die event_id schon gesehen, Verarbeitung überspringen, 200 OK zurückgeben

Ebene 3: Message-Queue / Inbox

Findet die Prüfung der Event-ID direkt im Code statt, der das Ereignis verarbeitet, riskieren Sie eine Race Condition, bei der zwei verschiedene Worker dasselbe Ereignis gleichzeitig verarbeiten. Deshalb gibt es das Inbox-Muster: Das Ereignis wird zuerst unter einer eindeutigen Einschränkung (Unique Constraint) in eine Inbox-Tabelle geschrieben; schlägt dieser Schreibvorgang fehl (existiert bereits), wurde das Ereignis bereits gesehen und die Verarbeitung wird sicher übersprungen.

INSERT INTO inbox (event_id, ...) VALUES ('evt_001', ...)
  → erfolgreich: erstmals gesehen, Arbeit wird eingereiht
  → Unique-Constraint-Fehler: bereits gesehen, wird stillschweigend übersprungen

Ebene 4: der Job selbst

Nach der Inbox wird die Arbeit an einen Hintergrundjob übergeben. Dieser Job kann selbst mehr als einmal eingereiht werden (ein Retry-Mechanismus, ein Redeploy). Die Job-Queue sollte einen zweiten Job mit demselben Job-Schlüssel (etwa payment_id + step_name) ablehnen.

Job-Schlüssel: payment_id=pay_42, step=finalize_stock
  → ein zweiter Job-Versuch mit demselben Schlüssel: wird auf Queue-Ebene abgelehnt

Ebene 5: der Saga-Schritt selbst

Selbst während der Job läuft, muss der Schritt selbst idempotent sein — denn der Job kann nach einem Absturz erneut starten. Diese letzte Ebene sind die Schritt-Marker aus Teil drei: Jeder Schritt markiert dauerhaft seinen eigenen Abschluss, und wird der Schritt erneut aufgerufen, prüft er diesen Marker, statt die eigentliche Arbeit (Bestand reduzieren, Ledger-Eintrag öffnen) zu wiederholen.

Schritt-Marker: finalize_stock=DONE (payment_id=pay_42)
  → wird der Schritt erneut aufgerufen, wird der Marker geprüft, die Arbeit wird nicht wiederholt

Warum alle fünf Ebenen getrennt nötig sind

Jede Ebene beantwortet eine andere Frage: Wer wiederholt hier — der Client, der PSP, die Queue, der Worker oder der Schritt selbst? Bauen Sie Idempotenz nur auf der API-Ebene und lassen die anderen vier aus, kann Ihre Finalisierungs-Saga „einmal in der API, aber dreimal im Hintergrund“ laufen — und Sie entdecken diesen Fehler meist erst in Produktion, oft über eine Kundenbeschwerde.

Die am häufigsten verwechselten Zuordnungen

❌ Einen Idempotency-Key-Header hinzuzufügen löst das Idempotenzproblem
✓ Dieser Header löst nur die Duplizierung auf der Client-API-Ebene

❌ Die Prüfung der PSP-Event-ID allein reicht aus
✓ Die Event-ID-Prüfung braucht eine Inbox / Unique Constraint gegen Race Conditions

❌ Liefert die Job-Queue At-least-once, wird der Job automatisch idempotent
✓ At-least-once-Zustellung plus ein nicht-idempotenter Job ist unsicher; der Job selbst muss idempotent sein

❌ Einen bereits erfolgreich abgeschlossenen Saga-Schritt erneut aufzurufen ist harmlos
✓ Ohne Schritt-Marker löst ein erneuter Aufruf die Nebenwirkung (Bestand, Zahlung) ein zweites Mal aus

Checkliste zur Prüfung Ihrer Idempotenz-Kette

  1. Hat Ihre API einen Idempotenzschlüssel? Wenn ja, wie lange bewahren Sie das gespeicherte Ergebnis auf?
  2. Prüfen Sie die Event-ID bei PSP-Webhooks, oder verarbeiten Sie jeden Webhook direkt?
  3. Hat Ihre Inbox-Tabelle einen Unique Constraint auf die Event-ID, oder erfolgt die Prüfung im Anwendungscode (Race-Condition-Risiko)?
  4. Lehnt Ihre Job-Queue einen zweiten Job mit demselben Job-Schlüssel ab?
  5. Prüft jeder Saga-Schritt einen Marker für seinen eigenen Abschluss, oder wiederholt er die Arbeit bei jedem Lauf von Neuem?

Fehlen zwei dieser fünf Ebenen, ist Ihr System wahrscheinlich anfällig für seltene, aber wiederkehrende Doppelverarbeitungsfehler.

Was aus diesem Teil bleiben sollte

  1. Idempotenz ist nicht ein einziger Header oder eine einzige Prüfung; sie ist eine Kette, die getrennt auf mindestens fünf unabhängigen Ebenen aufgebaut werden muss.
  2. Jede Ebene beantwortet eine andere Quelle von Wiederholungen (Client, PSP, Queue, Worker, Saga-Schritt); keine ersetzt die andere.
  3. Das Inbox-Muster ist eine strukturelle Lösung, die die Event-ID-Prüfung gegen Race Conditions abgesichert — nicht nur eine „if“-Prüfung.
  4. Unter einem At-least-once-Zustellmodell wird ein nicht-idempotenter Arbeitsschritt früher oder später zweimal ausgeführt.

Idempotenz auf einen einzigen Header zu reduzieren ist, als würde man einem fünfstöckigen Gebäude eine Tür geben und die Fenster aller anderen vier Stockwerke offen lassen.

FAQ

Häufige Fragen

Was ist API Idempotency Key?

Ein vom Client gesendeter eindeutiger Schlüssel, der garantiert, dass eine wiederholt gesendete Anfrage dasselbe Ergebnis liefert.

Was ist Gateway Event ID?

Eine eindeutige Kennung, die der PSP jedem Webhook oder jeder Benachrichtigung anfügt; unterscheidet mehrere Zustellungen desselben Ereignisses.

Stimmt es, dass „Einen Idempotency-Key-Header hinzuzufügen löst das Idempotenzproblem“?

Dieser Header löst nur die Duplizierung auf der Client-API-Ebene

Was legt dieser Teil fest?

Dieser Teil erklärt, wie Sie Idempotenz getrennt auf jeder dieser fünf Ebenen aufbauen — nicht nur an einer Stelle. Idempotenz ist nicht ein einziger Header oder eine einzige Prüfung; sie ist eine Kette, die getrennt auf mindestens fünf unabhängigen Ebenen aufgebaut werden muss. Die meisten Teams lernen Idempotenz als „einen `Idempotency-Key`-Header zur API-Anfrage hinzufügen“ und hören dort auf. In Zahlungssystemen ist das nur die sichtbare Spitze des Eisbergs. Dieselbe Operation kann Ihr System von mindestens fünf verschiedenen Stellen als „Wiederholung“ durchlaufen: Retry des Clients, erneute Zustellung des PSP-Webhooks, At-least-once-Zustellung des Message-Brokers, erneutes Abholen des Jobs durch den Worker, erneutes Laufen des Saga-Schritts.

Gelernte Engineering-Prinzipien

  • Idempotenz ist kein einziger Header; sie ist eine Kette, die getrennt auf den Ebenen API, Gateway, Inbox, Job und Saga-Schritt aufgebaut werden muss.
  • Jede Ebene beantwortet eine andere Quelle von Wiederholungen; eine aufzubauen und die anderen auszulassen lässt das System halb geschützt.
  • Unter einem At-least-once-Zustellmodell wird ein nicht-idempotenter Schritt früher oder später garantiert zweimal ausgeführt.

Weiterlesen

Weiterlesen

Nachster Teil der Serie

Nachster Teil der Serie

Aus derselben Serie

Paylaş