Microframework Quarkus – ein erster Einblick

In diesem Blog Beitrag geben wir einen kurzen Einblick in das Java-Microframework Quarkus.

Quarkus ist ein Open Source Projekt das unter einer Apache License Version 2.0 veröffentlicht wurde und von Red Hat mitentwickelt wird.

Quarkus verfolgt eine „container first“ Philosophie und unterstützt daher eine native Kompilierung die speziell für Container optimiert wird, diese ist aber vollkommen optional. Hierfür wird GraalVM verwendet, welche native Java executables ausführen kann. Diese starten wesentlich schneller und benötigen zusätzlich deutlich weniger Speicher als eine herkömmliche JVM. Aufgrund dessen eignet sich Quarkus ausgezeichnet für Servless-, Cloud- und Kubernetes-Plattformen.

Des Weiteren unterstützt Quarkus MicroProfile (aktuell Version 3.3). MicroProfile ist ein Projekt von Eclipse, welches Spezifikationen zur Verfügung stellt mit dem Ziel Enterprise Java Anwendungen für Microservice Architekturen zu standardisieren und zu optimieren. Dies beinhaltet unter anderem verschiedene Mechanismen zur Anwendungskonfiguration, Features zur Verbesserung der Fehlertoleranz, REST Clients, die Erfassung von verschiedenen Metriken und vieles mehr.

Quarkus ist außerdem vielfältig konfigurierbar und bietet zahlreiche Erweiterungen, wodurch Quarkus in seiner Funktionalität auch mächtigeren Java-Frameworks in nichts nachsteht.

Erstellung eines Quarkus Projekts

Ein Quarkus Projekt kann sowohl mit Maven als auch Gradle erstellt werden. In diesem Beitrag beziehen wir uns immer auf Gradle, dies macht aber für die Anwendung keine großen Unterschiede.

Ein neues Projekt kann einfach über ein beliebiges Terminal erstellt werden, dafür muss beispielsweise folgender Befehl ausgeführt werden:

mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
    -DprojectGroupId=<my-groupId> \
    -DprojectArtifactId=<my-artifactId> \
    -DprojectVersion=<my-version> \
    -DclassName="<org.my.group.MyResource>" \
    -Dextensions="resteasy,resteasy-jackson" \
    -DbuildTool=gradle

Dies initialisiert ein Projekt mit dem angegebenen „buildTool“, in diesem Fall Gradle und mit den angegebenen Extensions, hier „resteasy“ und „resteasy-jackson“.

Eine andere Alternative ist das Projekt via https://code.quarkus.io/ zu erstellen. Hier hat man eine ausführliche Übersicht über alle vorhandenen Extensions und kann diese individuell für das zu erstellende Projekt auswählen.

Nach dem ausführen des Befehls erhalten wir ein Projekt mit der folgenden Struktur:

Abb. 1: Dateistruktur nach dem Anlegen eines Projekts.

Diese beeinhaltet neben dem source code in „src/main/java“ auch standardmäßig verschiedene Dockerfiles. Diese sind folgende:

  • Dockerfile.jvm: baut das JVM kompatible Image der Anwendung
  • Dockerfile.legacy-jar: ein legacy Dockerfile, welches ebenfalls ein JVM kompatibles Image baut, jedoch mit womöglich langsamerer boot time
  • Dockerfile.native: baut das native Image, welches für GraalVM verwendet werden kann (ohne JVM)
  • Dockerfile.native-distroless: baut ebenfalls ein natives Image, jedoch zusätzlich noch distroless, also ohne eigenes Betriebssystem

Hier ist also sofort der „container first“ Ansatz sichtbar. Des Weiteren wird auch gleich für die Tests ein separates „native-test“ package generiert.

Zusätzlich werden die „application.properties“, welche im Grunde ähnlich der „application.properties“ in Spring Boot sind und die grundlegende Gradle Projektstruktur erzeugt.

Dependency Injection

Quarkus ist ein MicroProfile-kompatibles Framework, welches für Dependency Injection(DI) den JEE-Standard „Contexts and Dependency Injection for Java 2.0“ verwendet. Es ist ebenfalls möglich via Extension Spring DI zu verwenden.

Wenn man rein native Anwendungen mit Quarkus entwickeln will muss man bei DI jedoch aufpassen, da hier intern häufig Reflections beim zugriff auf private Attribute verwendet werden. Dies muss für GraalVM explizit kenntlich gemacht werden.

Extensions

Quarkus Anwendungen sind sehr leicht via Extensions erweiterbar. Dies ist wieder einfach über den Befehl:

./gradlew addExtension --extensions="<extension>"

möglich. Alternativ kann auch einfach die gewünschte Extension in der „build.gradle“ Datei des Projekts unter den dependencies ergänzt werden. Obwohl Quarkus noch relativ jung ist gibt es bereits zahlreiche Extensions. Darunter unter anderem Unterstützung von Kubernetes, OpenApi, ApacheCamel, Hibernate/Panache, HashiCorp Vault, zahlreiche Datenbanken und vieles mehr.

Konfiguration

Die Konfiguration einer Quarkus Anwendung erfolgt wie zuvor schon erwähnt über die „application.properties“.

Abb. 2: Beispiel einer application.properties Datei

Quarkus bietet standardmäßig die drei Konfigurations Profile test, dev und prod an. Die Variablen für ein spezielles Profil können einfach mit „%{profile}.config.key=value“ definiert werden. Das jeweilige Profil kann dann wiederum einfach über die environment Variable QUARKUS_PROFILE=<yourProfile> aktiviert werden.

Desweiteren können alle Variablen in den „application.properties“ mit Environment Variablen der jeweiligen Umgebung überschrieben werden. Dafür müssen diese lediglich in einem bestimmten Format gesetzt werden. Ein kleines Beispiel zu dem Code aus Abbildung 2. Um die Variable „quarkus.datasource.jdbc.url“ zu überschreiben, muss lediglich in der Umgebung die Environment Variable

QUARKUS_DATASOURCE_JDBC_URL=<newValue>

gesetzt werden. Also jeweils der Name der Variable groß geschrieben und mit Unterstrich statt Punkt verküpft.

Dies ist sehr praktisch und erleichtert eine umgebungsspezifische Konfiguration, ohne Environment Variablen in den „application.properties“ mehrfach für verschiedene Umgebungen zu definieren.

Fehlertoleranz

Quarkus bietet eine Implementierung der MicroProfile Fault Tolerance Spezifikation (Extension:“smallrye-fault-tolerance“). Dies ermöglicht unter anderem das einfache Hinzufügen der folgenden Mechanismen.:

  • Resiliency Retries: bei fehlgeschlagenen Requests wird maxRetries mal versucht die Schnittstelle erneut aufzurufen. Beispiel:
@GET
@Retry(maxRetries = 4)
public List<Employee> employees(){...}
  • Resiliency Timeouts: fügt dem Endpoint einen Timeout hinzu, sobald dieser abgelaufen ist wird der Zugriff auf die externe Resource abgebrochen. Vor allem bei optionalen Inhalten ist dies sinnvoll.
@GET
@Path("/recommendations")
@Timeout(250)
public List<Coffee> recommendations(){...} 
  • Resiliency Fallbacks: nach einem bestimmten Timeout wird die angegebene Fallback Methode aufgerufen.
@Fallback(fallbackMethod = "fallbackRecommendations")
public List<Coffee> recommendations(){...}
  • Resiliency Circuit Breaker: Kann die Anzahl der Fehler die im System auftreten limitieren. Wenn bestimmte Methoden Aurufe zu häufig fehlschlagen kann der circuit breaker für eine bestimmte Zeit Anfragen blockieren um das System wieder in einen stabileren Zuständ zu bringen.

Die annotierten Parameter können sogar zur Laufzeit über die „application.properties“ geändert werden. Zum Beispiel:

org.acme.EmployeeResource/employees/Retry/maxRetries=6

Kubernetes

Mit den Extensions „kubernetes“ und „jib“ wird mit jedem build auch ein container image mit jib gebaut. Des Weiteren bietet Quarkus zahlreiche Konfigurationsparameter für die so erstellten images an. Hier können beispielsweise über die „application.properties“ image group, name und tag konfiguriert werden. Ebenso ist es möglich environment variablen des images zu konfigurieren und secretes, sowie volumes zu verwalten.

Reactive und Vert.x

Quarkus basiert auf Vert.x, eine Toolbox für reaktive Anwendungen, welche intern von Quarkus für fast alle netzwerkbezogenen Features verwendet wird aber nach außen kaum sichtbar ist. Dabei wird der Vert.x Event Bus verwendet, um asynchrone Nachrichten zwischen verschiedene Komponenten und weiteren Clients zu verwalten.

Um den reaktiven Ansatz zu verdeutlich schauen wir uns ein kurzes Beispiel an in dem die reaktive und die imperative Ausführung eines HTTP Request verglichen werden. Abbildung 3 zeigt dabei den Workflow durch Vert.x bei einem Http Request an Quarkus.

Abb. 3: Workflow mit Vert.x (Quelle: https://quarkus.io/guides/getting-started-reactive)

Der Vert.x Server ist dabei in Quarkus eingebettet und leitet die Anfragen entsprechend weiter. Dabei kann der Applikationscode sowohl imperativ (synchron) als auch reaktiv (asynchron) sein. Aufgrund dessen verwendet Vert.x entweder einen normalen synchronen worker Thread, der blockiert bis die Anfrage vollständig ausgeführt und abgearbeitet wurde oder einen asynchronen I/O Thread, der während der Wartezeit bei der Anfrage für andere Prozesse zur Verfügung steht. Die I/O Threads ermöglichen dadurch eine deutlich bessere Ressourcen Auslastung, da Threads deutlich kürzer blockiert werden. Dies kann man sich gut anhand einer Datenbankabfrage über das Netzwerk vorstellen, während man die volle Zeit auf das Ergebnis wartet können in der Zwischenzeit andere Anfragen bearbeitet werden. Erst wenn die Antwort erhalten wird springt der I/O Thread zurück zur ursprünglichen Anfrage und gibt die Antwort zurück an den Client.

Um diese Vorteile zu nutzen muss der Code nicht blockierend geschrieben sein. Dafür bietet Quarkus die Unterstützung verschiedener reaktiver Bibiliotheken wie Mutiny an, auf die wir hier aber nicht näher eingehen.

Fazit

Die Landschaft der Java Frameworks und Microframeworks ist vielfältig und im ständigen Wandel. Quarkus schafft es aber hier deutlich hervorzustechen. Quarkus verwendet MicroProfile und verwendet eine reaktive Anwendungs-Engine, die die Vorteile des imperativen und reaktiven Programmierens vereint.

Dazu ist Quarkus außerordentlich entwicklerfreundlich mit vielfältigen Konfigurationsmöglichkeiten und einfacher Erweiterbarkeit. Quarkus erscheint besonders interessant, wenn es um die Entwicklung Cloud nativer Anwendungen geht, da Quarkus ganz speziell hierfür ausgerichtete Features bereitstellt. Wenn die Images nativ kompiliert und mit GraalVM ausgeführt werden bietet Quarkus deutlich schnellere Startup Zeiten, sowie eine deutlich niedrigere Arbeitsspeicher Auslastung als beispielsweise das weit verbreitete Java-Framework Spring Boot. Wobei dieses aktuell auch seine erste Beta für einen Native Modus gestartet hat. Ein Nachteil des Ganzen ist aber eine etwas höhere Anfragelatenz, dies muss für den konkreten Anwendungszweck daher natürlich immer abgewogen werden.

Wir sind davon überzeugt, dass Quarkus die Chance hat der Platzhirsch unter den Java Frameworks zu werden.

Quellen:

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

%d Bloggern gefällt das: