info@yenlo.com
deu
Menu
API Management 6 min

So entwerfen Sie Ihre ReST-APIs für eine „Exactly Once“-Verarbeitung

Wie können Sie REST-APIs entwerfen, die zuverlässig sind, Fehler effektiv handhaben und doppelte Verarbeitung verhindern? In diesem Blog befassen wir uns mit der Stärke von idempotenten Operationen und untersuchen Strategien, um das API-Design zu vereinfachen und gleichzeitig eine genaue Einmalverarbeitung zu gewährleisten. Entdecken Sie, wie häufige Fallstricke bei POST-Anfragen die Zuverlässigkeit beeinträchtigen können, und erfahren Sie, warum PUT in Kombination mit bedingten Headern Ihren Ansatz für die API-Architektur revolutionieren kann.

Hans Bot
Hans Bot
Senior Solution Architect
So entwerfen Sie Ihre ReST APIs für eine Exactly Once'' Verarbeit

Kürzlich bin ich auf eine interessante Diskussion auf Erik Wildes exzellentem YouTube-Kanal „Getting APIs to Work“ gestoßen. Es bietet einen kleinen, aber wertvollen Einblick in das Design von REST-APIs. Meiner Meinung nach verdient diese RESTful-API-Idee ein größeres Publikum. Hier ist also, was ich daraus mitgenommen habe.

Die Idempotenz von APIs ist eines dieser Themen, von denen man weiß, dass sie wichtig sind, aber irgendwie nie wichtig genug erscheinen, um es in ein Minimum Viable Product zu schaffen. Und raten Sie mal? In der Regel schafft es auch nicht in die nächste Sprint-Planung. Das Bedürfnis, dieses Thema anzugehen, wird einfach nie dringend genug.

Für viele Architekten ist die „Exactly Once“-Verarbeitung ein Grund, ereignisgesteuerte Architekturen zu fördern. Hier sorgt der Broker dafür, dass jede Nachricht zugestellt wird. Und sobald Sie einen transaktionalen Client implementieren, sind Sie der „Exactly Once“-Verarbeitung bereits sehr nahe. Sie müssen nur sicherstellen, dass Ihr Zielsystem ein RPO=0 aufweist oder gegen Wiederholungen abgesichert ist. Aus Sicht des Architekten ist das einfach. Zum Glück gibt es einen noch einfacheren Weg, und zwar so:

Was ist die Kernidee?

Die Kernidee ist verblüffend einfach. Wenn Sie nur idempotente REST-API-Operationen verwenden, können Sie diese wiederholen, bis sie erfolgreich sind. Und falls Sie sie auch nach einem erfolgreichen Abschluss erneut ausführen, weil die Bestätigung vielleicht irgendwo verloren ging, hat das einfach keinen Einfluss auf den Zustand der Ressource (das „Ding“ hinter der URL – ein Uniform Resource Locator).

Wenn Sie eine REST-API entwerfen, können Sie aus vier Hauptoperationen wählen: POST, PUT, GET und DELETE. Von diesen Methoden ist POST die einzige, die von Natur aus nicht idempotent ist. Wenn Sie dieselbe Darstellung eines Objekts zweimal posten, fügen Sie Ihrer Datensammlung zwei Ressourcen hinzu. So verhält es sich (vorausgesetzt, Sie POSTen in eine Sammlung, wie es in REST empfohlen wird). Manchmal ist das genau das, was Sie wollen, aber häufiger ist es ein unglücklicher Nebeneffekt von schlechtem Fehlerhandling oder schlechtem UI-Design. Und genau deshalb sollten Sie erwägen, die Verwendung von POST vollständig zu verbieten.

Zu radikale Idee? Nicht umsetzbar? Nun, es ist bei weitem die einfachste Implementierung, um vollständige Idempotenz zu erreichen, die ich je gesehen habe. Lesen Sie weiter, um zu erfahren, warum.

Welches Problem wollen wir lösen?

Im Internet gehen ständig Dinge schief. Es kommt vor, dass Sie eine POST-Anfrage senden, aber nie eine Antwort erhalten. Das könnte bedeuten, dass der Ressourcenserver Ihre Anfrage nie verarbeitet hat, in welchem Fall Sie sie erneut senden möchten. Es ist aber auch möglich, dass die Anfrage verarbeitet wurde, die Antwort jedoch verloren ging. In diesem Fall möchten Sie die Anfrage nicht erneut senden, da dies zu einem Duplikat führen würde. Und wir alle wissen, wie schmerzhaft es sein kann, diese sicher zu deduplizieren.

Aber wie findet man heraus, was tatsächlich passiert ist? Sie könnten die HEAD-Methode ausprobieren, aber dafür benötigen Sie den Bezeichner der Ressource, die möglicherweise erstellt wurde. Bei einem POST ist in der Regel der Ressourcenserver für die Vergabe dieses Bezeichners zuständig. Sie finden ihn in der Antwortnachricht, die Sie nie erhalten haben. Sie sitzen also ziemlich fest und müssten raten.

wp advanced api management guide
Whitepaper Leitfaden Für Die Erweiterte API-Verwaltung

Empfehlungen, Um Ihnen Zu Helfen, Ihre Eigene Architektur Zu Entwerfen Und Dabei Eine Auswahl Zu Treffen

Jetzt herunterladen

Was ist die Alternative?

Offensichtlich können Sie stattdessen einen PUT verwenden. Sie können ein Objekt so oft PUTten, wie Sie möchten, es wird maximal eine Ressource erstellt. Sie nehmen einfach eine zufällige GUID als Bezeichner der Ressource, die Sie PUTten. Das ist ohnehin eine gute Praxis. Sie sollten keine leicht erratbaren Bezeichner wie eine Sequenz, eine E-Mail-Adresse oder eine Sozialversicherungsnummer verwenden, erinnern Sie sich? Und ob der Client oder der Server die GUID ausgibt, spielt keine Rolle, oder?

Aber das bringt auch einige Nachteile mit sich. Ein Randfall ist Regression. Wenn man darüber nachdenkt, entspricht ein POST einem INSERT in der Datensammlung, während ein PUT einem UPSERT entspricht. Angenommen, Sie PUTten eine neue Bestellung, aber die Antwort ging verloren. Während Sie auf ein Timeout warten, wurden eine oder mehrere Updates verarbeitet. Der Bestand wurde aktualisiert und eine Zahlungsaufforderung ausgegeben. Jetzt setzen Sie den Zustand der Ressource wieder auf den ursprünglichen Stand zurück. Schlechte Dinge werden sicherlich passieren. Aber es stellt sich heraus, dass Sie das völlig verhindern können.

Das Geheimnis dieses Musters liegt in der Verwendung bedingter Anfragen (siehe RFC 7232 für Details). Mithilfe dieses API-Designtricks wird die HTTP-Methode nur dann ausgeführt, wenn eine Bedingung erfüllt ist. Es wird oft in Verbindung mit einem E-Tag oder Entity-Tag als Signatur einer Antwort verwendet. Da Sie das Objekt einmal PUTten, aber keine Bestätigung erhalten haben, möchten Sie das Objekt nun PUTten, und zwar nur, wenn die Ressource noch nicht existiert. Es stellt sich heraus, dass es einen einfachen Weg gibt, dies zu tun, ohne den vom Server ausgegebenen E-Tag-Wert zu kennen. Fügen Sie einfach den folgenden Header zu Ihrem PUT hinzu, und Sie können es so oft wie nötig erneut versuchen:

If-None-Match „*“

Diese Bedingung verwandelt eine PUT-Anfrage effektiv von einem UPSERT in ein idempotentes INSERT. Sie signalisiert, dass die Ressource nur PUT wird, wenn nichts zu übereinstimmen ist. Mit anderen Worten, wenn die Ressource noch nicht existiert. Ein leerer E-Tag, wenn Sie so wollen. Andernfalls wird ein Statuscode HTTP:304 (Not Modified) zurückgegeben. Es wird nichts aktualisiert. Genial.

Gibt es einen Haken?

Nicht wirklich, zumindest nicht, soweit ich es überblicken kann. Natürlich gibt es die geringe Chance, dass bereits eine andere Ressource mit derselben GUID in Ihrem Benutzerstore existiert. Aber das kann leicht mit demselben Trick umgangen werden. Wenn Sie denselben Header zur ersten PUT-Anfrage hinzufügen, würde ein 304 beim ersten Versuch anzeigen, dass die von Ihnen generierte GUID tatsächlich nicht eindeutig ist. Presto!

Es bleibt nur die winzige Chance, dass das 304 bei der ursprünglichen Anfrage verloren geht und Sie das 304 beim erneuten Versuch fälschlicherweise als Erfolg interpretieren. Wenn das ein Problem darstellt, können Sie vor Ihrem ersten PUT immer eine HEAD-Anfrage senden. Wenn Sie eine HTTP:200 erhalten, ist es Ihr Glückstag. Ziehen Sie den Kauf eines Lottoscheins in Betracht, aber vergessen Sie nicht, vor dem erneuten PUTten Ihrer Ressource eine andere GUID zu generieren.

Gibt es noch einen anderen Weg?

Im Web gibt es immer einen anderen Weg. In diesem Fall, wenn Sie wirklich an der Verwendung von POST anstelle von PUT hängen, wenn Sie eine Ressource hinzufügen, ziehen Sie Folgendes in Betracht: Es gibt nichts in der HTTP-Spezifikation, das Sie daran hindert, an eine identifizierte Ressource zu POSTen, anstatt an eine Sammlung. Das POSTen zu einer Sammlung ist nur eine Praxis im RESTful-API-Design. Und dieser Bezeichner könnte auch eine clientseitig generierte GUID sein. Sie würden bei einem Konflikt eine HTTP:409 erhalten und diesen genauso behandeln, wie Sie es mit dem 304 tun würden. Aber es würde niemals, niemals etwas überschreiben.

Der ereignisgesteuerte Ansatz, den ich in der Einleitung erwähnt habe, könnte ebenfalls funktionieren. Sie könnten die Anwendung von CQRS in Betracht ziehen. Hier gibt es einen Broker zwischen der Clientanwendung und dem Backend. Sobald die Erstellungsanforderung bestätigt wurde, garantiert der Broker die Lieferung. Mit anderen Worten, der Client muss nicht warten, sondern erhält seine Bestätigung fast sofort. Sie würden wahrscheinlich eine AsyncAPI verwenden, um ein Erstellungsevent an den Broker zu veröffentlichen, unter Verwendung einer CloudEvents-Beschreibung. Der Ressourcenbezeichner könnte sowohl im Client als auch im Backend generiert werden. Das Backend würde seine Antwort entweder an den Antwortkanal senden, den Ihr Client abonniert hat, oder als direkten Callback, wenn der Client einen Webhook angegeben hat. Klingt kompliziert? Nicht so sehr, zumindest wenn Sie bereits in die Ereignisfähigkeit investiert haben. Aber die idempotente REST-API gewinnt definitiv den Einfachheitswettbewerb.

Was soll man wählen?

Es gibt mehrere Optionen mit unterschiedlichen Vor- und Nachteilen. Als Integrationsarchitekt besteht meine tägliche Arbeit darin, die Vor- und Nachteile mehrerer architektonischer Alternativen, sowohl großer als auch kleiner, zu analysieren. Ich kann Ihnen helfen, eine bewusste Wahl zu treffen. Darüber hinaus stelle ich sicher, dass die gewählte Option bei den Stakeholdern Akzeptanz findet. Sie können uns gerne kontaktieren.

Sind Sie bereit, Ihren POST hinter sich zu lassen? Bevorzugen Sie die idempotente Verwendung von POST? Oder akzeptieren Sie faul die Anfälligkeit Ihres POST?

deu
Schließen
Was ist auf unserer Speisekarte