Microservices mit Kotlin, Vert.x und OpenAPI, Teil 1

Das Problem zur Lösung

Der Artikel besteht aus zwei Teilen. Er beginnt mit der Vorstellung unseres Anwendungsfalles und der für die Implementierung ausgewählten Technologien. Des Weiteren wird die zu implementierende REST-Schnittstelle im Detail beschrieben. Im zweiten Teil wird die eigentliche Implementierung des Microservice erläutert.

Wir beginnen also mit einem typischen Anwendungsfall eines Remote-Proxys. Ein IT-System soll Daten nutzen, die ein externer, nicht unter der eigenen Kontrolle stehender Service liefert. Der Remote-Proxy soll systemfremde Daten sowie das Verhalten des externen Service so anpassen, dass die eigenen Dienste damit einfach interagieren können.

  • Fremde Daten sollen in das heimische Domänenmodell konvertiert werden.
  • Die Authentifizierung gegen den externen Service erledigt der Remote-Proxy, ohne dass sich die Dienste darum kümmern müssen.
  • Der Remote-Proxy kann einen Cache unterhalten, um Anfragen schneller zu bedienen. Dadurch lassen sich technische Störungen durch langsame Antworten des fremden Service vermindern.
  • Bei einem Ausfall des fremden Service kann der Proxy Anfragen aus seinem Cache bedienen oder Anfragen mit einem vordefinierten Fehlercode beantworten. Darauf können heimische Services entsprechend reagieren. Der Proxy kann also im Sinne des CircuitBreaker-Patterns agieren. Somit lassen sich Auswirkungen externer Störungen auf das IT-System vermindern.

Weiterhin sollen Microservices eine möglichst einfache Schnittstelle für ihre Konsumenten bereitstellen. Hier hat sich als Quasistandard das REST-Paradigma etabliert. Schnittstellen lassen sich damit programmiersprachenunabhängig beschreiben und realisieren.

Der Artikel zeigt nun, wie Entwickler mit diesen Anforderungen einen Remote-Proxy für eine externe REST-API bauen können. Als externe API dient in diesem Fall die frei verfügbare Astronomy Picture of the Day API der amerikanischen Weltraumagentur NASA. Es handelt sich dabei um eine REST-Schnittstelle, über die sich die Texte und Bild-URLs der Webseite "Astronomy Picture of the Day" anhand des Veröffentlichungsdatums abrufen lassen. An die NASA-API ist pro Bild eine Anfrage zu stellen, als Anfrageparameter wird ein Datums-String angegeben. Der Proxy soll es also ermöglichen, dass sich Anfragen für Daten über Astronomiebilder an ihn stellen lassen. Er liefert die gewünschten Bilddaten dann an den Konsumenten zurück.

Der Proxy wird die Anfragesemantik so verändern, dass er selbst ein Mapping vom Datum eines Bildes auf eine numerische ID vorhalten wird, über die sich der Proxy dann abfragen lässt. Zusätzlich soll es möglich sein, die Bilder mit einer Bewertung zu versehen. In einer eigenen Datenbank soll der Proxy eine numerische Bewertung zwischen 1 und 10 Punkten für jedes Bild speichern können. Über einen weiteren REST-Endpunkt sollen Anwender die aktuelle Bewertung für jedes Bild abrufen können.

Proxys implementieren keine oder nur wenig eigene Geschäftslogik. Sie konvertieren Daten und fangen externe Störungen möglichst so ab, dass das IT-System dadurch nicht beeinträchtigt wird. Da es Ziel ist, zum einen eigene numerische IDs für jedes Bild zu vergeben und dazu noch Bewertungen zu speichern, geht das Beispiel über das klassische Proxy-Pattern hinaus. Bei der Anwendung ist also von einem Microservice die Rede, der unter anderem das Remote-Proxy-Pattern implementiert.

Abbildung 1 zeigt in einem Sequenzdiagramm, wie die einzelnen Komponenten des Gesamtsystems miteinander interagieren sollen. In der grünen Box sieht man die beiden Komponenten des Microservice. Der Webserver nimmt von einem Servicenutzer einen HTTP-GET-Request entgegen. Um den Request bedienen zu können, werden Daten der externen NASA-API benötigt. Um an diese Daten zu kommen, wird eine asynchrone Nachricht an den Remote-Proxy gesendet, der daraufhin einen GET-Request an die NASA-API schickt. Sobald er von dort die Antwort mit den gewünschten Bilddaten erhält, sendet der Remote-Proxy diese Daten wieder über den Eventbus an den Webserver zurück. Letzterer ist damit nun in der Lage, die Anfrage des Servicenutzers zu bedienen.

Sequenzdiagramm zum Zusammenspiel des Microservice mit Anwender und externer API (Abb. 1)

Auswahl der Techniken

Mittlerweile existieren zahlreiche ausgereifte Werkzeuge und Frameworks, mit denen sich Microservices schnell und ohne viel Boilerplate-Code entwickeln lassen. Dieser Artikel zeigt, wie sich so etwas umsetzen lässt. Dabei werden folgende Techniken zum Einsatz kommen.

Schnittstellenbeschreibung mit OpenAPI 3

Um die vom Microservice angebotene Schnittstelle zu beschreiben, wird die OpenAPI-Spezifikation genutzt. Hierbei handelt es sich um eine auf YAML basierende Notation, mit der sich frei von Technik Schnittstellen und Datenobjekte beschreiben lassen. Die Beschreibungen lassen sich dann verwenden, um daraus Quellcode für eine Reihe von Programmiersprachen zu generieren. Der Microservice bietet also gleich die Möglichkeit, Quellcode für konsumierende Applikationen zu erzeugen. Es existieren Codegeneratoren für beispielsweise Java, C/C++, C#, Kotlin, PHP, Python, JavaScript und TypeScript. Im Beispiel wird es sogar möglich sein, die OpenAPI-Spezifikation einzusetzen, um damit einen Webserver zu konfigurieren.

Implementierung mit Vert.x und Kotlin

Kotlin ist eine Programmiersprache, die in Bytecode für die Java Virtual Maschine (JVM) übersetzt werden kann. Sie kann auf Java-Bibliotheken zugreifen, und mit Kotlin erstellter Code lässt sich aus Java heraus verwenden. Das hohe Maß an Interoperabilität mit Java macht es einfach, Kotlin in Java-Projekte zu integrieren. Kotlin-Code ist für Java-Entwickler in der Regel einfach zu verstehen. Die Syntax ist der von Java ähnlich, jedoch an vielen Stellen deutlich "schlanker".

Mit Vert.x steht ein ereignisorientiertes Entwicklungsframework zur Verfügung. Es unterstützt Nebenläufigkeit und setzt auf das Paradigma der reaktiven Programmierung. Das Framework hat zum Ziel, während der Ausführung möglichst wenige Kernel-Threads in Anspruch zu nehmen. Dadurch lässt sich ein hohes Maß an Nebenläufigkeit bei gleichzeitig geringer Inanspruchnahme von Betriebssystemressourcen erreichen.

Anwendungslogik implementiert man in Vert.x mit sogenannten Verticles. Diese Ausführungseinheiten arbeiten ereignisgetrieben und kommunizieren asynchron miteinander über den durch Vert.x bereitgestellten Eventbus. Das Framework ermöglicht es, mit wenigen zusätzlichen Codezeilen einen Webserver zu implementieren, der neben dem Uraltprotokoll HTTP/1.1 auch HTTP/2 anbietet.