Java hat inzwischen fast drei Jahrzehnte auf dem Buckel - und ist in nahezu allen Bereichen des täglichen Lebens angekommen, von Embedded Chips bis hin zu riesigen Serverfarmen. Die Kombination aus einer grundsoliden virtuellen Maschine und einer massiven Bibliotheken-Sammlung bildet ein fruchtbares Ökosystem für Code, der überall läuft.
Ein Bereich, in dem sich Java dabei zunächst schwergetan hat, ist die Serverwelt. Hier gilt es, mit Tausenden oder gar Millionen von Benutzerverbindungen zu jonglieren. In den frühen Jahren waren Java-Tools State of the Art, um serverseitige Anwendungen zu erstellen, die die Business-Logik für alle Benutzer durchsetzten. Java Frameworks wie J2EE, Hibernate, Spring und das Java-Servlet-Modell erleichterten es schließlich, performante Webanwendungen auf die Beine zu stellen.
Die Technologie florierte, bis JavaScript und Node.js aufkamen. Node.js erregte viel Aufmerksamkeit und die Entwicklergemeinde begann, auf die JavaScript-Laufzeitumgebung umzusteigen. Dafür gab es im Allgemeinen zwei Gründe:
Die Entwickler begrüßten die Möglichkeit, denselben Code auf dem Server und einem Browser-Client auszuführen.
Node.js-Server lieferten dank ihres reaktiven Modells oft einen erheblich schnelleren Durchsatz.
Das Java-Ökosystem passte sich entsprechend an, um konkurrenzfähig zu bleiben. Zunächst übernahmen einige Entwickler Tools wie Google Web Toolkit, um Java in JavaScript umzuwandeln. Als nächstes arbeiteten sie daran, Java auf dem Server zu beschleunigen. Frühe Java-Frameworks für Server wiesen jedoch eine Einschränkung auf: Jede eingehende Anfrage erhielt einen eigenen Thread. Das war eine saubere Methode, um eingehende und ausgehende Daten zu organisieren, aber auch sehr mühsam. Einen Thread zu erstellen, erfordert Tausende von Bytes an Overhead, wodurch die Anzahl der Benutzer, die ein Server bewältigen kann, eingeschränkt werden kann. Node.js verwendete ein anderes Modell, das es ermöglichte, viel mehr Benutzer zu bewältigen - ganz ohne Overhead.
In jüngerer Zeit haben Java-Entwickler Innovationen aus Node.js in den Java-Stack übernommen, insbesondere Cloud-native Java-Frameworks. Diese Frameworks imitieren den Ansatz von Node.js und unterstützen leichtgewichtige Funktionen, die auf Cloud-Maschinen laufen und schnell gestartet und beendet werden können. Sie kommen ohne zusätzliche Bibliotheken aus, um eine schnelle Bereitstellung auf den kleinsten verfügbaren Serverinstanzen zu unterstützen. Cloud-native Java-Frameworks sind darauf ausgelegt, Microservices-Konstellationen zu unterstützen, die unabhängig installiert und neu gestartet werden können. Sie werden in der Regel in Containern wie Docker oder Podman ausgeliefert, um möglichst schnelle Builds und Installationen zu ermöglichen.
Moderne Java-Entwickler, die eine Cloud-native Erfahrung suchen, haben eine Reihe von Optionen: Ein ideales Cloud-natives Java-Framework nutzt die umfassende Erfahrung, die in die Java-Plattform und ihre Bibliotheken von Drittanbietern investiert wurde - und passt diese so an, dass sie in der Cloud schneller und leichter ausgeführt werden können. Die folgenden Java-Frameworks wurden von Grund auf für Cloud-native Entwicklung und Bereitstellung geschaffen.
Micronaut
Die Entwickler von Micronaut wollten die besten Eigenschaften klassischer Java-Frameworks wie Spring und Grails übernehmen, etwa flexible Konfiguration und Dependency Injection. Dabei sollten enormer Speicherbedarf und langsame Startvorgänge über Bord geworfen werden - was für die Entwicklung von Microservices nicht wünschenswert ist. Sorgfältig entwickelte Annotationen liefern genügend Informationen für Dependency Injections, ohne die speicherfressende Reflexion, die in älteren Frameworks verwendet wird. Da die Konfiguration von Micronaut im Zeitraum der Kompilierung erfolgt, läuft der Code schneller und leichter.
Das Framework wurde entwickelt, um eine Vielzahl von JVM-basierten Sprachen (derzeit Java, Kotlin und Groovy) zu unterstützen und sie in verschiedenen Clouds auszuführen. Vordefinierte Konfigurationsdateien vereinfachen es, Server- oder Serverless-Funktionen in allen wichtigen Clouds bereitzustellen. Zudem existieren qualitativ hochwertige Dokumentationen für alle wichtigen Datenbankverbindungen.
Micronaut soll darüber hinaus auch die Kollaboration in Entwicklungsteams unterstützen. Eine HttpClient-Implementierung gehört zum Paket und erleichtert es, Unit-Tests zu schreiben, ohne Micronaut zu verlassen oder Mehrarbeit zu verursachen. Diese Tests sind darüber hinaus oft simpler und umfassender als die für dynamische Frameworks benötigten.
Micronaut ist aber nicht nur für die Entwicklung von Anwendungen mit Cloud-Funktionen gedacht. Das Framework ist allgemein genug, um traditionelle Rollen und einige Desktop-Anwendungen zu unterstützen. Seine enge Integration mit GraalVM ermöglicht es, mit Micronaut native Anwendungen zu erstellen.
Quarkus
Entwickler, die eine Mischung aus imperativem und reaktivem Code verwenden möchten, können dazu auf Quarkus zurückgreifen. Das Team hinter dem Framework antizipierte zunächst die häufigsten Use Cases für Cloud-native Entwicklung. Anschließend wurde das Rahmenwerk mit Beispielen aufgebaut, die diese Anwendungsfälle - nahezu konfigurationslos - unterstützen. Das Ergebnis lässt sich einfach in einen Container packen und über ein Kubernetes-Cluster bereitstellen. Dabei hat das Entwicklungsteam besonderes Augenmerk daraufgelegt, dass die Boot-Zeiten möglichst kurz ausfallen, damit Kubernetes-Cluster schnell skaliert werden können - ein ideales Merkmal für Funktionen, die nur sporadisch ausgeführt werden.
Eine Zielsetzung des Projekts: viele bestehende, in der Java-Community verbreitete Standards und Bibliotheken zu übernehmen und zu erweitern. Zum Beispiel definieren JAX-RS-Annotationen die REST-Endpunkte. Die Konfiguration beginnt mit dem Eclipse MicroProfile. Die Entwickler von Quarkus haben außerdem mehr als 50 Standardbibliotheken integriert. Die Wahrscheinlichkeit ist also hoch, dass Sie die Design Patterns in bestimmten Fällen wiedererkennen.
Sie können das grundlegende Quarkus-Framework für eine Vielzahl von Services nutzen. Ab Quarkus 2.8 fördern die Quarkus-Entwickler behutsam das RESTeasy Reactive-Modell. Dieses bietet eine einfachere, nicht-blockierende Struktur und Muster: Anstatt jeder Anfrage einen Thread zuzuweisen, erledigt eine Reihe von nicht blockierenden Threads die gesamte E/A und ruft Ihren Code bei Bedarf auf.
Quarkus bietet auch eine breite Palette von Deployment-Optionen. Trotz des Zusatzes "container first" kann das Framework auch auf Bare Metal laufen. Eine integrierte Konfigurationsoption namens Funqy, die es vereinfacht, Funktionen zu erstellen, die von AWS Lambda, Azure Functions, Knative und anderen akzeptiert werden.
Spring Cloud Function
Java-Entwickler sind mit dem Spring-Framework vertraut, da es seit rund zwei Jahrzehnten die Grundlage für viele Projekte bildet. Das Team hinter Spring hat sich dazu entschlossen, eine neue Version zu entwickeln, die sich besser für den Cloud-Einsatz (und einige andere Aufgaben) eignet. Dabei sind die Funktionen von Spring Cloud Function so konzipiert, dass sie schnell und einfach für eine Vielzahl von Aufgaben wie Webdienste, Stream-Verarbeitung oder Hintergrundarbeit eingesetzt werden können. Das Spring-Cloud-Function-Framework übernimmt die von Spring eingeführte Philosophie auf mehreren Ebenen. Die Cloud-Funktionen in diesem Framework unterstützen einen reaktiven oder imperativen Stil sowie eine hybride Mischung aus beidem.
Ein großes Ziel des Projekts ist es, eine Vielzahl von Optionen zu unterstützen: Es gibt Adapter, die die Funktionen von
Google Cloud Platform und einigen anderen gängigen Cloud-Funktionsumgebungen einbinden.
Solche Adapter existieren auch für wichtige Streaming-Frameworks wie Apache Kafka, Solace und RabbitMQ sowie die eigenständige Option Spring Cloud Stream. Paketierung und Deployment laufen weitgehend automatisiert ab, so dass Sie sich auf die Entwicklung der Funktionen konzentrieren können. Das Entwicklungsteam von Spring Cloud Functions hat auch hart daran gearbeitet, viele der üblichen Fallstricke und Herausforderungen der Cloud-Bereitstellung zu bewältigen:
Spring Cloud Skipper etwa jongliert Deployments über mehrere Clouds hinweg.
Spring Cloud Sleuth hilft bei der Fehlersuche, indem es Datenflüsse nachverfolgt.
Spring Cloud Security managt viele Tasks zur Absicherung von Applikationen, damit nur die richtigen Personen zugreifen können.
Das Projekt ist eine sehr gute Grundlage für die Verteilung von Geschäftsanwendungen über eine Vielzahl von Plattformen. Sobald Ihre Anwendungslogik in einem Cloud Function POJO gekapselt ist, kann sie in Dutzenden von verschiedenen Rollen ein Zuhause finden.
Vert.x
Das Ziel der Schöpfer von Vert.x lag darin, ein sehr schnelles Framework zu erschaffen. Dazu wollten sie den Event Loop vereinfachen und die Datenbankverbindung optimieren. Wie Node.js hat auch Vert.x eine einzige Ereignisschleife, die mehrere Verbindungen zeitgleich erlaubt. Zudem kommt das Threading-Modell von Java zum Einsatz, um Events mit mehreren Threads in einem Pool zu verarbeiten, die auf mehreren Kernen laufen können, wenn diese verfügbar sind.
Die Struktur soll es auch vereinfachen, die Pipeline zu erstellen, um Ereignisströme zu verarbeiten. Um unübersichtlichen Code mit mehrschichtigen Rückrufen zu vermeiden, greift sie auf Konstrukte wie 'Promises' und 'Futures' zurück. Die asynchronen Optionen sind sauberem, lesbarem Code zuträglich, der mit einfachen Ketten von Methodenaufrufen gefüllt ist, während sich die Ereignisse durch den Event Bus bewegen.
Was seine Vision angeht, ist das Vert.x-Entwicklungsteam nicht dogmatisch - es bezeichnet Vert.x als Toolkit und nicht als Framework. Der Code von Vert.x ist modular aufgebaut, so dass Sie selbst entscheiden können, welche Funktionen Sie verwenden möchten, und eine Architektur zusammenstellen können, die zu Ihrer Anwendung passt. Programmierer, die eine imperative Struktur bevorzugen, finden auch Support für die Co-Routinen von Kotlin.
Dieses Projekt ist Teil des Eclipse-Ökosystems. Eine Vielzahl von Versionen und Optionen bieten viel Freiheit. Der Vert.x-Anwendungsgenerator zum Beispiel erzeugt entweder Java- oder Kotlin-Code mit Dutzenden von möglichen Abhängigkeiten wie Template-Engines oder API-Unterstützung.
Eclipse MicroProfile
Das Eclipse-Team hat das MicroProfile-Projekt ins Leben gerufen, um Jakarta EE für den Betrieb kleinerer Microservices-Konstellationen anzupassen. Es eliminiert einen Teil des Overheads der größeren Plattform und bündelt gleichzeitig Bibliotheken, die für viele Microservices-Architekturen zum Standard gehören.
Der Ansatz ist vor allem für Entwickler attraktiv, die Code von größeren, älteren Java-EE- oder Jakarta-EE-Projekten migrieren. Ein Großteil der Konfiguration und Architektur bleibt gleich. In vielen Fällen sind die Anpassungen nur geringfügig. Einige Entwickler nutzen MicroProfile auch als "Sprungbrett" auf dem Weg zu moderneren Cloud-nativen Frameworks.
Dropwizard
Entwickler mit einer natürlichen Vorliebe für ältere, gut getestete Module werden Dropwizard mögen. Dessen Entwickler legen schon seit jeher Wert auf Stabilität und Ausgereiftheit. Sie haben Module für Datenbankverbindungen wie Hibernate gesammelt und Frameworks für Formulare und andere Standard-Webanwendungskomponenten integriert. Dropwizard vereinfacht darüber hinaus auch, Abhängigkeiten und Laufzeit-Wartungsprozesse wie Konfiguration und Protokollierung einzubinden.
Dropwizard eignet sich besonders für Teams, die bestehende Anwendungen überarbeiten und erweitern. Die Struktur ist mit den älteren, ausgereiften Ansätzen kompatibel, da sie auf ihnen aufbaut.
Einstiegs-Frameworks für Cloud-Plattformen
Manchmal ist es gar nicht nötig, besonders komplex oder aufwändig zu entwickeln. Alle Clouds bieten grundlegende Beispiele, die sich gut eignen, um damit zu beginnen, einfache Funktionen zu schreiben. Sie sind hauptsächlich dazu gedacht, einfache Entscheidungen zu unterstützen und Entwicklern einen schnellen Einstieg zu ermöglichen.
Das Entwicklungsteam von Google Cloud Platform hat beispielsweise sein grundlegendes Framework für Java-Funktionen (die in seinem Function-as-a-Service (FaaS) ausgeführt werden), in Open-Source-Form bereitgestellt. Damit erstellter Code soll sich schnell in die Standard-Trigger von GCP integrieren lassen, obwohl er auch auf jedem lokalen Rechner erfolgreich ausgeführt werden kann.
Auch Microsoft hat sein Java-Framework Open Source zur Verfügung gestellt. Das Modell umfasst mehrere Routinen, um Datenübertragungen zu vereinfachen. Wenn der Funktionsauslöser mit dem Aufruf Metadaten liefert, werden diese direkt vom Framework verarbeitet.
Mit diesen beiden Frameworks lassen sich viele einfache Aufgaben erledigen, indem Sie nur eine einzige Klasse mit einer einzigen Funktion schreiben. Bei komplizierteren Projekten kann es sinnvoll sein, diese grundlegenden Tools mit den beschriebenen Frameworks zu kombinieren. (fm)
Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation Infoworld.