Container-less Microservices im Java-EE-Umfeld mit Inkstand

Microservice, wie hast du’s mit den Standards?

Gerald Mücke, Nathalie Schmidli, Roland Innerhofer

(c) Shutterstock / Kirill_M

Microservices sind feingranulare, monothematische Dienste, aus denen robuste, skalierbare und widerstandsfähige Applikationen komponiert werden können. Jeder Microservice ist auf das Wesentlichste reduziert und klein genug, um einfach ausgetauscht oder entsorgt werden zu können [1]. Um Deployments einfach zu halten, folgen viele Frameworks dem container-less Deployment-Modell. Dafür setzen sie auf eigene Mechanismen, womit sie jedoch nicht mehr mit Java-Standards konform sind, was die Portabilität und Wiederverwendbarkeit einschränkt. Es gibt aber auch Alternativen. In diesem Artikel wird eine Auswahl der verfügbaren Frameworks hinsichtlich ihrer Standard-Konformität betrachtet und ein alternativer Ansatz – das Inkstand Framework – vorgestellt, das ausschließlich auf Java-EE-Standards setzt.

Der Begriff oder das Konzept von Microservices ist schon länger bekannt, hat jedoch vor allem in den letzten Jahren durch populäre Beispiele wie Amazon, Netflix oder Spotify erheblich Aufwind bekommen. Microservices sind ein Architekturstil für verteilte Anwendungen, der vor allem im Zusammenspiel mit agilen Methoden und DevOps seine Stärken [2] ausspielen kann. Im Grundsatz sollten agile Entwicklerteams frei über die eingesetzten Technologien entscheiden. Es empfiehlt sich jedoch, vor allem im Unternehmenskontext, gewisse Standards zu etablieren, um Synergien zu nutzen. So setzt Netflix ausschließlich auf Cassandra zur Persistenz, um Expertise und Tooling für diesen unternehmenskritischen Teil so stark wie möglich auszubauen, auch wenn nicht alle Use Cases damit optimal abgebildet werden können [3].

Ebenso kritisch für den Erfolg ist es, sich auf einzelne, wenige Sprachen, Protokolle und Konzepte zu beschränken, da andernfalls Kosten für Personal, wie Einstellung, Weiterbildungen, aber auch Betrieb und Wartung den Nutzen überwiegen. Ist Java als eine der Sprachen festgelegt, so ist die Verwendung von Java-Standards naheliegend, da sie zu einem gewissen Grad Herstellerunabhängigkeit und Wiederverwendbarkeit gewährleisten. Die Java Enterprise Edition (EE) bündelt mehrere Standards, die für unternehmenskritische Anwendungen von Bedeutung sind.

Application Server und Containerless Applications

Typischerweise werden Java-EE-Anwendungen in einem Java-EE-Applikationsserver wie JBoss, WebLogic oder WebSphere betrieben. Im Kontext von Microservices verlieren Applikationsserver jedoch zunehmend an Bedeutung, da die Unterstützung aller Java-EE-Standards in einer Plattform für Microservices mit ihrem engem Domain-Kontext nicht nur unnötig ist, sondern auch mit einer hohen Komplexität einhergeht, die dem Microservice-Ansatz diametral entgegenwirkt [4].

Die Alternative zu den schwergewichtigen Applikationsservern sind container-less Applikationen in einem ausführbaren Jar, die alle für den Betrieb notwendigen Funktionen, wie WebServer, selbst bereitstellen. Frameworks für container-less Applikationen setzen jedoch häufig auf eigene Mechanismen, die mit Java-Standards nicht mehr konform sind. Auch wenn Microservices von einer Share-Nothing-Philosophie profitieren, so kann es innerhalb eines Unternehmens von wirtschaftlichem Nutzen sein, wiederkehrende und gemeinsame Funktionen in einem an Open-Source-Entwicklung angelehnten Community-Prozess zu pflegen. Für diesen Prozess ist die Verwendung von Standards als gemeinsame Grundlage von großem Vorteil.

Ausgangslage

Im Versicherungsumfeld sind wir unter anderem für die Betreuung und Weiterentwicklung von verschiedenen Applikationen zur elektronischen Dokumentverarbeitung zuständig. Aus den Erfahrungen von Wartung und Betrieb dieser eher monolithischen Applikationen heraus, sind wir auf der Suche nach einer geeigneteren Gesamtarchitektur, die den Herausforderungen im sich ständig ändernden betrieblichen Umfeld besser gewachsen ist.

Als unternehmenskritische Anforderungen kristallisierten sich Skalierbarkeit, Widerstandsfähigkeit und Fehlertoleranz gegenüber Ausfällen von Drittsystem, sowie modulare Erweiterbarkeit heraus. Weiterhin sollte ein Vendor-Lock-In durch Verwendung eines offenen Standards bei der Datenhaltung von strukturierten Dokumenten-Daten vermieden werden. Die Entwicklung einer Microservice-Architektur und Verwendung des Standards für Java Content Repositories (JCR) [6] erschien uns als vielversprechender Ansatz, den wir näher untersuchen wollten.

Es boten sich diverse Frameworks für die Umsetzung der angestrebten Lösungsarchitektur an. Jedes Framework wies dabei ganz eigene Vor- und Nachteile bzw. Stärken und Schwächen auf, welche wir nachfolgend kurz aufzeigen.

Spring Boot

Spring Boot ist ein Ansatz auf Basis des Spring-Frameworks, um die Komplexität, die dessen Konfiguration erreichen kann, durch Convention-over-Configuration zu reduzieren und mit funktionellen Erweiterungen vollständige Standalone Web-Applikationen realisieren zu können. Das Spring-Framework, auf das Spring Boot aufsetzt, bietet zahlreiche Module, die funktionelle und integrative Erweiterungen bieten, z.B. Spring Data, Spring MVC oder Spring Batch.

Da Spring eines der ersten Dependency-Injection-Frameworks ist, setzt es traditionell auf eigene Mechanismen und Funktionen auf, die zum Teil deutlich über den Java-EE-Standard hinausgehen, zu diesem jedoch vollständig inkompatibel sind. Gleiches gilt für die Unterstützung von RESTful Web-Applikationen, bei denen Spring ebenso seinen eigenen Weg abseits des Java-EE-Standards geht. Auf Seite der Persistenz bietet Spring eine umfangreiche Unterstützung von JPA und JDBC, jedoch keine Unterstützung von JCR.

Mit Spring Boot kommt neben der einfacheren Konfigurierbarkeit noch ein embedded Jetty-Web-Server sowie ein Maven-Plug-in zum Erstellen von ausführbaren Jars hinzu, wodurch eine Web-Applikation auch ohne Container betrieben werden kann.

Das Spring Framework ist funktionell das umfassendste hier beschriebene Framework und hat eine sehr große und aktive Community. Jedoch ist der für Spring entwickelte Code durch die fehlende Unterstützung bestimmter Java-EE-Standards nicht auf andere Plattformen portabel, sofern man nicht das gesamte Spring-Ökosystem mitliefert. Durch das vielfältige und komplexe Ökosystem ist der Einstieg relativ schwierig und die Kombination mit alternativen, nicht von Spring unterstützen Technologien teilweise sehr kompliziert.

Dropwizard

Dropwizard ist ein Open-Source-Java-Framework zur Erzeugung von Microservices, das seit dem Jahre 2011 entwickelt wird. Es nutzt bereits vorhandene und gereifte Java-Bibliotheken und Frameworks und ergänzt sie um eigene Funktionen. Es bietet „out of the box“-Unterstützung für Konfiguration, Logging, Metriken und Betrieb. Als Kerntechnologien nutzt es Jetty, Jersey und Jackson, um Jax-RS basierte REST-Ressourcen via HTTP in einer JSON-Repräsentation auszuliefern.

Konfigurationen werden als Klasse spezifiziert, die dann via Jackson aus YAML Files befüllt werden. Die Registrierung der REST-Ressourcen erfolgt manuell in einer eigenen Klasse. Dropwizard bietet keine native Unterstützung der Java-CDI-Spec, sondern nutzt seine eigene Object-Lifecycle-Implementierung für Managed Objects. Dropwizard hat Integrationen für JDBC (via JDBI) und JPA, jedoch kein JCR.

Anders als Spring Boot ist Dropwizard ein leichtgewichtiges Framework mit leichtem Einstieg, das einige Java-Standards unterstützt (JAX-RS, JDBC, JPA), in anderen Bereichen jedoch auf eigene Lösungen setzt (CDI). Für Konfiguration und Ressourcen-Registrierung muss in Bezug auf Business-Value irrelevanter Code produziert werden, dafür bietet Dropwizard schon Unterstützung von anderen Bibliotheken, welche sowohl Entwicklung, als auch Auslieferung und Betrieb erleichtern, wie z.B. Metrics, JDBI, HttpClient, Freemarker oder Liquibase.

Play Framework

Das Play Framework existiert seit 2009 als Open-Source-Projekt. Es umfasst nicht nur funktionale Bibliotheken, sondern bietet ein eigenes Ökosystem zur Projekterstellung, Entwicklung, Paketierung und zum Betrieb von Web-Applikationen. Dementsprechend bricht es mit dem Java-EE-Standard und setzt auf eigene Konzepte, um mit wenig Aufwand Standalone-Web-Anwendungen zu entwickeln. Auch die Verwendung von Maven zur Dependency-Verwaltung ist nicht ohne Hürden [5]. Einzig die Anbindung von JDBC-Datenquellen wird unterstützt. Anders als die anderen hier besprochenen Frameworks setzt es auf asynchrone, reaktive Verarbeitung und bietet nativen Scala Support.

Apache Sling

Apache Sling sieht sich weniger als Microservices-Framework, jedoch bedient es ähnliche Anforderungen. Sling setzt auf einen OSGi-Container, in den zahlreiche Plug-ins integriert werden können. Der OSGi-Container kann sowohl standalone als auch innerhalb einer Web-Applikation betrieben werden. Sling ist eines der ersten Web-Frameworks, die JCR unterstützen und setzt konsequent auf eine REST-Architektur. Allerdings folgt es nicht dem JAX-RS Standard. Es bietet keine native Unterstützung von JDBC, JPA oder der CDI-Spec, ist jedoch durch OSGi entsprechend erweiterbar. Da Sling in erster Linie ein Web- und weniger ein Service-Framework ist, liegt sein Schwerpunkt beim Ausliefern und Modifizieren von Content. Auch wenn es ein paar einfache Content-zentrische Beispiele für Sling gibt, ist der Einstieg für Microservice-Entwickler relativ schwer.

Vorstellung unserer Lösung: Inkstand Microservice Container

Ohne ein bestehendes Framework, das unseren Anforderungen vollständig genügt, haben wir auch nach alternativen Ansätzen gesucht. Dabei stießen wir auf das Projekt “Hammock” [7], bei dem der Weld-CDI-Container als Laufzeitumgebung dient und Undertow und RESTEasy zum Bedienen von JAX-RS Resourcen via HTTP genutzt wird. Mit dem Designziel, konsequent auf Java-Standards und etablierten Frameworks aufzubauen, sowie die Entwicklung eigener Microservices so weit wie möglich zu vereinfachen, haben wir die Idee von „Hammock“ aufgegriffen, weiterentwickelt und unsere Ergebnisse im Projekt inkstand.io veröffentlicht.

Inkstand bietet gegenüber der Hammock-Konfigurierbarkeit, automatische Erkennung und Registrierung von JAX-RS Resourcen, sowie eine Integration von JCR, so dass dieses auch im CDI Kontext nutzbar ist. Inkstand basierte Microservices können eigenständig als Executable-Jar betrieben werden. Nicht nur die Domain-spezifischen REST-Ressourcen können durch Verwendung von JAX-RS auch auf einem herkömmlichen Java-EE-Applikationsserver betrieben werden, sondern durch die Verwendung der CDI-Spec auch die Inkstand-CDI-Extensions, sowie die Funktionalen Module, wie der JCR-Support.

Architektur

Inkstand nutzt einen Standalone-CDI-Container, um darin die entsprechenden Erweiterungen zum Betreiben einer Web-Applikation zu injizieren. Dazu gehören in erster Linie ein HTTP-Server und eine Applikation, die über den HTTP-Server mit der Außenwelt kommuniziert. Im Grunde lassen sich in den CDI-Container auch beliebige andere Dienste integrieren. Um dem Ziel „HTTP/Rest als Integrationsprotokoll“ gerecht zu werden, wurde zunächst die Unterstützung auf HTTP und JAX-RS beschränkt.

Da für die drei Kernkomponenten CDI-Container, HTTP-Server und JAX-RS bereits etablierte und produktionsbewährte Implementierungen existieren, bietet das Inkstand-Framework lediglich eine leichtgewichtige Integration der existierenden Bibliotheken, analog zu Dropwizard.

Inkstand bietet einen Launcher sowie entsprechende CDI-Producer zur Integration von HTTP-Server und JAX-RS an. Der Launcher startet den CDI-Container und hält ihn am Laufen. Mittels CDI-Extensions werden REST-Resources und Provider automatisch erkannt, sofern sie in einem gültigen Bean Deployment Archive (BDA), d.h. einem JAR mit einer /META-INF/beans.xml, liegen. Um nun mit Inkstand loslegen zu können und seinen eigenen Microservice zu entwickeln, genügt es, die entsprechenden Inkstand-JAR-Dateien im Classpath bereitzustellen und ein Service-Profile in der beans.xml als Stereotype auszuwählen. Das reine Coding beschränkt sich nun auf die JAX-RS-Ressourcen.

muecke_mircoservices_jee_1

Abb. 1: Inkstand Container Architektur

 

Wie im Projekt „Hammock“ kommt als CDI Container in der aktuellen Version die Referenzimplementierung Weld zum Einsatz. Als HTTP-Server wird der Undertow-Server, als JAX-RS-Implementierung RESTeasy eingesetzt. Alle drei Kernkomponenten werden auch im WildFly-Applikationsserver und im JBossAS (hier ohne Undertow) verwendet und sind praxiserprobt. Zur Konfiguration wird der Mechanismus von Apache Deltaspike verwendet, womit einfache JVM-Parameter, Property-Files als auch komplexere Konfigurationen via CDI injected werden können. Für die wichtigsten Konfigurationen gibt es Standardwerte.

Extensions

Inkstand bietet zwei portable CDI-Extensions, um die Laufzeitkonfiguration und damit Convention-over-Configuration zu erleichtern.

  • Global Alternative
    Diese Extension erlaubt es, in einem Dritt-BDA die Alternativen zu spezifizieren, die aus einem “Provider”-BDA in ein “Consumer”-BDA injiziert werden. Normalerweise kann ein BDA nur bestimmen, welche Alternativen in die eigenen Injection-Points injiziert werden sollen, nicht jedoch für ein anderes BDA. Mit CDI 1.1 kam die @Priority Annotation, die die Injizierbarkeit von Alternativen in Dritt-BDAs generell erlaubte, jedoch keine Spezifikation der Alternativen in der beans.xml zuließ. Inkstand verwendet jedoch sogenannte „Alternative-Bundles“. Alternative-Bundles sind BDAs, die mehrere Alternativen enthalten, die zusammen mit den Consumer-BDAs in einer Applikation zusammengefügt werden. Der Entwickler entscheidet dann über die beans.xml, welche der Alternativen zum Einsatz kommen soll.
Abb. 2: Global Alternative CDI Extension

Abb. 2: Global Alternative CDI Extension

  • Resource and Provider AutoDiscovery
    Diese Extension erfasst beim Scannen der BDAs alle mit @Path oder @Provider annotierten Klassen und stellt diese über eine CDI-injectable-Bean zur Verfügung. Dadurch müssen diese nicht mehr explizit in der JAX-RS-Applikation spezifiziert werden, sondern werden automatisch verlinkt.
Abb. 3: Resources and Providers CDI Extension

Abb. 3: Resources and Providers CDI Extension

Hello-World-Beispiel

Um einen Inkstand-basierten Microservice zu entwickeln, werden folgende Basiselemente benötigt:

  1. Inkstand-JARs im Classpath (via Maven Dependencies)
  2. beans.xml mit der Spezifikation des Service-Profils
  3. JAX-RS-Klassen, die die Resourcen darstellen (eigentliche Business-Logik)

Die JARs gelangen per Maven in den Classpath. Es genügt, die Abhängigkeiten für einen HTTP-Server und einen JAX-RS-Provider in der pom.xml anzugeben:

<dependency>
    <groupId>io.inkstand</groupId>
    <artifactId>inkstand-http-undertow</artifactId>
    <version>0.1.2</version>
</dependency>
<dependency>
    <groupId>io.inkstand</groupId>
    <artifactId>inkstand-deployment-resteasy</artifactId>
    <version>0.1.2</version>
</dependency>

Für den Service muss eine beans.xml im META-INF Folder angelegt werden, in der das Service-Profil als Stereotyp spezifiziert wird. Das Profil gibt an, welche Rolle der Microservice einnimmt, und aktiviert einige Komponenten, z.B. den HTTP-Server. Inkstand kennt derzeit zwei Profile:

  • PublicService – bietet keine Authentisierung (default)
  • ProtectedService – bietet Authentisierung

Das Hello-World-Beispiel wird als PublicService definiert, der keine Benutzer-Authentisierung benötigt.

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
           http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1"
       bean-discovery-mode="all">
   <alternatives>
      <stereotype>io.inkstand.PublicService</stereotype>
   </alternatives>
</beans>

Da der PublicService der default ist, kann die Stereotype-Angabe auch weggelassen werden.

Die eigentliche Geschäftslogik kann man nun als REST-Ressource definieren . Für das Hello-World-Beispiel wird lediglich ein String zurückgegeben.

@Path("/")
public class HelloWorldService {
    @GET
    @Path("/hello/world")
    public Response helloWorld() {
        return Response.ok("Hello world").build();
    }
}

Die main-Methode zum Starten liegt in io.inkstand.Inkstand. Die Konfiguration erfolgt durch Deltaspike mit Standardwerten (port 80 und localhost), kann aber via eigener PropertyConfig übersteuert werden. Sobald der Service gestartet wird, ist der HelloWorld-Service über http://localhost/hello/world erreichbar. Das komplette Beispiel (minimal-hello-world) und noch weitere sind auf Github [8] veröffentlicht.

Ausblick

Inkstand bietet derzeit noch wenige Funktionen, setzt jedoch von Anfang an auf Java-Standards. Neben alternativen Implementierungen für CDI-Container (OpenWebBeans), HTTP-Server (Jetty und Tomcat) und JAX-RS-Implementierung (Jersey) sind zahlreiche funktionale, integrative, wie auch Management-Erweiterungen geplant, so dass Entwicklerteams weiterhin eine freie Technologiewahl haben. Langfristig geplant ist die Unterstützung weiterer Java-EE-Spezifikationen, zusätzliche Möglichkeiten zur Persistenz, darunter JPA und native JDBC, Authentisierung via LDAP und Oauth sowie die Integration in bestehende Frameworks wie Zookeeper oder Hysterix. Ob Inkstand zur Ablösung unserer Anwendungen zum produktiven Einsatz kommt, ist aufgrund des Reifegrades im Moment unwahrscheinlich. Jedoch hat der Ansatz genügend Potential für andere Anwendungsfälle, so dass wir ihn auf jeden Fall weiter weiterverfolgen werden.

Zusammenfassung

Inkstand geht einen ähnlichen Ansatz zur Entwicklung von Microservices wie Dropwizard. Anders als Dropwizard setzt es auf einen CDI-Container als Laufzeitumgebung, wodurch der CDI-Standard nativ unterstützt wird. Durch Inkstand wird es im Gegensatz zu Dropwizard noch leichter, einen Microservice zu entwickeln, da lediglich die Geschäftslogik implementiert werden muss. Code für Konfiguration oder Resource-Registrierung entfallen. Durch die konsequente Verwendung von Java und Java-EE-Standards sind die mit Inkstand entwickelten Applikationen und Services auch in einem herkömmlichen Java-EE-Applikationsserver betreibbar.

Da Inkstand momentan noch ein sehr junges Framework ist, hat es den Nachteil, dass erst wenige Funktionalitäten und Integrationen umgesetzt sind. Allerdings lassen sich schon jetzt damit produktionstaugliche Microservices z.B. zum Abrufen von Informationen oder Content aus einem JCR Repository entwickeln.

Aufmacherbild: Group of Technicians repairing CPU. Technology concept. / Urheberrecht: Kirill_M

Verwandte Themen:

Geschrieben von
Gerald Mücke
Gerald Mücke ist als IT Berater und Software Architekt für Enterprise Content Management Systeme tätig. Er verfügt über langjährige Erfahrung im Java Enterprise Umfeld, sowohl in der Produktentwicklung als auch in der Beratung. Seine Themenschwerpunkte liegen bei Agiler Softwareentwicklung, Microservices und Java Content Repositories.
Nathalie Schmidli
Nathalie Schmidli
Nathalie Schmidli ist Service Ownerin und Test Manager einer Enterprise Content Management Applikation im Versicherungsumfeld. Sie beschäftigt sich mit der Qualitätssicherung, der Optimierung von Betriebsprozessen und ist für die Stabilität der Applikation im Betrieb verantwortlich. Dabei liegen die Schwerpunkte bei Agile Test Management und Devops.
Roland Innerhofer
Roland Innerhofer
Roland Innerhofer entwickelt als Senior Consultant und Solution Architect für Kunden Lösungen im Content- als auch im Output-Management-Bereich. Er ist ein Verfechter von "clean code" und beschäftigt sich intensiv mit CDI, aber auch neuen Entwicklungen im Java EE-Umfeld.
Kommentare

1
Hinterlasse einen Kommentar

avatar
4000
1 Kommentar Themen
0 Themen Antworten
0 Follower
 
Kommentar, auf das am meisten reagiert wurde
Beliebtestes Kommentar Thema
1 Kommentatoren
Waldemar Letzte Kommentartoren
  Subscribe  
Benachrichtige mich zu:
Waldemar
Gast
Waldemar

„was die Portabilität und Wiederverwendbarkeit einschränkt.“

Was’n Käse. Wenn ich ein Runnable-Jar hab, dann kann ich das überall ausführen, wo man Java installieren kann. 😉