Quarkus macht Java fit für die Cloud

Keine Kommentare

Vor über zwanzig Jahren wurde Java vorgestellt, und es ist bis heute eine der erfolgreichsten Programmiersprachen. Durch sein Alter ist Java jedoch nicht auf die Cloud optimiert und fällt hier hinter anderen Sprachen zurück. Java ist eher auf ein monolithisches System mit langer Laufzeit und vollem Zugriff auf die verfügbaren Ressourcen ausgelegt. Außerdem sind Eigenschaften, wie die Unterstützung von verschiedenen Plattformen, heute nicht mehr so relevant wie früher. Durch Container wird dies einfach ermöglicht.

Die Anforderungen an eine Programmiersprache wandeln sich durch neue Gegebenheiten. In Cloud- und Serverless-Umgebungen fällt die Performance von Java hinter anderen Sprachen, wie Node.js und Go, zurück. Java hat, verglichen mit diesen Sprachen, sehr lange Startzeiten, produziert große Artefakte und hat einen großen Speicherbedarf. Um in diesem Gebiet konkurrenzfähig zu bleiben, muss Java seinen Fokus ändern.

Ein Framework, das hierbei helfen kann, ist Quarkus von Redhat. Quarkus wirbt damit, dass es ein „Supersonic Subatomic Java“ liefere. Aber was verbirgt sich hinter diesem Slogan?

Was bietet Quarkus?

Eines der Ziele von Quarkus ist es eine sinnvolle Möglichkeit zu bieten, Java in Cloud-Umgebungen zu nutzen. Außerdem soll das Arbeiten mit dem Framework möglichst einfach und angenehm sein. Um diese Ziele zu erreichen, bietet Quarkus folgende Eigenschaften:

Container-First

Bei der Optimierung von Quarkus steht besonders der Einsatz in Cloud- und Container-Umgebungen im Fokus. Hierbei sind besonders geringe Speichernutzung und schnelle Startzeiten wichtig. Quarkus erreicht diese Eigenschaften durch verschiedene Optimierungen:

Sehr gute Unterstützung von Graal/SubstrateVM

Mit Hilfe der GraalVM kann eine Java-Anwendung zu einer nativen Anwendung kompiliert werden. Hierzu werden in der GraalVM die JVM-Class-Dateien weiterverarbeitet und zu nativen Binaries kompiliert. Anschließend werden diese in der SubstrateVM, einer abgespeckten Version der Java HotspotVM, ausgeführt. Diese native Anwendung hat die Vorteile, dass sie wesentlich schneller startet und weniger Speicher benötigt.

Verarbeitung der Metadaten zur Build-Zeit

Bei herkömmlichen Java-Anwendungen werden viele Daten erst beim Starten der Anwendung verarbeitet, und es gibt viele Klassen, die nur für das initiale Deployment der Anwendung benötigt werden. Bei Quarkus wird so viel wie möglich zur Build-Zeit verarbeitet. Dies führt dazu, dass im entstehenden Artefakt Klassen weggelassen werden können und beim Starten der Anwendung weniger Daten verarbeitet werden müssen, sodass die Speichernutzung sinkt und die Anwendung schneller startet.

Reduzierte Nutzung von Reflection

Viele Java-Frameworks setzen auf die umfangreiche Nutzung von Reflection. Reflection benötigt jedoch zusätzliche Ressourcen zur Laufzeit. Um die Startzeit und den Ressourcenbedarf zu verringern, wird bei Quarkus soweit wie möglich auf Reflection verzichtet.

Vorverarbeitung des Boot-Prozesses

Beim Build-Prozess für die nativen Programme wird ein Großteil des Boot-Prozesses ausgeführt und serialisiert. Dies führt dazu, dass beim Starten der Anwendung nur das serialisierte Ergebnis eingelesen werden muss, sodass die benötigte Startzeit noch weiter sinkt.

Alle diese Schritte führen dazu, dass eine Anwendung, die mit Quarkus nativ gebaut wurde, wesentlich schneller startet und weniger Ressourcen benötigt als eine vergleichbare Java-Anwendung. In der folgenden Grafik kann man die Performance von Quarkus im Vergleich zu herkömmlichen Java-Anwendungen sehen.

Quarkus Performance – Quelle: QuarkusCreative Commons 3.0

Angenehmes Entwickeln

Quarkus soll nicht nur ein gutes Framework für die Entwicklung von Webanwendungen und Microservices sein, sondern den Entwickler hierbei auch bestmöglich unterstützen. Die angebotenen Funktionen sollen möglichst einfach und intuitiv sein. Außerdem soll so wenig Konfiguration wie möglich notwendig sein, um einen schnellen Einstieg zu ermöglichen.

Einheitliche Konfiguration

Quarkus bietet eine einzelne Konfigurationsdatei, in der alle nötigen Konfigurationen vorgenommen werden können. In dieser Datei kann sowohl Quarkus selbst als auch die angebotenen Erweiterungen konfiguriert werden. Außerdem werden für alle möglichen Konfigurationsparameter Standardwerte geboten, sodass viele Dinge nicht manuell konfiguriert werden müssen. Die Konfigurationsdatei ist eine properties-Datei, die auf dem Eclipse-MicroProfile basiert.

quarkus.http.port: 8090
quarkus.datasource.url: jdbc:postgresql://localhost:5432/postgres
quarkus.datasource.driver: org.postgresql.Driver
quarkus.datasource.username: postgres
quarkus.datasource.password: postgres

 

Hot Code Reloading

Bei der typischen Entwicklung mit Java wird eine Anwendung für jede durchgeführte Änderung neu gestartet. Dies ist ein großes Problem für die Performance des Entwicklers. Quarkus bietet hier einen Reloading-Mechanismus, der die Entwicklung vereinfachen soll: Wenn man eine Änderung am Code vornimmt und anschließend die Funktion aufruft, führt Quarkus einen Refresh durch und liefert direkt das neue Ergebnis.

Das Reloading funktioniert so, dass Quarkus, sobald es einen Http-Request erhält, diesen pausiert und prüft, ob etwas am Code geändert wurde. Der geänderte Code wird anschließend kompiliert und die Anwendung neu deployed. Anschließend wird der HTTP-Request an die Anwendung weitergeleitet. Da der Prozess des Kompilierens sehr schnell durchgeführt wird, fällt er beim Entwickeln nicht negativ auf.

Quarkus ist opinionated

Der Code wird so optimiert, wie es das Team von Quarkus als sinnvoll erachtet.Der Entwickler erhält bewusst Vorgaben zur Nutzung, um die Designentscheidungen zu erleichtern. Es gibt jedoch auch die Möglichkeit, direkt auf die von Quarkus verwendeten Frameworks zuzugreifen, um die Flexibilität zu bieten, ggf. auch Sonderfälle zu implementieren, die vom Quarkus-Team so nicht vorgesehen waren.

Nutzung von bewährten Bibliotheken und Standards

Quarkus ist ein Fullstack-Framework, das eine ganze Reihe von Bibliotheken integriert. Hierbei wird stark auf bewährte Bibliotheken, wie z.B. Hibernate, RESTEasy oder Apache Kafka, gesetzt. Das Ziel hierbei ist, dass Entwickler nicht viel Zeit investieren müssen, um neue Technologien zu lernen, sondern mit bewährten Bibliotheken und Standards arbeiten können. Quarkus sieht sich hierbei nicht nur als einfacher Konsument, sondern möchte auch daran teilhaben die verwendeten Bibliotheken zu verbessern.

Viele der Bibliotheken, die in Quarkus verwendet werden, müssen vorher für das Framework optimiert werden. Quarkus verzichtet so gut es geht auf Reflection, und da viele Frameworks stark darauf ausgelegt sind, müssen diese angepasst werden. Es stehen hierzu eine Reihe von Erweiterungen bereit, welche für Quarkus genutzt werden können. Diese liefern direkt die optimierten Versionen der Bibliotheken.

GraalVM und native Anwendungen

Mit Hilfe der GraalVM können die Anwendungen, die mit Quarkus entwickelt werden, zu nativen Anwendungen kompiliert werden. Die GraalVM verwendet einen Ahead-Of-Time Compiler, um aus dem Java-Code nativen Maschinencode zu erstellen. Das hieraus entstehende Programm wird nicht mehr in der Java Hotspot-VM ausgeführt, sondern in der SubstrateVM.

Die hierbei entstehenden Anwendungen bieten einige Vorteile gegenüber klassischen Java-Anwendungen: Der Memory-Footprint ist wesentlich kleiner, da durch den Ahead-of-Time-Compiler Optimierungen im Build-Prozess gemacht werden können. Ein weiterer Punkt, der im Container-Umfeld wichtig ist, ist der Verzicht auf Java als Ausführungsumgebung. Die entstehenden Container sind hierdurch wesentlich kleiner.

Eine erste kleine Anwendung erstellen

Momentan wird zum Anlegen einer Quarkus-Anwendung das Maven- oder Gradle-Plugin empfohlen. Es ist aber auch geplant später einen Webstarter anzubieten.

Mit folgendem Befehl kann eine einfache Anwendung über das Maven-Plugin erstellt werden:

mvn io.quarkus:quarkus-maven-plugin:0.12.0:create \
-DprojectGroupId=dev.lohmann \
-DprojectArtifactId=quarkus \
-DclassName="dev.lohmann.quarkus.GreetingResource" \
-Dpath="/hello"

Es wird ein neues Maven-Projekt mit den benötigten Libraries und einer ersten REST-Ressource angelegt. Das Projekt ist in diesem Zustand lauffähig und kann direkt gestartet werden.

Der Code besteht momentan aus einer einzigen REST-Ressource, welche unter dem Pfad /hello einfach den String hello zurück gibt. Die Implementierung basiert hierbei auf Standard JAX-WS.

@Path("/hello")
public class GreetingResource {

   @GET
   @Produces(MediaType.TEXT_PLAIN)
   public String hello() {
       return "hello";
   }
}

Für die Entwicklung kann die Anwendung nun im Entwickler-Modus mit live reload gestartet werden.

./mvnw compile quarkus:dev

Um die Anwendung für die produktive Nutzung bereit zu machen, kann dann eine native Anwendung gebaut werden. Die Anwendung kann hierbei entweder für das aktuelle System oder speziell für eine Container-Umgebung gebaut werden. Wenn man die Anwendung für eine Container-Umgebung baut, wird als Ausführungsumgebung ein 64-Bit Linux angenommen.

Für lokale Builds:

./mvnw package -Pnative

Für Container-Umgebungen:

 ./mvnw package -Pnative -Dnative-image.docker-build=true

Die entstehende Datei kann im target-Ordner gefunden werden und lässt sich nativ starten. In der Datei  ist die SubstrateVM direkt enthalten, sodass keine spezielle Ausführungumgebung benötigt wird.

Anschließend kann noch ein Docker-Image erstellt werden, in welchem die Anwendung laufen kann.

docker build -f src/main/docker/Dockerfile.native -t quarkus/hello .

Wenn man mit diesem Image einen Container startet, wird die Anwendung direkt ausgeführt.

Container mit Docker-Image starten

Mit diesem Beispiel sollte schon einmal ein kleiner Einblick in die Entwicklung mit Quarkus gezeigt werden. In einem weiteren Blogpost werde ich zeigen, wie man einige der Erweiterungen nutzt und eine richtige Anwendung mit Quarkus entwickeln kann.

Fazit

Momentan ist Quarkus noch am Anfang seiner Entwicklung. Doch schon jetzt kann man den Nutzen des Frameworks gut erkennen. Mit Hilfe von Quarkus lassen sich einige große Schwächen von Java in Cloud-Umgebungen beheben. Es hilft dabei, dass Java konkurrenzfähig zu anderen Sprachen bleibt. Bei Quarkus wird viel Wert darauf gelegt, sich an bewährten Bibliotheken und Standards zu orientieren und dem Entwickler einen möglichst einfachen Einstieg zu gewähren.

Da  sich Quarkus momentan noch in der Betaphase befindet, sollte es noch nicht produktiv eingesetzt werden. Ein Großteil der bereitgestellten Funktionen kommt jedoch aus bewährten Bibliotheken, wie Hibernate und RESTEasy, und kann schon verwendet werden, sodass erste Tests mit Quarkus problemlos machbar sind.

Quarkus ist ein sehr interessantes Framework und kann gut in Cloud-Umgebungen wie Kubernetes eingesetzt werden. Auch das Zusammenspiel mit Tools wie Knative kann sehr interessant sein. Da die Startzeit von nativen Quarkus-Anwendungen sehr kurz ist, kann es auch als Serverless-Anwendung gut deployed werden. Alle Ressourcen der Anwendung können dann freigegeben werden, falls diese nicht genutzt wird, und bei einem Request kann schnell ein neuer Container gestartet werden.

Ich persönlich bin sehr gespannt, wie sich Quarkus entwickeln wird. Ich denke, dass Java mit Quarkus einen Schritt in die richtige Richtung macht. Wenn die Anzahl der verfügbaren Erweiterungen noch weiter steigt, könnte sich Quarkus als eine gute Alternative für die Entwicklung von Java-Cloud-Anwendungen entwickeln.

 

Weitere Informationen:

Quarkus: https://quarkus.io/

Codebeispiel: Github

Enno Lohmann

Enno ist seit 2018 als Software Engineer bei der codecentric AG tätig. Sein Schwerpunkt liegt in der Fullstack Java Entwicklung mit Spring Boot und modernen Webframeworks. Außerdem interessiert er sich für Cloud- und Containertechnologien.

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.