Was ist Quarkus?

Die Programmiersprache Java ist als alteingesessene „Industrie-Sprache“ bekannt. Sie wurde bereits in den Anfangsjahren des Internets konzipiert, doch in der Zwischenzeit hat sich das Web rasant weiterentwickelt. Neben der klassischen Client-Server-Architektur existiert eine Reihe spannender alternativer Modelle: Containerbasierte Anwendungen, Microservices, Serverless-Computing und reaktive Web-Apps haben sich im Mainstream durchgesetzt. Diese Arten von Anwendungen ließen sich mit Java bisher nur schwer realisieren; mit dem Quarkus-Framework hat sich dies nun geändert. Der RedHat-Manager Ken Johnson hat das so beschrieben:

Zitat

„… it’s a very small Java stack, perfect for containers, serverless and other scenarios where you’re running Java applications in the cloud.“ – Ken Johnson, Quelle: https://www.openshift.com/blog/quarkus-is-here-for-your-java

„Quarkus“ ist ein kleiner Java-Stack, perfekt für Container, Serverless und andere Szenarien, bei denen Java-Anwendungen in der Cloud laufen.“ (Übersetzung: IONOS)

Wir stellen Quarkus vor und zeigen, wie das Framework das Erstellen von Java-Anwendungen revolutioniert.

Was ist das Besondere an Quarkus?

Quarkus ist ein von der Firma RedHat entwickeltes Framework zur Erstellung von Java-Anwendungen. Quarkus wurde mit dem Ziel entwickelt, Java-Programme in Containern auszuführen. Insbesondere liegt das Augenmerk auf der Unterstützung der Orchestrierungs-Software Kubernetes. Ein weiterer Fokus der Quarkus-Entwicklung liegt auf der Nutzung etablierter Java-Bibliotheken und Standards.

Als Ausführungsschicht für den Java-Code kommt „HotSpot“ vom OpenJDK-Projekt als Java Virtual Machine (JVM) zum Einsatz. Ferner lässt sich die darauf aufbauende Entwicklung „GraalVM“ nutzen. Letztere erlaubt das Kompilieren des Java-Codes in direkt ausführbaren Maschinencode. Um den unmittelbaren Vorteil bei der Nutzung von Quarkus nachzuvollziehen, schauen wir uns zunächst an, wie Java-Anwendungen mit und ohne Quarkus ausgeführt werden.

Wie wurden Java-Anwendungen traditionell ausgeführt?

Die Grundidee, die Java bei der Einführung revolutionär machte, war so einfach wie bestechend: Java sollte es ermöglichen, ein Programm zu schreiben, ohne an eine spezifische Hardware oder ein Betriebssystem gebunden zu sein. Diese Plattformunabhängigkeit wird oft unter dem Sprichwort „write once, run anywhere“ („einmal schreiben, überall ausführen“) zusammengefasst. Die damit einhergehende Portabilität erlaubt, das Programm zwischen Plattformen zu bewegen. Ein toller Trick! Doch wie kann das funktionieren?

Wie in anderen Programmiersprachen auch beginnt ein Java-Programm mit dem menschenlesbaren Quelltext. Um die Anweisungen des Quelltextes auf einem Computer auszuführen, werden dazu korrespondierende Instruktionen im Format des spezifischen Prozessors erzeugt. Bei Java kommt noch ein Zwischenschritt hinzu: Der Quelltext wird – wie bei der Sprache Python – zunächst in ein intermediäres Format, den sogenannten Bytecode, übersetzt. Der Bytecode wird in der „Java virtual machine“ (JVM, „Java virtuelle Maschine“) ausgeführt. Um ein Java-Programm auf einem Gerät auszuführen, muss auf dieser also eine JVM installiert sein.

Zum Ausführen in der JVM wird der Bytecode traditionell interpretiert. Dabei werden die Bytecode-Anweisungen Stück für Stück in Maschinen-Code-Instruktionen übersetzt und ausgeführt. Performanter ist der Prozess der „Just-in-time compilation“ (JIT, „Kompilierung zum passenden Zeitpunkt“). Hierbei wird der Bytecode ebenfalls in Maschinencode umgewandelt, es kommen jedoch weitere Optimierungen zum Tragen. Zum Ausführen eines Java-Programms gehören also die folgenden Schritte:

  1. Java-Quelltext mit dem Java-Compiler-Befehl 'javac' zu Bytecode kompilieren:
javac java_program.java
  1. Java-Bytecode mit dem Java-Runtime-Befehl 'java' ausführen – dabei wird Maschinen-Code erzeugt:
java java_program
Hinweis

Wir sprechen hier von einer „virtuellen Maschine“. Obwohl der Begriff derselbe ist, ist damit keine Technologie zur Virtualisierung eines Betriebssystems gemeint. Stattdessen wird intermediärer Code in Maschinen-Code übersetzt.

So praktisch Javas „write once, run anywhere“-Modell auch ist, der Ansatz birgt einige Schwächen: Die Nutzung der JVM bringt einen nicht unerheblichen Overhead mit sich. Zum einen wird zum Starten der JVM eine gewisse Zeit benötigt, die zur Laufzeit der eigentlichen App hinzukommt. Zum anderen ergibt sich neben einem höheren Speicherverbrauch eine Einbuße an Performance. All dies spielt eine wenig kritische Rolle bei Anwendungen, die lange laufen. Jedoch ist der Ansatz für kurzlebige, containerbasierte Anwendungen wenig geeignet. Diese sollen idealerweise so schnell wie möglich starten; eine Startzeit von mehreren Sekunden ist inakzeptabel.

Wie werden Java-Anwendungen mit Quarkus ausgeführt?

Im Gegensatz zur nativen Ausführung von Java-Anwendungen bieten sich mit Quarkus einige Vorteile. Unterscheiden wir die beiden von Quarkus unterstützten Modi:

  1. Optimierung des Bytecodes und Ausführen in der JVM
  2. Ausführen als nativer Code nach Kompilierung

Mit Quarkus geschriebener Java-Code lässt sich ganz normal auf der JVM ausführen. Jedoch ergeben sich beachtliche Vorteile in Bezug auf Speicherverbrauch und Startzeit der laufenden Anwendung. Um dies zu erreichen, bedient sich Quarkus einiger Tricks. Insbesondere wird eine Reihe zeitaufwendiger Schritte von der Ausführung in den Build-Prozess verschoben. Dazu gehören die ansonsten bei jeder Ausführung einer Java-Anwendung ablaufenden Schritte:

  • Laden und Parsen von Konfigurationen
  • Scannen des Java-Klassen-Pfads und Auflösen von Annotationen
  • Ggf. Erstellen von Entitäten-Modellen für Datenbanken o. ä.

Mit Quarkus werden diese Schritte einmalig durchgeführt und die Resultate für den schnellen Abruf zwischengespeichert. Als weitere Performance-Optimierung reduziert Quarkus die Menge der zur Laufzeit dynamisch vorliegenden Informationen. Diese werden durch entsprechende statische Konstrukte ersetzt. Das ist insbesondere im Hinblick auf den Einsatz in Containern sinnvoll. Denn eine containerisierte Anwendung wird für gewöhnlich ohnehin nicht verändert und läuft immer in derselben Umgebung.

Der zweite von Quarkus unterstütze Modus zur Ausführung von Java-Anwendungen ist noch interessanter. Bei der „Ahead-of-time compilation“ (AOT) wird aus dem Java-Quelltext statt Bytecode direkt ausführbarer Maschinencode erzeugt, d. h. es wird auf der Ziel-Hardware keinerlei JVM mehr benötigt. Dafür läuft das Programm nur auf einer spezifischen Prozessorarchitektur und muss für andere Plattformen neu kompiliert werden. Für den Einsatz in Containern ist diese Einschränkung in der Regel jedoch unerheblich. Die bei der AOT-Kompilierung erzielten Ersparnisse an Speicherverbrauch und Startzeit der Anwendung sind geradezu atemberaubend. Vergleichen Sie die hier dargestellten Performance-Richtwerte von der offiziellen Quarkus-Homepage:

Anwendung Szenario Speicherverbrauch Zeit zur ersten Antwort
Quarkus + AOT REST 12 MB 0.02 s
Quarkus + AOT REST + CRUD 28 MB 0.04 s
Quarkus + JIT REST 73 MB 0.94 s
Quarkus + JIT REST + CRUD 145 MB 2.03 s
Cloud-Native Stack REST 136 MB 4.3 s
Cloud-Native Stack REST + CRUD 209 MB 9.5 s
Hinweis

Zur Terminologie: Mit REST ist gemeint, dass nur ein Webserver im Container läuft. Beim Szenario REST + CRUD läuft neben dem Webserver eine Datenbank. Beim Cloud-Native Stack enthält der Container neben der Java-Anwendung eine JVM.

Wofür nutzt man Quarkus?

Bei Quarkus handelt es sich nicht bloß um ein weiteres Anwendungs-Framework. Stattdessen will die Software neu definieren, was es bedeutet, mit Java Anwendungen zu entwickeln. Rufen wir uns in Erinnerung: Traditionell war es wichtiger, dass eine Java-Anwendung lange Zeit stabil läuft. Wie lange die Anwendung zum Starten benötigte, war unkritisch.

Denken wir nun an containerbasierte Anwendungen: Neue Container werden ggf. durch Orchestrierungs-Software automatisch gestartet. Die im Container befindliche Anwendung soll dann sofort einsatzbereit sein. Ferner werden häufig für einen Service mehrere redundante Container gestartet. Die mit Quarkus erzielte Verringerung des Ressourcen-Verbrauchs multipliziert sich dementsprechend.

Der RedHat-Manager Alex Handy fasst das so zusammen:

Zitat

„When you think of serverless computing, microservices and the […] cloud, there’s one language you’re probably not [thinking of]: Java. And that’s a real shame. […] Java was and is the workhorse language of business. It remains the third most popular language in the world […] It’s been the language of choice for corporations that need to keep a single application up and running for years at a time.“ – Alex Handy, Quelle: https://thenewstack.io/quarkus-gives-spring-boot-users-a-path-to-serverless-and-live-coding/

„Wenn Sie an Serverless-Computing denken, an Microservices oder die Cloud, dann gibt es eine Sprache, an die Sie wahrscheinlich nicht denken: Java. Und das ist echt schade. Java war und ist das Arbeitstier der Wirtschaft. Sie ist weiterhin die drittbeliebteste Programmiersprache der Welt und ist seit langem die erste Wahl für Unternehmen, die eine einzelne Anwendung jahrelang am Laufen halten müssen. (Übersetzung: IONOS)

Die Vorteile von Quarkus liegen auf der Hand. Jedoch bringt das Framework auch einige Limitationen mit sich. Daher ist Quarkus nicht primär dafür gedacht, existierende Java-Applikationen zu migrieren. Vielmehr lohnt es sich, Quarkus als Ausgangspunkt für eine Neuentwicklung einzusetzen. Im Folgenden schauen wir uns ein paar konkrete Einsatzgebiete an. Bei allen genannten Beispielen kommt als Build-Tool Maven oder Gradle zum Einsatz. Man legt das Einsatzgebiet per Konfiguration des 'mvn'- oder 'gradle'-Befehls fest. Das Build-Tool erzeugt dann automatisch die benötigten Konfigurationen und Artefakte.

Mit Java und Quarkus Microservice-Anwendungen in Kubernetes realisieren

Bei Kubernetes handelt es sich um eine Orchestrierungs-Software für Container-Anwendungen. Beliebt ist der Einsatz von Kubernetes mit Docker-Containern. Einzelne Services einer Anwendung werden als Docker-Image gespeichert und von Kubernetes verwaltet. Dabei übernimmt der Orchestrierer das Management der aus den Images erzeugten Container: Kubernetes startet, steuert und überwacht die Services. Häufig werden zur Lastverteilung und für erhöhte Fehlertoleranz mehrere Kopien eines Service gestartet. Stürzt einer der Services ab, wird der Container zerstört und ein neuer Container aus demselben Image erzeugt. Quarkus bringt die für den Einsatz in Kubernetes notwendigen Konfigurationen von Hause aus mit.

Mit Java und Quarkus REST-APIs und Serverless-Anwendungen realisieren

Bei REST handelt es sich um den alteingesessenen Architektur-Stil für Webanwendungen. Insbesondere APIs werden meist diesem Ansatz folgend implementiert. Einer REST-API zugrunde liegt die Client-Server-Architektur. Dabei läuft die Kommunikation über das HTTP-Protokoll unter Einsatz der „Verben“ GET, POST, PUT, DELETE. Diese korrespondieren zum aus dem Datenbank-Umfeld bekannten CRUD („create, read, update, delete“). Der Datenaustausch zwischen API und Nutzer erfolgt meist per JSON.

Serverless-Computing ist eine alternative Architektur für cloudbasierte Anwendungen. Bei diesem auch als „Function as a Service“ (FaaS) bekannten Modell läuft eine einzelne Funktion kurzfristig in einem Container. Die Funktion wird aufgerufen, führt eine Berechnung durch und wird dann wieder abgeschaltet. Trotz des Namens laufen die Serverless-Funktionen weiterhin auf Servern. Nun muss man sich als Programmierer nicht mehr um diese kümmern. Mit AWS Lambda, Google Cloud Functions und Microsoft Azure Functions stehen Serverless-Umgebungen auf allen großen Cloud-Plattformen bereit. Mit Quarkus lässt sich Java-Code auf diesen Plattformen einsetzen.

Tipp

Erstellen Sie Ihre eigene REST-API – auf einem Dedicated Server von IONOS.

Mit Java und Quarkus reaktive Web-Apps erstellen

Die reaktive Programmierung stellt im Gegensatz zur imperativen Programmierung ein modernes Programmierparadigma dar. Man beschreibt, welche Aktionen ablaufen sollen, wenn bestimmte Ereignisse eintreten. Die wohl bekanntesten Vertreter dieses Programmierstils sind die in JavaScript verfassten Frameworks „React“ und „Vue“. Der Fokus liegt bei beiden auf dem Erstellen webbasierter Benutzerschnittstellen. Mit Quarkus lassen sich Anwendungen im imperativen und reaktiven Stil realisieren. Es ist sogar möglich, beide Paradigmen zu kombinieren.

Wo wird Quarkus eingesetzt?

Quarkus wurde mit dem Ziel konzipiert, Java-Anwendungen für den Einsatz in Containern und Cloud-Umgebungen zu optimieren. Durch die Möglichkeit, ein Java-Programm direkt in Maschinen-Code zu kompilieren, ergeben sich jedoch noch weitere spannende Einsatzmöglichkeiten. Wir betrachten die derzeit interessantesten Einsatzgebiete für Quarkus.

Erinnern wir uns zunächst, wie ein mit Quarkus entwickeltes Java-Programm ausgeführt wird. Während des Build-Prozesses wird der Java-Quelltext in Bytecode kompiliert, der bei der Ausführung in Maschinen-Code übersetzt wird. Mit Quarkus lässt sich Bytecode erzeugen, der dann in einer Java-Laufzeitumgebung wie der HotSpot VM per Interpretation oder Just-in-time(JIT)-Kompilierung ausgeführt wird. Dabei kommen je nach Konfiguration verschiedene performancerelevante Optimierungen zum Tragen.

Zum anderen kann die auf HotSpot basierende GraalVM eingesetzt werden, um mittels Ahead-of-time(AOT)-Kompilierung ein natives Image zu erzeugen. Beim nativen Image handelt es sich um eine Binärdatei, die alle zur Ausführung der Anwendung notwendigen Bibliotheken und Abhängigkeiten enthält. Da zur Ausführung keine JVM benötigt wird, ergeben sich aus der AOT-Kompilierung die größten Performance-Zugewinne.

Java-Anwendungen in Container-Umgebungen

Meist kommt beim Einsatz einer Java-App in Containern Kubernetes zum Einsatz. Eine als Docker-Image verpackte Java-App lässt sich auch auf einem OpenShift-Cluster einsetzen. Den Einsatz von Quarkus mit Kubernetes können Sie auch selbst ausprobieren, z. B. mit einer Minikube-Installation auf Ihrem lokalen System.

Java-Funktionen in Serverless-Umgebungen

Nutzen Sie Quarkus, um unkompliziert eine in Java geschriebene Funktion in den Serverless-Umgebungen von Amazon, Google und Microsoft einzusetzen.

Java-Programme in eingebetteten Systemen

Mit der Möglichkeit, ein natives Image aus einer Java-Anwendung zu erstellen, lässt sich Java-Code auch auf eingebetteten Systemen nutzen. Hierbei kommt die AOT-Kompilierung zum Einsatz, die im konkreten Anwendungsfall für geringen Speicherverbrauch und schnelle Startzeiten sorgt.

Tipp

Nutzen Sie Managed Kubernetes von IONOS für Ihre Container-Apps.

Quarkus im Vergleich mit anderen Frameworks

Quarkus eignet sich für ein breites Spektrum unterschiedlicher Einsatzszenarien. Andere Frameworks sind z. T. spezifischer. Schauen wir uns ein paar vergleichbare Alternativen an:

  • React: Das JavaScript-Framework hat sich als Standard für die namensgebende reaktive Programmierung etabliert.
  • Open Liberty: Das von IBM stammende Framework erlaubt ebenfalls die Entwicklung von Microservice-Anwendungen mit Java. Wie Quarkus bringt Open Liberty eine Live-Reload-Funktionalität mit.
  • Micronaut: Mit dem Micronaut-Framework lassen sich in Java Microservices und Serverless-Anwendungen programmieren. Dabei kommt wie bei Quarkus die GraalVM zum Einsatz.
  • Spring / Spring Boot: Bei Spring handelt es sich um das wohl beliebteste Java-Framework für Webanwendungen. Spring setzt auf der GraalVM auf und unterstützt neben der Erstellung von Microservices die reaktive Programmierung und Live-Reload. Im Performance-Vergleich schlägt Quarkus Spring; ein existierendes Spring-Projekt lässt sich relativ komfortabel zu Quarkus migrieren.

Was sind die Vor- und Nachteile von Quarkus?

Der herausragende Vorteil bei der Entwicklung von Java-Anwendungen mit Quarkus ist der Zugewinn an Performance. Dieser kommt insbesondere beim Einsatz von Java-Anwendungen in Container-Umgebungen zum Tragen. Zu den Performance-Vorteilen zählen:

  • Schnelle Startzeit der Anwendung
  • Geringer Speicherverbrauch der laufenden Anwendung
  • Beinahe unmittelbare Skalierung von Services
  • Geringer Platzbedarf der nativen Images

Neben den Performance-Vorteilen glänzt Quarkus vor allem durch seine Benutzerfreundlichkeit. Der Einsatz des Frameworks ist für erfahrene Java-EE- und Spring-Entwickler leicht zu erlernen. Dem kommt weiterhin zugute, dass Quarkus auf einem solidem Grundgerüst aufsetzt. Zum Einsatz kommen u. a. die folgenden Standard-Technologien:

  • Eclipse MicroProfile
  • Spring Dependency Injection
  • Hibernate ORM

Ferner bietet Quarkus eine Live-Coding-Umgebung, in der Entwickler schnell prototypisieren können. Zur reibungslosen Entwicklung trägt das Live-Reload-Feature bei: Nach Aktivierung des Dev-Modus werden Änderungen an Quelltext und Konfiguration im Hintergrund kompiliert. Der Entwickler muss nur noch das Browserfenster neu laden, um die Änderungen nachzuvollziehen.

Kommen wir am Schluss noch zu den Nachteilen beim Einsatz von Quarkus. Diese ergeben sich hauptsächlich aus den Optimierungen, die beim Kompilieren zum Tragen kommen.

  • Insbesondere die Verringerung der dynamisch zur Laufzeit erzeugten Informationen kann in manchen Szenarien zu Problemen führen.
  • Die stark eingeschränkten Möglichkeiten zur Introspektion erschweren u. U. das Debugging einer Anwendung.
  • Der hochoptimierte Build-Prozess für native Images nimmt viel Zeit in Anspruch.

Quarkus ist nicht für jedes beliebige Java-Projekt gedacht. Der Einsatz des Frameworks erfordert z. T. die Umstellung von Prozessen.