Asynchrone API-Anfragen in Java über CompletableFutures
Lesezeit: 3 Minuten
Java 8 wurde 2014 veröffentlicht, und mit der Veröffentlichung wurden zahlreiche neue Sprachfunktionen wie Lambda-Ausdrücke und die Streams-API eingeführt. Seit dem Jahr 2014 hat sich allerdings viel verändert: Java ist mittlerweile schon bei Version 15 angelangt, aber wie Branchenumfragen zeigen, wird Version 8 immer noch am häufigsten verwendet. Nur einige wenige Entwickler nutzen Version 7 oder älter.
Im Oktober dieses Jahres wurde mit Version 8.0.0 die Twilio Java-Hilfebibliothek für die Verwendung von Java 8-Funktionen aktualisiert. Diese neue Hauptversion spiegelt die Tatsache wider, dass die Bibliothek keine Unterstützung mehr für Java 7 bietet.
Eine Java 8-API, die manchmal übersehen wird, ist die CompletionStage-API, auf die normalerweise über die CompletableFuture-Klasse zugegriffen wird. Mit der CompletionStage-API können Programmierer Pipelines für asynchrone Datenvorgänge definieren. Die API übernimmt dabei auch die Verarbeitung des asynchronen Verhaltens. Wir definieren damit also, was geschehen soll, und Java kümmert sich darum, wann das gewünschte Ereignis eintreten kann.
In diesem Beitrag erkläre ich, wie wir CompletableFutures mit der neuen Twilio-Hilfebibliothek verwenden können. Das gleiche Prinzip lässt sich auch auf jeden anderen asynchronen Code übertragen. Der HttpClient
von Java 11 verfügt beispielsweise über asynchrone Methoden, die CompletableFuture
-Instanzen zurückgeben.
Synchroner und asynchroner Code
Wenn wir die Twilio-API aufrufen, um eine SMS zu senden, sieht der Code in etwa so aus:
[vollständiger Code auf GitHub]
Dieser Code ruft die Twilio-API auf, reiht die SMS in die Warteschlange ein und gibt ein Message
-Objekt zurück, das Informationen zur Antwort einschließlich die SID der Nachricht enthält. Diese SID kann später verwendet werden, um nachzuschlagen, was mit der Nachricht passiert ist. Die tatsächliche Arbeit findet im Hintergrund statt, wie zum Beispiel das Senden von Anfrage und Antwort über das Internet. Auf meinem Computer dauert das Ausführen dieses Aufrufs etwa eine Sekunde, und dieser Code wartet, bis die Message
verfügbar ist, bevor fortgefahren wird. Das nennen wir einen synchronen (oder „blockierenden“) Aufruf.
Während die API-Anfrage jedoch in Bearbeitung ist, könnte unser Code andere Aufgaben ausführen. Wie wäre es also, wenn wir den Aufruf asynchron machen? Das würde bedeuten, dass unser Code weiterarbeiten könnte und wir die Message
einfach später abrufen, wenn wir sie wirklich brauchen. Um das zu erreichen, ersetzen wir einfach .create()
durch .createAsync()
.
Futures
Die Methode .createAsync()
gibt eine Future
zurück. Ähnlich wie bei Promises in anderen Sprachen handelt es sich bei „Futures“ um Objekte, die ein Ergebnis enthalten, sobald es bereitsteht. Die Arbeit findet in einem Hintergrundthread statt, und wenn wir das Ergebnis brauchen, können wir eine Methode am Future aufrufen, um das Ergebnis abzurufen. Wenn wir diese Methode aufrufen, müssen wir möglicherweise noch warten, aber der Code konnte in der Zwischenzeit andere Aufgaben ausführen.
Ab Version 8.0.0 der Twilio-Hilfebibliothek ist der zurückgegebene „Future“-Typ eine CompletableFuture
. Das Ergebnis wird bei diesem Typ mit der Methode .join()
abgerufen. Der Code könnte in etwa so aussehen:
So weit, so gut. Aber was die CompletionStage-API so besonders macht, ist die Tatsache, dass wir Code-Pipelines erstellen können. Bei diesen Pipelines wird jede Stufe dann ausgeführt, sobald sie bereit ist, ohne dass wir jedes kleine Detail des asynchronen Verhaltens selbst codieren müssen. Das ähnelt zwar der Verwendung von Rückrufen in anderen Sprachen, ist aber bei Weitem flexibler, wie wir anhand von Beispielen noch sehen werden.
Beispiele
Zugegeben, diese Beschreibung mag etwas komplex klingen. Deshalb hier ein paar Beispiele zur Veranschaulichung:
Sequentielle Verkettung der Berechnung
Mit .thenApply()
können wir nach dem Abschluss des API-Aufrufs Code ausführen. Die Methode .thenApply()
benötigt einen Lambda-Ausdruck oder einen Methodenverweis, der den Wert umwandelt und eine weitere CompletionStage zurückgibt. Dadurch können wir mehrere Aufgaben bei Bedarf miteinander verketten. Wenn wir das Endergebnis abrufen möchten, können wir erneut .join()
aufrufen:
[vollständiger Code auf GitHub]
Parallele Ausführung
Erweitern wir unser vorhergehendes Beispiel. Angenommen, wir müssen mehrere API-Anfragen stellen. Diese sind nicht voneinander abhängig, deshalb spielt die Reihenfolge, in der sie ausgeführt werden, keine Rolle. Allerdings müssen wir wissen, wann alle Anfragen abgeschlossen wurden, um z. B. die Ereignisse in eine Datenbank zu schreiben.
Mit CompletableFuture.allOf()
können wir planen, dass Code nach dem Abschluss mehrerer CompletionStages ausgeführt wird. Der Lambda-Ausdruck, den wir an .allOf()
übergeben, benötigt keine Argumente. Wir verwenden .join()
im Text des Lambda-Ausdrucks, um die Ergebnisse jeder Stufe abzurufen:
[vollständiger Code auf GitHub]
Fehlerbehandlung bei CompletionStages
Falls Ausnahmen im asynchronen Code ausgelöst werden, werden diese von der CompletionStage-API aufgegriffen. Für die Behandlung dieser Ausnahmen haben wir mehrere Möglichkeiten. Wenn wir sie überhaupt nicht behandeln, dann könnte durch den Aufruf der Methode .join()
eine CompletionException
ausgelöst werden, die die ursprüngliche Ausnahme als Ursache enthält.
Eine bessere Möglichkeit der Wiederherstellung ist die Verwendung der Methode .handle()
. Dabei stellen wir einen Lambda-Ausdruck bereit, der zwei Argumente benötigt: ein Ergebnis und eine Ausnahme. Wenn die Ausnahme ungleich Null ist, können wir sie hier behandeln. .handle()
gibt eine CompletableFuture zurück. Wir können also mit der Verkettung fortfahren oder mit .join()
das Ergebnis abrufen:
[vollständiger Code auf GitHub]
Die vollständige CompletionStage-API
Diese kurzen Beispiele kratzen nur an der Oberfläche der CompletionStage-API.
Es gibt Dutzende von Methoden, um asynchrone Aktionen auf verschiedene Weise miteinander zu verketten oder zu kombinieren.
Weitere Beispiele zu den Verwendungsmöglichkeiten von CompletableFutures findest du in der offiziellen Dokumentation oder in dieser praktischen Liste mit 20 Beispielen.
Zusammenfassung
Die CompletionStage-API von Java 8 gibt uns Java-Entwicklern leistungsstarke Tools an die Hand, um komplexe asynchrone Vorgänge zu definieren, und ist nur eine der zahlreichen Ergänzungen in der neuen Twilio-Java-Hilfebibliothek.
Wenn du Twilio und Java verwendest, würde ich eine Aktualisierung auf die neue Hilfebibliothek empfehlen. Und wenn du mit Twilio und Java entwickelst, würde ich mich freuen, davon zu hören. Ich bin gespannt, von deinen Entwicklungen zu hören.
Verwandte Posts
Ähnliche Ressourcen
Twilio Docs
Von APIs über SDKs bis hin zu Beispiel-Apps
API-Referenzdokumentation, SDKs, Hilfsbibliotheken, Schnellstarts und Tutorials für Ihre Sprache und Plattform.
Ressourcen-Center
Die neuesten E-Books, Branchenberichte und Webinare
Lernen Sie von Customer-Engagement-Experten, um Ihre eigene Kommunikation zu verbessern.
Ahoy
Twilios Entwickler-Community-Hub
Best Practices, Codebeispiele und Inspiration zum Aufbau von Kommunikations- und digitalen Interaktionserlebnissen.