Über unsMediaKontaktImpressum
Lars Röwekamp 14. Februar 2017

Microservices mit Java EE: Alptraum oder Dreamteam?

Auf den ersten Blick scheinen Java EE und Microservices nicht wirklich gut zueinander zu passen. Auf der einen Seite ein technologisches Urgestein, welches seit mehr als 15 Jahren dafür berühmt-berüchtigt ist, einer trägen Evolution zu unterliegen. Auf der anderen Seite eine von modernen Firmen wie Netflix, Amazon, Twitter und Co. in schnellen Schritten getriebene Software-Revolution. Dabei würden doch auch die Java EE-Entwickler so gerne in der neuen Wunderwelt der Microservices mitspielen...

Auch wenn der Java Enterprise Standard Java EE eigentlich alles mitbringt, was es braucht, um eine Microservice-basierte Anwendung zu implementieren, hält sich in der Enterprise-Community hartnäckig das Gerücht, dass dies nur mit dem proprietären Spring Stack (Boot und Cloud) sinnvoll möglich ist. Dabei spricht in der Praxis eigentlich nichts gegen die Verwendung von Java EE. Ganz im Gegenteil. Gerade für Teams, die bereits Erfahrungen mit Java EE gesammelt haben, bietet sich dessen Verwendung an. Die Möglichkeit, etablierte und bekannte APIs anwenden zu können, ist ein nicht zu unterschätzender Vorteil. Die Migration hin zu Microservices bringt schon mehr als genug technische und organisatorische Herausforderungen mit sich. Da freut man sich über alte Bekannte.

Warum aber besitzt dann Java EE bei vielen Enterprise-Entwicklern und -Architekten nach wie vor einen eher schlechten Ruf, wenn es um das Thema Microservices geht. Machen wir uns auf die Suche...

Microservices in a Nutshell

Microservices sind fachlich in sich geschlossene Einheiten – im DDD-Jargon auch Bounded Context genannt – die von entsprechend fachlich orientierten Teams separat entwickelt, getestet und deployed werden können. Jeder Service läuft in einem eigenen Prozess. Dies verhindert, dass sich im Falle eines Laufzeitproblems negative Effekte für die anderen Services ergeben können. Da jeder Service zunächst einmal für sich zu betrachten ist, kann eine Skalierung bedarfsgerecht, also je Fachlichkeit bzw. je Service, erfolgen.

Zum Austausch mit anderen Services wird eine technologieneutrale, häufig auf JSON-Payload basierende Schnittstelle verwendet. Diese kapselt die Implementierungsdetails des Service und versteckt sie so vor der Außenwelt. Optimierungen oder Anpassungen innerhalb des Services sind solange problemlos möglich, solange sie nicht zu einer Inkompatibilität der Service-Schnittstelle führen. Die Kommunikation der Services untereinander erfolgt in der Regel über leichtgewichtige Protokolle, wie zum Beispiel REST over Http (synchron) oder Messaging (asynchron).

Die Entwicklung des Services selbst kann in einer beliebigen Programmiersprache erfolgen, solange diese in der Lage ist, die oben beschriebene Schnittstelle und die zugehörigen Protokolle zu bedienen.

Java EE Api

Ein Blick auf die APIs von Java EE [1] zeigt, dass die eben skizzierten Charakteristika von Microservices problemlos mit dem Java Enterprise Standard erfüllt werden können. Mit Hilfe von JAX-RS lassen sich RESTful Endpoints erstellen, deren eingehende Requests via Bean Validation auf Korrektheit geprüft werden können. Alternativ kann JAX-WS zur Realisierung eines anderen Endpoint-Typs verwendet werden.

Die Parsing- und Binding-APIs JAXB, JAXP und JSON-P können im Anschluss zum automatischen Marshalling bzw. Unmarshalling der übertragenen Payload hin zu Java-Objekten – vice versa – herangezogen werden. Für die Realisierung der fachlichen Logik innerhalb des Services, inkl. potentieller Datenhaltung, kommen CDI oder EJB sowie JPA inkl. Dependency Injection zum Einsatz. Sollen die Services nicht synchron via REST sondern asynchron via Messaging kommunizieren, steht die JMS API zur Verfügung.

Soweit so gut. Wenn die Java EE APIs aber alles erfüllen, was das Entwicklerherz begehrt, wo bitte liegt dann das Problem?

Das Leid mit der Runtime

Das eigentliche Problem von Java EE im Zusammenspiel mit Microservices zeigt sich nicht während der Entwicklung, sondern erst im Deployment und zur Laufzeit. Gemäß Spezifikation wird eine Java EE-Anwendung als WAR- oder EAR-Artefakt verpackt und in einem Application Server deployed. Der Server stellt die notwendige Infrastruktur für Security, Datenbankanbindung, Messaging Broker und vieles mehr.

Ein erster, naiver Ansatz könnte also so aussehen, dass die verschiedenen Microservices einer Anwendung als separate WARs gepackt und anschließend in einem Application Server deployed werden. Dies hätte den Vorteil, dass sich die Services die durch den Server zur Verfügung gestellte Infrastruktur problemlos teilen könnten (s. Abb.1).

Was sich zunächst erst einmal recht attraktiv anhört, ist aber gleichzeitig auch der große Nachteil des Szenarios. Zur Erinnerung: "Microservices sind fachlich in sich geschlossene Einheiten ... die ... separat entwickelt, getestet und deployed werden können. Jeder Service läuft in einem eigenen Prozess."

Ziel ist es also, eine größtmögliche Unabhängigkeit der Services untereinander zur Entwicklungs-, Deploy- und Laufzeit herzustellen. Genau dies ist aber nicht gegeben, wenn alle Services in einem Server bzw. einen Server-Cluster deployed werden. Zum einen beeinflusst das Deployment oder genauer gesagt der damit verbundene Ressourcenverbrauch eines Services das Laufzeitverhalten aller anderen Service. Zum anderen kann der Ausfall eines einzelnen Services dafür sorgen, dass der Server hängt und somit die anderen Services ebenfalls nicht mehr angesprochen werden können.

Weniger ist mehr – Variante 1

Um das eben beschriebene Problem zu umgehen, könnte man natürlich hergehen und je WAR – also je Service – einen eigenen Server aufsetzen. So würde man die gewünschte Unabhängigkeit der Services erreichen und gleichzeitig den Vorteil der managed Infrastruktur durch den Server nicht verlieren (s. Abb.2).

In einer Anwendung mit zweistelligen oder gar dreistelligen Service-Instanzen bedingt dies allerdings einen nicht zu unterschätzenden operativen Aufwand für System-Konfiguration, Deployment, Management und Monitoring der vielen, vielen Server/Services. Das Szenario ist somit nur dann realistisch umzusetzen, wenn man es weitestgehend automatisiert, indem zum Beispiel die einzelnen Server als Docker-Images vorliegen.

Zur Entwicklungszeit wirkt sich das beschriebene Vorgehen extrem positiv aus. Nach Änderungen an einem Service wird dieser bzw. das zugehörige WAR zunächst neu gebaut, was in der Regel nur einige wenige Sekunden dauern dürfte. Im Anschluss wird das Docker-Image, basierend auf einem dedizierten JDK, einem Java EE Server Basis-Image, ein wenig Health-Check-Infrastruktur und dem neuen WAR erstellt. Auch dies sollte nicht länger als einige wenige Sekunden dauern, da – mit Ausnahme von dem WAR – immer wieder auf bereits bekannte, nicht veränderte Komponenten zurückgegriffen werden kann. 

Dem Vorteil zur Entwicklungszeit steht allerdings ein Nachteil zur Laufzeit entgegen: Die Größe der Images bzw. der damit verbundene Ressourcenverbrauch. Da wir je Docker-Image einen vollständigen, spezifikationskonformen Java EE Server – Full Profile oder Web Profile – gestartet haben, ist die Last auf den Servern entsprechend groß. Dies gilt auch dann, wenn wir eigentlich nur einen Bruchteil der Funktionalität des Java EE Servers für unseren Microservice benötigen.

Weniger ist mehr – Variante 2

Was aber wäre, wenn wir für unseren Service nur diejenigen Bestandteile des Servers nutzen bzw. bundeln würden, die wir tatsächlich benötigen und somit eine Art flexibles "Micro Java EE" nutzen könnten. Stellt unser Service zum Beispiel eine RESTful-Schnittstelle nach außen bereit, macht es sicherlich keinen Sinn, schwergewichtige Frameworks wie JSF oder JSP mit zu deployen. Natürlich wäre das in Summe dann nicht mehr spezifikationskonform, die verwendeten APIs selbst wären es aber schon.

Genau dies haben auch etliche Java EE Application Server-Hersteller erkannt und bieten verschiedenste, proprietäre Lösungen zur Umsetzung des eben beschriebenen Mechanismus an. Unabhängig vom jeweiligen technologischen Ansatz ist das Prinzip bei allen Herstellern mehr oder minder identisch. Die für den Microservice notwendigen Sever-Komponenten werden mit dem Code des Services und einem Server-spezifischen Bootstrapping kombiniert und als JAR – also als ausführbares Java-Programm – gebundelt.

Der Vorteil dieses Ansatzes liegt klar auf der Hand: Der resultierende Microservice beinhaltet genau die Bestandteile, die er zur Laufzeit benötigt. Nicht mehr und nicht weniger. So zumindest die Theorie.

Je nach Server-Hersteller können sich nämlich die Granularität der auswählbaren Komponenten, sowie die Möglichkeiten zum Eingriff in das Bootstrapping und das damit verbundene Classloading, stark unterscheiden. Auch scheint bei fast allen Herstellern das Abhängigkeitsmanagement der Server-Komponenten untereinander noch nicht zu 100 Prozent optimiert zu sein, sodass auch bei eigentlich kleinen Services am Ende doch recht große Artefakte entstehen.

Weitere Unterschiede finden sich bei den angebotenen Zusatzfunktionalitäten jenseits der Java EE-Spezifikation. Gemeint sind hierbei die für Microservices essentiellen Aspekte, wie zum Beispiel Health-Check, Resilience, Security, Service-Registry & -Discovery, Monitoring oder Configuration-Management. Zu den bekanntesten Vertretern des flexiblen "Micro Java EE" zählen derzeit Wildfly Swarm [2], Payara Micro [3], KumuluzEE [4], TomEE Shades [5] und Websphere Liberty Profile [6].

Es soll an dieser Stelle nicht verschwiegen werden, dass mit Hilfe der eben beschriebenen zweiten Variante zwar kleinere Laufzeitartefakte entstehen als bei der ersten Variante, das Builden der JARs sowie der Docker-Images allerdings durch das fortwährende Einbinden der Server-Komponenten in den Build-Prozess entsprechend länger dauert.

Ein neuer Standard: microprofile.io

Was haben wir bisher erreicht? Dank "Micro Java EE" sind wir in der Lage, eigenständige Microservices umzusetzen, welche die zur Laufzeit benötigten Server-Komponenten – und nur genau diese – in sich tragen. Je nach gewähltem Hersteller ist dabei die Anbindung zusätzlich benötigter Infrastruktur mehr oder minder trivial. Das hört sich doch schon einmal gut an. Verloren haben wir allerdings die bisher an Java EE geschätzte Herstellerunabhängigkeit. Da jeder Hersteller sein eigenes Bootstraping sowie eine proprietäre Anbindung an die Infrastruktur mitbringt, lässt sich ein Microservice nicht mehr einfach so mit Server-Komponenten unterschiedlicher Java EE-Hersteller bundeln.

Natürlich lassen sich die entsprechenden Code- und Konfigurations-Teile sauber kapseln. Es bleibt aber immer ein nicht unerheblicher Teil des Services, der bei einem Wechsel auf einen anderen Hersteller ausgetauscht werden müsste.

Dieses Dilemma ist natürlich auch den Herstellern bewusst und es liegt daher nahe, einen entsprechenden Standard – a.k.a. Microprofile – zu entwickeln. Gegen eine Standardisierung im Rahmen von Java EE, mit Hilfe eines entsprechenden Java Specification Requests (kurz: JSR) und dem zugehörigen Java Community Process (kurz: JCP), spricht allerdings der Marktdruck. Die Erfahrung zeigt, dass ein neuer JSR im Umfeld von Java EE drei Jahre und mehr in Anspruch nimmt. Gehen wir einmal davon aus, dass die entscheidenden Änderungen nicht schon mit Java EE 8 sondern erst mit Java EE 9 eintreten würden, hieße dies, dass erst 2020 mit Resultaten zu rechnen ist. Eine derart späte Reaktion auf die aktuellen Marktbedürfnisse wäre gleichzusetzen mit dem Ende von Java EE im Microservices-Umfeld.

Genau aus diesem Grund haben sich einige Key-Player der Szene als Initiative namens MicroProfile.io [7] zusammengeschlossen und vereinbart, einen "de facto"-Standard zu entwickeln. "An open forum to optimize Enterprise Java for a microservices architecture by innovating across multiple implementations and collaborating on common areas of interest with a goal of standardization".

Mission von microprofile.io.

Der Standard soll auf etablierten Java EE APIs aufsetzen und Themen wie Bootstrapping oder Anbindung von Infrastruktur vereinheitlichen. Um das Vorhaben von Anfang an in geregelte Bahnen zu lenken, wurde das Projekt Ende 2016 in die Eclipse Foundation überführt [8]. Als Lizenzmodell ist Apache Licence, Version 2.0, geplant. Ein wesentliches Ziel der Initiative ist es, in sehr kleinen, schnellen Schritten, agil auf die Wünsche der Community einzugehen. Daher besteht die erste Version der Spezifikation (Baseline) auch lediglich aus drei APIs: JAX-RS 2.0, CDI 1.2 und JSON-P 1.0.

Wie es zukünftig mit der Spezifikation weitergeht, soll die Community bestimmen. So findet man auf der Webseite von microprofile.io entsprechende Umfragen. Ziel ist es, die vielen vorhandenen Ideen gemäß den Entwicklerwünschen zu priorisieren. Asynchronität, Reactive Microservices, Security, Service Discovery und die Anbindung einiger Komponenten aus dem NetflixOSS Stack stehen derzeit ganz oben auf der Liste.

Wer das Microprofile gerne selbst einmal ausprobieren möchte, kann dies mit einem der vielen Beispielanwendungen tun. Einfach runterladen [9] und mit einer der bisher 5 Implementierungen (WildFly Swarm, IBM Liberty Profile, Apache TomEE, Payara Micro und Hammock) laufen lassen.

Fazit

Java EE genießt nicht gerade den besten Ruf, wenn es um die Realisierung Microservice-basierter Anwendungen geht. Dabei bringt der standardisierte Java Enterprise Stack alles mit, was es technologisch braucht. Das eigentliche Problem findet sich weniger in der Entwicklung als zur Laufzeit. Das Modell des schwergewichtigen, monolithisch angehauchten Application Servers ist in Zeiten von Microservices schlichtweg überholt.

Eine mögliche Lösung stellt eine Eins-zu-eins-Kopplung zwischen Server und Service, also des Deployments lediglich eines Microservices je Application Server, dar. Dies aber erzeugt einen unnötig hohen Ressourcenverbrauch zur Laufzeit.

Eine zweite Lösung des Problems ist das gezielte Bundeln des Services mit lediglich denjenigen Server-Komponenten, die der Service zur Laufzeit tatsächlich benötigt. Auf diesem Weg entsteht ein schlanker, lauffähiger Service mit entsprechend optimiertem Ressourcenverbrauch. Nahezu alle modernen Java EE Application Server-Hersteller bieten entsprechende Möglichkeiten zum Bundeln. Nutzt man diese, verliert man allerdings die vielbeschworene Herstellerneutralität, die Java EE normalerweise mit sich bringt.

Möchte man weiterhin auf Java EE setzen und dabei nicht auf die Herstellerunabhängigkeit verzichten, lohnt sich ein Blick auf MicroProfile.io. Aktuell mit lediglich drei APIs noch relativ schlank, hat dieser neue "de facto"-Standard das Zeug dazu, in kürzester Zeit eine echte Alternative zu Spring Boot/Cloud und Co. zu werden. So können zukünftig auch Java EE-Entwickler in der Wunderwelt der Microservices mitspielen.

Autor
Das könnte Sie auch interessieren

Neuen Kommentar schreiben

Kommentare (1)
  • Andreas
    am
    Danke, war auch Jahre später sehr hilfreich. Muss noch herausfinden, wie der Stand heute ist und wie das z.B. mit micronaut.io zusammenspielt.