Wenn es um Entwicklungszykluszeiten, Startzeiten von Anwendungen, Größe der Deployments und Kompatibilität in oder mit Containerumgebungen geht, scheinen andere Sprachen wie JavaScript, Python oder Go gerade Java den Rang abzulaufen. Mit Quarkus soll sich das jetzt ändern: Native Anwendungen mit Java, Performance vergleichbar mit C, Startzeiten unter einer Sekunde und dazu alle bereits bekannten Vorteilen von Java. Damit dringt Java in eine neue Dimension vor uns setzt völlig neue Maßstäbe.
Quarkus ist ein für die GraalVMs optimiertes Java-Framework. Bei GraalVM handelt es sich im Grunde um zwei eigenständige virtuelle Maschinen:
- die eigentliche GraalVM ist eine mehr oder weniger gewöhnliche Java 8 zertifizierte JVM mit einigen Besonderheiten und Optimierungen sowie
- Substrate-VM, welche den JVM-Code (nicht nur Java) in plattformspezifischen Maschinencode übersetzen kann. Es werden damit dynamisch oder statisch gelinkte Executables für verschiedene Ausführungsumgebungen
Für die Substrate-VM gelten einige Besonderheiten. Vor allem von vielen Frameworks verwendete dynamische Java-Sprach-Features, wie Reflections und Dynamic Proxies können nur mit
Einschränkungen verwendet werden bzw. bedürfen spezieller Vorbereitungen. Es ist also leider nicht möglich, beliebigen JavaCode ohne Anpassungen on-the-fly in ein natives Executable zu
übersetzen. Als Konsequenz ist es notwendig, dass die Entwickler von Frameworks diese auf die Benutzung mit der Substrate-VM vorbereiten bzw. anpassen.
Ein Framework, das genau auf diese Bedingungen optimiert wurde, ist Quarkus. Dabei werden nicht nur bekannte Standards und Frameworks, wie z.B. RESTEasy, Hibernate, Netty, Vert.x,
JUnit, sondern auch weniger bekannte Bibliotheken wie zum Beispiel Panache, Narayana JTA und STM durch sogenannte Extensions eingebunden.
Prerequisites
Für die nun folgende Arbeit mit Quarkus und der GraalVM benötigen wir die folgenden Tools:
- GraalVM 19.2.1 inklusive Native-Image-Tool
- Docker-Installation 19.03.X
- Maven 3.6.2
und die beiden Umgebungsvariablen GRAALVM_HOME und JAVA_HOME müssen entsprechend korrekt gesetzt werden. Das Native-Image-Tool wird in neueren GraalVM Versionen nicht mehr als Bestandteil der Basisinstallation ausgeliefert. In diesem Fall muss es nachinstalliert werden (Listing 1).
(Listing 1)
Developer Joy
Die Arbeit mit Quarkus stützt sich auf die beiden weit verbreiteten Build-Werkzeuge Gradle und Maven, wobei wir uns im Weiteren auf Maven konzentrieren. Die Erstellung neuer Projekte wird auf zwei Wegen unterstützt:
- Verwendung des Quarkus-Maven-Plugins oder
- mithilfe der Webseite https://code.quarkus.io, auf der die Wunschkonfiguration zusammengestellt und die daraus generierten Projektdateien als Zip-File heruntergeladen werden können.
Auf beiden Wegen kann bequem und schnell ohne großes Dokumentationsstudium das Grundgerüst einer Anwendung erstellt werden. Die Herangehensweise ist von anderen Frameworks
wie Spring bekannt und mit diesen vergleichbar. Wir halten uns daher nicht weiter bei diesem Feature auf, sondern verwenden für die folgenden Schritte ein bereits etwas weiterentwickeltes Beispielprojekt.
Zur Unterstützung und Beschleunigung der Entwicklungsarbeit stellt Quarkus einen Developer-Mode zur Verfügung, der die üblichen Source-Code-Pfade des Projekts überwacht und auf Änderungen reagiert. Dieser Modus ist in das Maven-Plugin integriert und wird mit dem Kommando aus (Listing 2) gestartet. Der Parameter Ddebug=false schaltet die Erzeugung des DebugPorts ab, über den sich ein externer Debugger mit dem Prozess verbinden könnte, und spart so ein paar Sekundenbruchteile bei der Startzeit. Die Umgebungsvariablen sorgen dafür, dass nicht die per Default konfigurierte MySQL-, sondern eine In-Memory-Datenbank verwendet wird.
(Listing 2)
Wir müssen jetzt in einem zweiten Terminal-Fenster weiterarbeiten, da der Befehl aus (Listing 2) weiterhin aktiv ist. Dort können wir z.B. die Funktionalität eines der REST-Endpunkte testen (Listing 3). Der Source-Code des für diese Funktionalität verantwortlichen Controllers ist in (Listing 4) zu finden.
(Listing 3)
(Listing 4)
Gut erkennbar sind die an Eclipse-Microprofile und CDI (Context-and-Dependency-Injection) angelehnten Konfigurationsmöglichkeiten. Die aktuellen Werte der mit @ConfigProperty annotierten Variablen können via Konfigurationsdateien wie zum Beispiel application.properties (Listing 5), Systemparametern wie -Dgreeting.message=blah oder Umgebungsvariablen wie GREETING_MESSAGE=blahblah definiert werden.
(Listing 5 – application.properties)
Im Übrigen ist das Suffix QUARKUS bei Umgebungsvariablen bzw. quarkus bei Systemparametern für interne Anwendungen durch das Framework reserviert. Nun soll der Aufruf von /hello parametrierbar sein (Listing 6).
(Listing 6)
Natürlich führt der Aufruf dieser URL im schnell und vor der eigentlichen Umsetzung erstellten Unit-Test zu einem 404 – Not Found Fehler, da noch kein entsprechender Endpunkt implementiert ist. Das können wir nachholen, indem wir als Simulation der tatsächlichen Entwicklungsarbeit die Kommentarzeilen in (Listing 4) entfernen, die Änderung speichern und den Befehl aus (Listing 6) wiederholen, der dann mit Erfolg ausgeführt wird. Anhand der Ausgaben im ersten Terminal, der die Verzeichnisse überwacht und in dem nach wie vor der Quarkus-Developer-Mode aktiv ist, wird deutlich dass Quarkus die durchgeführte Änderung erkannt, den Source-Code kompiliert (“Changed source files detected, recompiling …”) und das Framework neu gestartet hat. Und das alles in nicht einmal einer Sekunde!
Super Sonic Java (Abb. 1)
The Usual Java-Build
Wie eingangs erwähnt, ist GraalVM auch eine ganz normale JVM und kann daher wie andere JVMs auf bekannte Weise gängige Java-Anwendungen erzeugen (Listing 7).
(Listing 7)
Dabei entstehen im Verzeichnis ./target zwei JAR-Dateien. Für unsere weitere Arbeit ist vor allem die größere der beiden mit dem Postfix -runner interessant. Sie enthält neben unserem eigenen Code zusätzlich Start-, Initialisierung- und Verwaltungs-Code. Die direkten Projektabängigkeiten aus dem Maven-POM-File finden sich dagegen im Verzeichnis ./target/lib. Damit haben wir genug Informationen, um unsere Java-Anwendung mit analoger Konfiguration wie im Developer-Mode zu starten (Listing 8), wofür nicht einmal ganze drei Sekunden benötigt werden.
(Listing 8)
Going Native
Nun wollen wir eine native, d.h. ohne Verwendung einer JVM, direkt vom Betriebssystem ausführbare Anwendung erstellen. Das erledigt für uns ein Aufruf des Quarkus-Maven-Plugins (Listing 9). Das Erzeugen nativer Anwendungen ist zeitaufwendig und benötigt nicht unerhebliche Ressourcen. Auf einem MacBook mit Intel Core i7, 2,5 GHz und 16 GByte dauerte der Prozess etwas mehr als 5 Minuten und verbrauchte in der Spitze circa 3 GByte RAM.
(Listing 9)
Danach liegt im ./target Verzeichnis neben den beiden bereits erwähnten JAR-Files eine rund 60 MByte große ausführbare Datei namens evl-quarkus-runner. Da wir die Erzeugung auf einem Mac ausgeführt haben, handelt es sich um eine native Mac-OSX-Datei. Von der für Entwicklung (Profile dev) und Test (Profil test) verwendeten In-Memory-Datenbank H2 gibt es noch keine native Version, deshalb nutzen wir das prod Profil und starten dafür via ./run-create-container-dmo_mysql.sh eine externe MySQL-Datenbank in einer lokalen Docker-Instanz. Danach kommt die eigentliche Anwendung an die Reihe (Listing 10), die sich mit der MySQL-Datenbank verbindet.
(Listing 10)
Fast unmittelbar nach dem Drücken der Enter-Taste ist unsere Anwendung auch bereits gestartet. Das Framework selbst benötigt laut Konsolenausgabe weniger als 0,1 Sekunden für den Startvorgang!
Going Cross Native
Nun wollen wir native Programme nicht nur für das Betriebssystem unseres Entwicklungsrechner, sondern auch für andere Betriebssysteme wie Linux erstellen. Leider unterstützt GraalVM im Moment kein direktes Cross-Compiling. Es ist deshalb notwendig den Übersetzungsprozess in der jeweiligen Zielumgebung auszuführen. Für die Umsetzung dieser Aufgabenstellung verwenden wir Docker-Desktop-CE-for-Mac und eine Art Build-Container. Anstatt die Transformation in ein natives Executable mit Hilfe des Quarkus-Maven-Plugins anzustoßen, dass dann einen Docker-Build anstößt (Listing 11), bevorzugen wir den direkten Weg. Der Befehl aus (Listing 13) erstellt auf der Basis des (Multi-Stage-) Docker-Files aus (Listing 12) ein DockerImage. Dabei werden alle Build-Schritte im Container ausgeführt.
(Listing 11)
(Listing 12)
(Listing 13)
Zum Abschluss zeigt (Listing 14) das Starten der Anwendung.
(Listing 14)
Die Log-Ausgaben, (Listing 15) erläutert wie man diese anzeigen kann, belegen, dass auch in einem Container die Anwendung unglaublich schnell startet, und zwar rund 0,1 Sekunden. Auch wenn die implementierte Funktionalität relativ trivial ist, handelt es sich dennoch um eine vollständige REST-basierte Java-Anwendung mit Anbindung an eine externe Datenbank.
(Listing 15)
Fazit
Auch wenn Quarkus noch relativ jung ist, kann es bereits einige gelungene Feature auf der Habenseite verbuchen:
Der Developer-Mode ist gut gelungen.
Der Java-Code kann mit einigen Einschränkungen und
Besonderheiten in kleine schnell startende Anwendungen kompiliert werden. Damit ist das Framework insbesondere für die Erstellung von Microservices oder für die Implementierung von Businesslogikbausteinen im Umfeld von Message-Brokern wie zum Beispiel Kafka geeignet und natürlich auch für Serverless-Anwendungen à la OpenFaas o.ä. Dem stehen auch einige Punkte auf der Sollseite gegenüber:
Die Kompilierung in native Anwendungen ist ressourcenund zeitaufwendig.
Existierender Code und Frameworks müssen für die Verwendung in oder mit der Substrate-VM fit gemacht werden. Dafür sind u.U. weiterreichende Anpassungen notwendig.
Die in oder mit Quarkus eingesetzten Frameworks verwenden einige, zumindest für Spring-Anwender ungewöhnliche Konzepte, zum Beispiel Panache für die Persistenz.
Bernd Fischer beschäftigt sich seit seinem Studium der Elektrotechnik mit Software-Entwicklung und -Architektur und arbeitet heute als CTO bei der MindApproach GmbH in Dresden.
Über Assembler, Fortran, Pascal, C/C++ kam er vor mehr als 15 Jahren zur Programmiersprache Java und ist ihr seither weitestgehend treu geblieben.
Neben seiner hauptberuflichen Tätigkeit ist er in der JUG Saxony e.V. und der Dresdner Docker-Community aktiv.
(Abb. 1) combined into one picture publiziert unter: Creative Commons License Attribution 3.0 Unported (CC by 3.0 – https://creativecommons.org/licenses/by/3.0/)