ifeelgood v5
symproject icon 4

Java, I feel good - again!

Paradigmenwechsel

Wer einige Jahre in der Softwareentwicklung tätig ist, hat mindestens einen Paradigmenwechsel miterlebt. Die älteren von uns konnten erfahren, dass das immer wieder passiert. Innovation in der Hardwareentwicklung und Netzwerktechnik brachte neben immer kleineren und performanteren Systemen auch neue Architekturen hervor. Analog entwickelten sich neue Technologien im Bereich der Software. Betriebssysteme wandelten sich von Single-User- zu Multi-User-Systemen, Single-Tasking zu Multi-Tasking. Server mit Terminals und Einzelplatz-PC wurden durch Client-Server-Architekturen abgelöst. Sun prägte den Begriff „The network is the computer“. Der Siegeszug des Internet machte seine eigene Entwicklung durch und wandelte sich vom WebServer mit einfachen Webseiten zum Web 2.0 mit Javascript im Browser und einem ähnlichen Benutzererlebnis wie auf Rich-Clients. Programmiersprachen entwickelten sich weiter, neue kamen hinzu. Prozedurale Entwicklung wurde durch OOP abgelöst, Funktionale Programmiersprachen konkurrieren. Relationale Datenbanken werden durch Document- und Key-Value-Stores ergänzt. Neuronale Netze, Deep-Learning und Large-Language-Models bringen KI voran.

Neben Nischensystemen entwickelten sich im Bereich der Programmiersprachen Mainstreams, die so verbreitet sind, dass damit die Mehrzahl der Systeme entwickelt werden. Java gehört dazu. Diese Universalsprache stellt mit seinen unzähligen Bibliotheken praktisch das De-Facto-Werkzeug für die Entwicklung von Backend-Systemen dar.

 

Wo stehen wir heute?

Virtualisierte Systeme und Cloud-Architekturen schaffen Unabhängigkeit von dedizierter Hardware. Systeme können nahezu beliebig skaliert werden. Microservice-Architekturen lösen monolithische Systeme ab.

Für Java-Entwickler hat die Entwicklung in den letzten Dekaden jedoch eine Menge zusätzliche Komplexität gebracht. Unzählige Bibliotheken werden eingebunden, ohne automatisierte Verwaltung von Abhängigkeiten geht nichts mehr. Das Starten eines Java-Programms in der Java-Virtual-Machine mit Initialisierung aller beteiligten Bibliotheken und Frameworks dauert mitunter länger als 20 Sekunden. Im Vergleich: Ein Javascript-Entwickler ist es gewohnt, im Editor seine Datei zu speichern und sieht das Ergebnis nahezu in Echtzeit im Browser.

Kurz: Es fühlt sich nicht mehr gut an. Software in Cloud-Containern unter Kubernetes-Regie muss bei Bedarf 100-fach kurzfristig gestartet werden, um Last abfangen zu können. Wenn beispielsweise an einem Tag um 20:00 Uhr der Vorverkauf für begehrte Konzertkarten beginnt, soll das System nicht erst eine Minute verzögert auf die Lastspitze reagieren und tausende User frustrieren, sondern sofort und zwar automatisch, ohne dass das im Vorfeld geplant und konfiguriert sein muss. Da ist es einfach schlecht, wenn der Start eines zusätzlichen Containers 20 Sekunden dauert und 100 Megabyte Hauptspeicher benötigt. Irgendwie passte das alles nicht mehr richtig zusammen. Ist Java noch geeignet für eine moderne Cloud-Umgebung?

Oder ist eine neue Programmiersprache wie GO die Lösung? Haben sich andere etablierte Programmiersprachen besser an die Cloud-Welt angepasst? Dann jedoch lief uns Quarkus über den Weg und versprach Abhilfe. Doch was ist Quarkus?

Traditionelle Java-Stacks wurden für monolithische Anwendungen mit langen Startzeiten und großem Speicherbedarf in einer Welt entwickelt, in der es noch keine Cloud, Container und Kubernetes gab. Java-Frameworks mussten sich weiterentwickeln, um den Anforderungen dieser neuen Welt gerecht zu werden.

"Quarkus wurde geschaffen, um Java-Entwicklern die Möglichkeit zu geben, Anwendungen für eine moderne, Cloud-native Welt zu erstellen. Quarkus ist ein Kubernetes-natives Java-Framework, das auf GraalVM und HotSpot zugeschnitten ist und aus den besten Java-Bibliotheken und -Standards entwickelt wurde. Ziel ist es, Java zur führenden Plattform in Kubernetes- und serverlosen Umgebungen zu machen und Entwicklern ein Framework zu bieten, das eine größere Bandbreite an verteilten Anwendungsarchitekturen abdeckt." (Quelle: quarkus.io)

 

Wieder ein neues Framework?

Eine neue Lernkurve? Aber die Aussicht schien verlockend!

Also starteten wir einen Selbstversuch und entwickelten ein Projekt – überschaubar, aber anspruchsvoll genug, um nicht als trivial durchzugehen. Die Software sollte Ähnlichkeiten in Datenmengen finden, Daten persistieren und ein Web Frontend besitzen. Zum Einsatz kam im Backend Redis als Cache und Postgres als Relationale Datenbank. Im Frontend verwendeten wir Vue.js.

Bemerkbar macht sich beim Entwickeln der Software sofort das „Live Coding“ im Quarkus Dev-Modus. Nach einer Quelltextänderung im IntelliJ-Editor übernimmt Quarkus unmittelbar im Hintergrund die Änderungen in das laufende Backend. Ein Klick auf einen Button in der Browser-Anwendung ruft dann im Backend direkt den aktualisierten Code auf. Das macht Spaß und bringt enormen Produktivitätsgewinn beim Entwickeln.

Quarkus besitzt einen Reactive-Kern, der es auf einfache Weise ermöglicht, asynchrone Streams über verteilte Systeme zu implementieren. Was bedeutet das für den Entwickler? Der Code kann geschrieben werden wie sequenzieller synchroner Code, läuft aber asynchron. In der Anwendung kann das Frontend beispielsweise schon Daten anzeigen, während das Backend weitere Daten bereitstellt. Das Frontend konsumiert die Daten oder Events einfach aus einem Stream, der vom Backend aus unterschiedlichen Quellen gefüttert wird. Dabei ist es nicht notwendig, mit Threads oder Futures zu hantieren. All das läuft Event-gesteuert in einem Haupt-IO-Thread.

 

Alles ist ein Stream

Ob Daten oder Events dabei aus einer Datenbank kommen oder von einem Message-Broker, ist nur ein marginaler Unterschied. Sobald man sich an das Reactive-Modell gewöhnt hat, sieht vieles einfach nur noch aus wie ein Stream mit Daten und das vereinfacht die Entwicklung extrem.

Damit das funktioniert, existieren Reactive-Treiber für die meisten verbreiteten Datenbanken und Message-Broker für Quarkus, so auch für Postgres und Redis. Neben dem besseren Benutzererlebnis bedeutet dies für das Backend weniger Ressourcenverbrauch in Form von Hauptspeicher, Threads und Thread-Kontextwechseln und damit einen höheren Durchsatz. Das wiederum wirkt sich direkt positiv auf Energieverbrauch und Cloud-Kosten aus.

Die Möglichkeit zur Kompilierung in nativen Code führt schließlich dazu, dass vom Start der Applikation bis zur Beantwortung des ersten Requests nur wenige Millisekunden vergehen im Vergleich zu 10 bis 20 Sekunden bei Verwendung der JVM und herkömmlichen Frameworks.

 

Quarkus bietet weit mehr als das, aber diese Themen machten für uns einen Unterschied. Mehr Details zu Quarkus gibt es bei quarkus.io

 

Unser Eintauchen in Quarkus in unserem Projekt hat zu vielen positiven Erfahrungen und Lösungsansätzen geführt, vor allem aber zu einer Erkenntnis Java: I feel good – again!