dark
JAVAPRO-Java13

Java-EE vs. Spring für Microservices

Avatar

Hat Jakarta-EE (ehem. Java-EE) immer noch den Ruf schwergewichtig zu sein und hat damit geringe Chancen für eine Microservice-Architektur in Betracht gezogen zu werden? Ist das Spring-Framework die bessere Antwort auf die Frage mit welcher Java-Technologie wir unsere Services bauen? Einmal mehr stehen sich die ewigen Duellanten Jakarta-EE und Spring gegenüber, dieses Mal jedoch mit dem Fokus auf Microservices.

 

Jakarta-EE vs. Spring – schon wieder?

Microservices mit Spring-Boot sind für viele Java-Entwickler zur Norm geworden und werfen einen erheblichen Schatten auf Jakarta-EE, wie Java-EE jetzt heißt. Durch seine beständige Evolution schafft es Spring immer wieder neue Entwicklungsansätze in die Welt zu tragen. Somit passt es sich den neuen Gegebenheiten und unseren sich ständig ändernden Anforderungen an. Spring-Boot hat sicherlich erheblich dazu beigetragen, dessen Grundlagen in der Blütezeit der Microservices entstanden sind. Gerade mit diesem Framework lassen sich schlanke Anwendungen schreiben, die in Self-Contained-JARs ausgeliefert werden. Diese und noch weitere Komponenten aus dem Spring-Ökosystem eignen sich nahezu perfekt, um den Kriterien eines Microservice gerecht zu werden.

Im Schatten steht nun Jakarta-EE mit seinen starren Spezifikationen und dem dazugehörigen, schwergewichtigen Application-Server. Doch genau diesem Nachteil haben sich viele Anbieter solcher Produkte bereits angenommen. Hervorgehoben sei dort die Entwicklung um Thorntail (zu Anfang Wildfly-Swarm). Nach dem Motto Just-enough-application-server konnte man sich bei diesem seine sog. Fractions zusammenstellen. Diese bestehen lediglich aus einzelnen Komponenten von Wildfly sowie Implementierungen anderer Frameworks. Somit war es tatsächlich möglich, einen leichtgewichtigen Application-Server in einer Self-Contained-JAR zu erzeugen. Der Weg zum schlanken Microservice wurde also mit diesen und anderen Frameworks bereits geebnet.

Zu dem neuen Hype um JEE-Microservices hat unter anderem auch das im Frühjahr 2019 erschienene Framework Quarkus beigetragen. Unter dem Slogan „Supersonic-Subatomic-Java” verspricht das von Red Hat gesponserte Open-Source-Projekt „Container-First, Unifies Imperative and Reactive, Developer Joy & Best-of-Breed Libraries and Standards”. Quarkus ist somit die konsequente Evolution von Just-enough-application-server hin zu Container-First. Durch seine geringe Startup-Zeit im Millisekundenbereich und der geringen Größe der Self-Contained-JARs, eignet es sich perfekt zur automatischen Skalierung in Container-getriebenen Orchestrierungs-Tools, wie zum Beispiel Kubernetes. Nun werden auch hier sog. Extensions verwendet, die gängige Funktionalitäten zum Betreiben des Microservice mitbringen. Auch dadurch fühlt sich Quarkus an vielen Stellen an wie Thorntail, was daran liegen mag, dass Design und Implementierungsideen von Thorntail 4.x Proof-of-Concept in Quarkus eingeflossen sind. Mit diesen Extensions werden die beworbenen „Best-of-Breed Libraries and Standards” eingebracht. Unter anderem lässt sich hier die Spezifikation für Microservices aus dem Projekt Eclipse-MicroProfile verwenden. MicroProfile seinerseits hat sich der Herausforderung angenommen, einen Standard für die Entwicklung von Microservices auf Basis von Java zu definieren. Wie schon Java-EE ist auch MicroProfile eine Sammlung von Spezifikationen, mit welcher die Entwicklung von Microservices zu einer einfachen Aufgabe werden soll. Die Sammlung beinhaltet bewährte JEE-Technologien wie CDI, JAX-RS, JSON-B & JSON-P, aber auch neue, auf Microservices zugeschnittene Spezifikationen wie Health-Check, Metrics, Config ect. Siehe JAVAPRO 2-2019, „MicroProfile für Microservices mit Java-EE„von Alexander Ryndin. Mit diesen Erneuerungen kann Java-EE wieder in den Ring mit Spring steigen und sich im Kampf um das bessere Framework für Microservices messen.

Bestandteile von MicroProfile 3.3 (Abb. 1)

Vergleichskriterien

Um die beiden Technologien miteinander vergleichen zu können, bedarf es zunächst geeigneter Kriterien. Was aber macht einen klassischen Microservice aus? Einerseits handelt es sich per Definition um ein unabhängig lauffähiges und deploybares Artefakt. Mithilfe externer Konfiguration passt es sich dynamisch an seine jeweilige Laufzeitumgebung an, wie zum Beispiel einem Kubernetes-Cluster. Da ein Service bekanntlich selten allein kommt, benötigt er weiterhin auch Schnittstellen zur Außenwelt. Aufgrund hoher Interoperabilität werden diese in der Praxis gerne als REST-Endpoints realisiert. Um jederzeit über die korrekte Funktion und Verwendung des Service im Bilde zu sein, werden ferner ein Health-Check sowie möglichst einfach abrufbare Metriken benötigt. Natürlich handelt es sich bei den genannten Kriterien nur um eine subjektive Zusammenstellung, die keinen Anspruch auf Vollständigkeit oder Allgemeingültigkeit erhebt. Dennoch bietet sie eine erste Entscheidungsgrundlage zur Auswahl einer passenden Technologie. Nach Festlegung der Kriterien kann die Gegenüberstellung beginnen.

Spring-Boot sieht sich selbst als Wegbereiter für unabhängige, produktionsreife Anwendungen, die sich einfach starten lassen. Diesem Anspruch folgend bestehen die mittels hauseigenem Initializr erstellten Projekte im Wesentlichen nur aus einer startbaren Application-Klasse nebst zugehöriger Konfigurationsdatei (Listing 1).

(Listing 1)

Dank Spring-Boot-Maven-Plugin genügt ein simples mvn spring-boot:run auf der Kommandozeile, um den Microservice mitsamt eingebettetem Web-Server (meist ein Tomcat) auf Port 8080 hochzufahren. Nicht viel anders verhält es sich bei Projekten auf Basis von MicroProfile bzw. Quarkus, welche sich ihrerseits wahlweise über den MicroProfile- bzw. Quarkus-Starter oder mittels Kommandozeile unter Verwendung des Quarkus-Maven-Plugins mvn quarkus:create generieren lassen (Listing 2).

(Listing 2)

Ins Auge fallen im Vergleich lediglich zwei vorbereitete Docker-Files, welche der Kubernetes-Native-Philosophie des Quarkus-Projektes Rechnung tragen. Bevor über das Quarkus-Maven-Plugin auch hier ein eingebetteter Web-Server gestartet wird, erfolgt optional eine native Kompilierung (Listing 3).

(Listing 3)

Variable Konfigurationswerte lassen sich bei Spring bequem in umgebungsspezifische Properties- oder YAML-Dateien auslagern und an nahezu beliebiger Stelle injizieren (Abb. 2). Profiles ermöglichen überdies auch die gezielte Aktivierung oder Deaktivierung einzelner Beans (Listing 4).

(Abb. 2)

 (Listing 4)

Wenig Anlass zur Überraschung bietet der anschließende Blick auf Quarkus, weil die MicroProfile-Config-Spezifikation diesem bewährten Prinzip folgt:

(Abb. 3)

 (Listing 5)

RESTful Webservices werden bei Spring (Boot) als @RestController mit zugehörigen Methoden definiert und zur Laufzeit über einen Component-Scan des Application-Contextes erfasst (Listing 6).

(Listing 6)

Seit jeher ähneln die verwendeten Annotationen jenen des JAX-RS-Standards, welcher auch MicroProfile und damit Quarkus zugrunde liegt (Listing 7).

(Listing 7)

Um zur Laufzeit mehr über seinen Microservice zu erfahren, stellt Spring das Actuator-Projekt bereit. Dieses lässt sich leicht als Dependency über einen Starter einbinden und stellt daraufhin eine Vielzahl nützlicher Endpunkte über REST und JMX bereit. Ein Blick auf den Health-Endpunkt genügt bereits, um wichtige Lebensfunktionen zu messen (Abb. 4).

(Abb. 4)

Sind diese nicht aussagekräftig genug, lassen sich beliebig komplexe Health-Indicators selbst definieren und als Bean in den Context einbinden (Listing 8).

(Listing 8)

MicroProfile verfügt mit der Health-Spezifikation über ein vergleichbares Konzept, unterscheidet jedoch grundsätzlich zwischen Liveness (Service läuft) und Readiness (Service ist bereit). Entsprechende Endpunkte stellen auch hier alle relevanten Informationen für einen Health-Check zur Verfügung (Listing 9). Implementierungsabhängige Kennzahlen werden hingegen unter /vendor angeboten (Listing 10).

(Listing 9)

(Listing 10)

Ferner bietet auch MicroProfile die Möglichkeit, selbst definierte Metriken zu erheben und unter einem eigenen Endpoint (relativ zu /application) zur Verfügung zu stellen. Sollen etwa die Aufrufe einer bestimmten fachlichen oder technischen Methode gezählt werden, lässt sich dies leicht mithilfe der @Counted Annotation bewerkstelligen (Listing 11). Mittels @Gauge ist auch eine dynamische Berechnung zum Zeitpunkt des Abrufs möglich (Listing 12).

(Listing 11)

(Listing 12)

Zur Bestimmung der (zeitlichen) Lastverteilung kann ferner die Annotation @Metered herangezogen werden (Listing 13). Zu guter Letzt bietet @Timed von Haus aus sogar ein wenig Arithmetik (Listing 14).

(Listing 13)

(Listing 14)

Fazit technologisch:

Welche Technologie eignet sich denn nun besser für die Realisierung eines Microservice? Wie schon so oft präsentiert sich Spring (Boot) einmal mehr als Vorreiter und darf nach einigen Jahren intensiver Erprobung in der Praxis guten Gewissens als absolut produktionstauglich bezeichnet werden. Spätestens seit Einführung der Auto-Configuration hält sich der erforderliche Glue-Code zum Aufsetzen eines Microservice in sehr überschaubaren Grenzen, sodass der Entwickler sich nun endgültig auf die Implementierung der Fachlogik konzentrieren kann. Insgesamt präsentiert sich das Spring-Ökosystem heutzutage als derart mächtig, dass sich schwerlich ein Bereich findet, welcher noch nicht durch ein entsprechendes Projekt nebst Starter abgedeckt wird. Schafft der Einsatz solcher Technologie im konkreten Projekt einen Mehrwert, kann dies bereits ein Entscheidungskriterium sein. An dieser Stelle lässt sich insbesondere Spring-Data heranführen, welches die Implementierung eigener Logik für Datenbankzugriffe nahezu überflüssig macht. Eine vergleichbare Funktionalität bietet MicroProfile aktuell noch nicht an. Losgelöst davon existiert bei Quarkus mit Panache zwar ein ähnliches Konzept, das aber mit einigen Restriktionen einhergeht, wie zum Beispiel Public-Attribute in den Entities. Seit jeher bestand eine Maxime des Spring-Frameworks auch darin das Rad nicht neu zu erfinden, sondern stattdessen die Einbindung anderer, am Markt etablierter Libraries und Frameworks (Hibernate, Netflix OSS etc.) zu vereinfachen. Ist die Integration spezieller Technologien angedacht, sollte deren Unterstützung ebenfalls berücksichtigt werden. Auf der anderen Seite reduziert die Verwendung vordefinierter Starter- und Parent-POMs zwar einen eigenen Konfigurationsaufwand, erzeugt aber auch starke Abhängigkeiten, die nicht außer Acht gelassen werden sollten.

Dass totgesagte länger leben, beweist das Trio aus Jakarta-EE, MicroProfile und Quarkus eindrucksvoll. Die meisten enthaltenen Enterprise-Standards (CDI, JAX-RS, ect.) sind nicht nur hochgradig praxiserprobt, sondern auch vollständig durchspezifiziert. Vielen Entwicklern dürfte ein Großteil eines solchen Frameworks daher allzu bekannt vorkommen, sodass leicht auf bereits gesammeltes Wissen und Know-How zurückgegriffen werden kann. Vervollständigt werden diese Standards durch neue, vollständig am Bedarf moderner Microservices orientierter Spezifikationen wie etwa Health, Metrics oder Fault-Tolerance. Die Einbindung und Konfiguration von Third-Party-Libraries ist nicht mehr erforderlich, weil Implementierungen wie Quarkus diese bereits über entsprechende Extensions mitliefern. Darüber hinaus beschränkt sich die Konfiguration auch bei MicroProfile auf das Wesentliche. Beides vereinfacht das Setup deutlich und kann so zu einer Kostenersparnis führen.

Stichwort Ersparnis: Speziell Quarkus setzt auf GraalVM und kann daher nativ kompiliert werden – inklusive Live-Coding im Development-Modus. Im Ergebnis reduzieren sich Startzeit und Round-Trips nach Änderungen drastisch. Jene Tage, in denen Java-EE als schwergewichtig, langsam und kompliziert galt, gehören damit endgültig der Vergangenheit an. Wie schon bei klassischen Application-Servern wirkt sich die Vorauswahl konkreter Implementierungen durch den jeweiligen Anbieter trotz Extensions negativ auf die Flexibilität aus. Manche Entwickler sprechen in diesem Zusammenhang zuweilen von einer Hassliebe.

Aus technischer Sicht ergibt sich also das Bild, dass man mit beiden Technologien wunderbar Microservices entwickeln kann. Man muss daher nicht zwingend von Java-EE auf Spring oder umgekehrt umsatteln.

Fazit organisatorisch:

Viel mehr spielen auch organisatorische Faktoren eine Rolle. Insbesondere ist es ratsam abzuwägen, welche Technologie die Umsetzung individueller Anforderungen besser unterstützt. Betrachten wir zunächst Spring, so kann man dieses flexibel auf die speziellen Anforderungen innerhalb der Anwendung anpassen. Durch seine einfache Integration anderer Libraries und Frameworks kann der Funktionsumfang bei Bedarf schnell erweitert werden. Auf der anderen Seite sollte aber Wert daraufgelegt werden, dass eben diese tatsächlich auch einen Großteil der Anforderungen abdecken. Bindet man stattdessen nach Belieben weitere Projekte ein, kann der eigene Service schnell auch zu einer JAR-Hölle werden. Deshalb gilt es zunächst gründlich zu evaluieren, welche Projekte in welchem Umfang die Umsetzung der eigenen Anforderungen unterstützen. Für diese Aufgabe werden jedoch Mitarbeiter benötigt, die ausreichend Zeit und Wissen mitbringen.

Auf der anderen Seite haben wir das Trio aus Jakarta-EE, MicroProfile und Quarkus, das zu vielen Anforderungen an Microservices schon die passenden Implementierungen durch Libraries mitbringt. Diese haben sich in der Praxis bewährt und wurden von der Community als tauglich befunden. An dieser Stelle wird die Zeit der Evaluation gespart und die Mitarbeiter können sich – gemäß dem ursprünglichen Versprechen von Java-EE – auf die eigentliche Geschäftslogik konzentrieren. Problematisch wird es bei Anforderungen die vom Standard abweichen. Dann erhöht sich der Implementierungsaufwand schnell um ein Vielfaches.

Weiterhin sollte man die Vorlieben der eigenen Mitarbeiter nicht außer Acht lassen. Auch wenn beide Technologien ihre individuellen Vor- und Nachteile besitzen, fühlen sich Entwickler gewöhnlich entweder im Spring- oder im JEE-Ökosystem zu Hause. Das Wissen, welches man sich über Jahre angeeignet hat, sollte weiterhin sinnvoll genutzt und für die Umsetzung von Microservices und deren neuen Herausforderungen herangezogen werden. So muss auch kein Mitarbeiter befürchten, sich unverhofft in einer Umgebung wiederzufinden, mit der er nicht vertraut ist.

 

Daniel Krämer

Daniel Krämer ist bereits seit mehreren Jahren als Software Engineer für die anderScore GmbH tätig und begeistert sich für die durchdachte Software-Architektur und strukturiertes Design. In seinen Kundenprojekten setzt er sich überwiegend mit individueller Java- und Web-Entwicklung sowie Fragestellungen rund um Migration und Integration (z.B. Microservices) auseinander. Er ist dafür bekannt, auch für komplexe Szenarien effektive und effiziente Strategien zur Test-Automatisierung zu finden. Daniel teilt seine Kenntnisse und Erfahrungen gerne mit anderen Entwicklern. Zu den Inhalten regelmäßiger abgehaltener, öffentlicher Trainings zählen Themen wie Microservices, Spring, Apache Wicket und jQuery.

GitHub | E-Mail

 

 

 

Maik Wolf

Maik Wolf ist bei der Kölner anderScore GmbH als Fullstack-Entwickler für Java Enterprise Projekte & Agile Coach im Kundeneinsatz. Durch seine Expertise in den Branchen Logistik, Managed-Hosting, Groß- & Einzelhandel und Dialogmarketing verfügt er über vielseitige und intensive Einsichten in verschiedene Softwarelandschaften und in ganz unterschiedliche Geschäftsanforderungen. Da sein zweites Herz für agile Arbeitsmethoden schlägt, integriert und optimiert er diese in seinen Projekten.

Blog | Twitter | E-Mail

Total
0
Shares
Previous Post

Keycloak individuell erweitern

Next Post

Boilerplate-Code minimieren mit Lombok

Related Posts

Die Zukunft von Containern – Was kommt als Nächstes?

Vielleicht haben Sie schon die Schlagworte gehört, die in aller Munde sind, wenn es um die Zukunft von Containern geht. Seltsame Namen wie "Micro-VMs"… "Unikernel"… "Sandboxes"… Haben Sie sich gefragt, was diese Dinge sind und wie Sie sie nutzen können? Oder sollten Sie diese überhaupt verwenden?
Avatar
Read More