Zum Inhalt springen
← Impulse

Idempotenz in APIs: Duplikate vermeiden, ohne Fehler zu verschleiern

Ein Idempotenzschlüssel ermöglicht sichere Wiederholungen sensibler Vorgänge, ohne ihre Auswirkungen zu duplizieren. Erfahre, wie du Geltungsbereich, Parallelzugriffe und Fehler behandelst.

Diagramm einer API, die Wiederholungen mithilfe eines Idempotenzschlüssels erkennt und doppelte Vorgangsauswirkungen verhindert.

Eine Anfrage kann den Server erreichen, ohne dass der Client die Antwort erhält. Vielleicht läuft die Anfrage in einen Timeout, nachdem eine Zahlung verbucht oder eine Bestellung angelegt wurde. Wiederholt der Client den Aufruf blind, führt das System den Vorgang möglicherweise ein zweites Mal aus. Idempotenz in APIs hilft, das zu vermeiden: Sie ermöglicht es, mehrere Anfragen demselben Geschäftsvorgang zuzuordnen und doppelte Auswirkungen zu begrenzen.

Eine Kopfzeile mit einem Schlüssel hinzuzufügen, reicht jedoch nicht aus. Es muss feststehen, wer den Schlüssel erzeugt, wie lange er gültig ist, welche Antwort wiederholt wird und wie lokale Schreibvorgänge mit externen Diensten abgestimmt werden. Ziel ist weder, Fehler zu verschleiern, noch eine exakt einmalige Ausführung zu versprechen. Vielmehr sollen Wiederholungen unter klar definierten Bedingungen sicher sein.

Was Idempotenz bedeutet und welches Problem sie löst

Was Idempotenz bedeutet und welches Problem sie löst

Ein Vorgang ist idempotent, wenn eine Wiederholung mit denselben Daten das Endergebnis nach der ersten Ausführung nicht mehr verändert. Eine Versandadresse für eine Bestellung auf einen bestimmten Wert zu setzen, kann beispielsweise idempotent sein: Dieselbe Aktualisierung zweimal anzuwenden, lässt die Ressource im selben Zustand wie eine einmalige Anwendung.

Ein Vorgang wie „eine Einheit zum Warenkorb hinzufügen“ ist dagegen nicht von sich aus idempotent: Eine Wiederholung kann die Menge zweimal erhöhen. Auch das Anlegen einer Bestellung oder das Verbuchen einer Belastung kann zusätzliche Auswirkungen haben, wenn jede Anfrage als neue Absicht behandelt wird. Bei solchen Vorgängen kann ein Idempotenzschlüssel Wiederholungen demselben logischen Versuch zuordnen.

Idempotenz bedeutet weder, dass alle Antworten identisch sind, noch, dass eine Anfrage niemals fehlschlägt. Sie bedeutet, dass die Auswirkungen von Wiederholungen gemäß einem Vertrag kontrolliert werden. Ein Client kann einen Fehler erhalten und bei einem erneuten Versuch das bereits gespeicherte Ergebnis bekommen; ebenso kann er erneut einen endgültigen Fehler erhalten. Entscheidend ist, dass der Server nicht unbeabsichtigt eine zweite Wirkung ausführt.

Wann Idempotenz sinnvoll ist – und wann nicht

Ein Risiko entsteht, wenn ein Vorgang relevante Auswirkungen hat und der Client nicht sicher erkennen kann, ob der Server ihn abgeschlossen hat. Eine verlorene Antwort, ein Timeout, eine Verbindungsunterbrechung oder ein automatischer Wiederholungsversuch können das Ergebnis unklar lassen. Besonders kritisch ist das beim Anlegen von Bestellungen, beim Starten von Zahlungen, beim Reservieren von Lagerbestand oder beim Bearbeiten von Anträgen.

Prüfe zunächst den bestehenden Vertrag, bevor du Schlüssel einführst. Eine Aktualisierung, die einen konkreten Zustand setzt, kann bereits durch ihr Design idempotent sein. Eine reine Leseabfrage verursacht normalerweise nicht dasselbe Problem. Ein Endpunkt, der bei jedem Aufruf eine neue Ressource oder Buchung erzeugt, braucht dagegen eine klare Strategie, wenn seine Aufrufer Anfragen wiederholen können.

Idempotenz verursacht zusätzlichen Aufwand: Schlüssel und Ergebnisse müssen gespeichert werden, es braucht Ablaufregeln, eine Behandlung paralleler Zugriffe und weitere Tests. Führe sie nicht unterschiedslos für jeden Endpunkt ein. Setze sie vorrangig dort ein, wo drei Anzeichen zusammenkommen:

  • Der Vorgang kann eine doppelte Auswirkung mit erheblichen oder schwer umkehrbaren Folgen haben.
  • Der Client oder die Infrastruktur wiederholt Anfragen bei vorübergehenden Fehlern.
  • Eine verlorene Antwort macht unklar, ob der Vorgang abgeschlossen wurde.

Lege außerdem fest, was im Geschäftsprozess als „derselbe Versuch“ gilt. Zwei absichtlich getätigte, identische Käufe dürfen nicht allein deshalb verwechselt werden, weil Betrag und Inhalt übereinstimmen. Die Duplikaterkennung muss auf einem Versuchsschlüssel beruhen, nicht auf einer Annahme über die Ähnlichkeit der Anfragen.

Einen Schlüssel entwerfen: Herkunft, Eindeutigkeit, Geltungsbereich und Dauer

Üblicherweise erzeugt der Client für jeden logischen Vorgang einen eindeutigen Schlüssel und verwendet ihn bei allen Wiederholungen erneut. Er kann ihn in einem vereinbarten Header oder im Anfragekörper übermitteln, sofern der Vertrag eindeutig ist. Erzeugt der Client bei jedem Wiederholungsversuch einen neuen Schlüssel, kann der Server die Anfragen nicht zuordnen. Verwendet er einen Schlüssel für einen neuen Kauf erneut, könnte der Server eine legitime Absicht blockieren.

Der Schlüssel kennzeichnet den Versuch, ersetzt jedoch weder Authentifizierung noch Autorisierung. Er muss einem Geltungsbereich zugeordnet sein, etwa dem authentifizierten Konto oder Händler und der Art des Vorgangs. So wird verhindert, dass eine zufällige Übereinstimmung zwischen zwei Clients ihre Ergebnisse vermischt. Der Server muss diese Grenzen bei jeder Anfrage prüfen.

Speichere außerdem einen Fingerabdruck der normalisierten Anfrage: die Felder, die die Auswirkungen bestimmen, mit stabilen Regeln für Standardwerte und Darstellung. Existiert ein Schlüssel bereits und trifft eine Anfrage mit abweichendem Inhalt ein, lehne sie als Konflikt ab. Behandle sie weder als gültige Wiederholung noch führe den neuen Inhalt aus. Der Fingerabdruck sollte irrelevante Daten auslassen, aber keine Angaben, die die Absicht verändern, etwa Betrag oder Währung.

Die Gültigkeitsdauer hängt vom Verhalten der Clients, Warteschlangen und Wiederherstellungsprozesse ab. Ein zu kurzes Zeitfenster ermöglicht, dass ein später Wiederholungsversuch die Auswirkungen erneut auslöst. Ein zu langes Zeitfenster häuft Datensätze an und kann legitime Wiederverwendung verhindern. Lege einen Zeitraum fest, der zur maximal sinnvollen Wiederholungsdauer passt, und kommuniziere, was danach geschieht. Bei Finanzvorgängen oder Vorgängen mit großen Auswirkungen kann es nötig sein, eine dauerhafte Ergebnisreferenz über das operative Zeitfenster hinaus aufzubewahren.

Antworten bei wiederverwendeten Schlüsseln und laufenden Anfragen

Der Vertrag sollte mehrere Fälle unterscheiden. Ist die Verarbeitung des Schlüssels abgeschlossen und stimmt der Fingerabdruck überein, kann der Server das gespeicherte Ergebnis des ursprünglichen Vorgangs zurückgeben. Dazu gehören üblicherweise der relevante Statuscode und Antwortkörper, aber nicht zwingend jeder Transport-Header. Die Antwort sollte dem Client ermöglichen, die angelegte Ressource oder den erreichten Zustand zu erkennen.

Wird der Vorgang mit diesem Schlüssel gerade verarbeitet, darf keine zweite Ausführung gestartet werden. Der Server kann einen Status zurückgeben, der die laufende Verarbeitung anzeigt, oder einen vorübergehenden Konflikt, der zum späteren Abfragen oder Wiederholen auffordert. Der Client braucht eine klare Regel: wie lange er warten soll, ob er denselben Schlüssel beibehalten muss und wie er das endgültige Ergebnis erhält. Eine noch nicht bestätigte Ausführung darf nicht als erfolgreich gemeldet werden.

Existiert der Schlüssel mit einem abweichenden Fingerabdruck, gib einen eindeutigen Fehler zurück und ändere den ursprünglichen Eintrag nicht. Ist die erste Ausführung fehlgeschlagen, muss festgelegt werden, welche Fehler als endgültiges Ergebnis gespeichert werden und welche eine erneute Verarbeitung erlauben. Ein Validierungsfehler kann beispielsweise für diese Anfrage endgültig sein, während eine Unterbrechung vor der Bestätigung von Auswirkungen eine Wiederherstellung zulassen kann. Eine allgemeingültige Regel gibt es nicht: Sie muss dem Punkt entsprechen, an dem das System nachweisen kann, was geschehen ist.

Persistenz, Parallelzugriffe und Auswirkungen in anderen Systemen

Wenn die Schlüsselreservierung und die Erzeugung der lokalen Wirkung dieselbe Datenbank verwenden, sollten beide atomar koordiniert werden. Eine Eindeutigkeitsbedingung für Geltungsbereich und Schlüssel hilft zu verhindern, dass zwei gleichzeitige Anfragen dieselbe anfängliche Prüfung bestehen. Die Logik muss eine Kollision behandeln und den von der erfolgreichen Anfrage erstellten Zustand auslesen, statt sich allein auf die Abfolge „erst suchen, dann einfügen“ zu verlassen.

Speichere verständliche Zustände, zum Beispiel „in Bearbeitung“, „abgeschlossen“ und „wiederherstellbar fehlgeschlagen“ oder „endgültig fehlgeschlagen“. Ergänze Zeitstempel und eine Wiederherstellungsregel für Datensätze, deren Verarbeitung unterbrochen wurde. Eine Sperre, die nie abläuft, kann Vorgänge festsetzen; eine unkontrolliert ablaufende Sperre kann dazu führen, dass zwei Worker gleichzeitig handeln. Vor einer Wiederaufnahme muss die Wiederherstellung den Zustand der bereits erfolgten Auswirkungen prüfen.

Eine lokale Transaktion umfasst nicht automatisch einen Zahlungsanbieter oder einen anderen entfernten Dienst. Speichert das System die Bestellung und scheitert anschließend vor dem Aufruf des Anbieters, oder verarbeitet der Anbieter die Zahlung und die Antwort geht verloren, müssen die Zustände abgeglichen werden. Nutze, wenn passend, eine transaktionale Outbox, um Arbeit erst nach Bestätigung der lokalen Änderung zu veröffentlichen, und übermittle eine stabile Referenz an das externe System, sofern es Duplikaterkennung unterstützt. Speichere externe Kennungen und berücksichtige Abfragen oder Abstimmungen.

Versprich keine durchgängige „Exactly-once“-Ausführung, nur weil ein Schlüssel existiert. Zwischen Netzwerken, Datenbanken und Anbietern können mehrdeutige Fehler auftreten. Die tatsächliche Garantie muss beschreiben, welche Auswirkungen in welchem Geltungsbereich und für welche Dauer dedupliziert werden und welche Fälle einen Eingriff oder Abgleich erfordern.

Häufige Fehler und Checkliste

Häufige Fehler und Checkliste
  • Neuer Schlüssel bei jedem Wiederholungsversuch: Der Client muss den Schlüssel des ursprünglichen Versuchs speichern und erneut verwenden.
  • Ein globaler Schlüssel ohne Geltungsbereich: Verknüpfe ihn mit dem authentifizierten Client und dem betreffenden Vorgang.
  • Abweichender Inhalt beim selben Schlüssel: Vergleiche einen Fingerabdruck und lehne eine unvereinbare Wiederverwendung ab.
  • Ablauf ohne Prüfung später Wiederholungen: Dokumentiere das Zeitfenster und lege fest, wie ältere Vorgänge wiederhergestellt werden.
  • Mehrdeutige Antwort bei paralleler Verarbeitung: Gib an, wie der Client abfragt oder wiederholt, solange die erste Anfrage noch aktiv ist.
  • Vertrauen darauf, dass der Schlüssel externe Systeme abdeckt: Ergänze Referenzen, Abgleich und den Umgang mit Teilausfällen.

Prüfe vor der Veröffentlichung des Endpunkts, ob der Client für jede Absicht einen Schlüssel erzeugt und ihn nach einem Timeout beibehält; ob zwei gleichzeitige Anfragen mit demselben Schlüssel die Wirkung nicht duplizieren; und ob derselbe Schlüssel mit abweichenden Daten keinen neuen Vorgang ausführt. Teste außerdem Fehler vor und nach dem Schreibvorgang, Prozessneustarts, den Ablauf des Schlüssels und verlorene Antworten.

Beobachte schließlich Kennzahlen zu wiederholten Schlüsseln, Fingerabdruckkonflikten, festhängenden Vorgängen und Abweichungen bei externen Diensten. Diese Signale helfen, Integrationsfehler zu erkennen und das Aufbewahrungsfenster anzupassen. Eine nützliche Implementierung beseitigt Fehler nicht. Sie macht deutlich, was ohne doppelte Auswirkungen wiederholt werden kann, und bietet einen sicheren Weg, verbleibende Unsicherheiten zu klären.

Fuentes y referencias

  1. Web standardsW3C
  2. OWASP Cheat Sheet SeriesOWASP Foundation
  3. Web performanceweb.dev