Microservices mit dem Eclipse Microprofile

Von: Thomas Bayer
Datum: 21. Okt. 2019

Das MicroProfile verspricht die standardisierte und herstellerunabhängige Entwicklung von Microservices auf der Java Plattform. Das Profil basiert auf den Java Enterprise Technologien CDI, JSON sowie JAX-RS und erweitert diese um essentielle Microservices Features wie Konfiguration und Monitoring.

Dieser Artikel beschreibt das MicroProfile und die standardisierten Technologien.

1. Microservices mit Enterprise Java?

Mit der Java Enterprise Edition verbindet man vielmehr die Entwicklung von Monolithen als die von Microservices. Java EE bietet aber Technologien wie Dependency Injection, REST und JSON an, die für Microservices benötigt werden. Dagegen fehlen bei JEE andere für Microservices essentielle Features wie z.B. Health Checks und Fault Tolerance. Der Standardisierungsprozess hat die Entwicklung von Java EE gebremst und in den letzten Jahren ist nicht nennenswert viel passiert. Das Microprofile ergänzt JEE um die für die Microservices fehlenden Cloud-Eigenschaften und unterliegt einem agileren Standardisierungsprozess.

2. Die MicroProfile Spezifikationen

Hersteller von Java Enterprise Software sowie diverse Organisationen arbeiten im Microservices Projekt zusammen, um festzulegen, wie man mit Java EE Technologie Microservices entwickeln kann. Zu den Mitgliedern zählen u.a. IBM, Microsoft und Oracle. Mit der Aufnahme als Eclipse Foundation Projekt wurde 2016 das Projekt in Eclipse Micro Profile umbenannt.

Das Microprofile ist eine Spezifikation, ein Standard, der von mehreren Herstellern implementiert werden kann. Ziel ist die Austauschbarkeit zwischen den Implementierungen, d.h. ein auf Open Liberty entwickelter Microservice sollte ebenso auf Oracles Helidon lauffähig sein. Die Spezifikation wird unter der Apache Lizenz veröffentlicht und kann als Open Source Spezifikation bezeichnet werden.

Das Microprofile ist, wie der Name verrät, ein Profil oder in Deutsch, ein Blumenstrauß verschiedener Bibliotheken aus Java EE und weiteren, von der Eclipse Organisation veröffentlichten Spezifikationen. Bei der aktuellen Version 3 des Profiles sind das 13 Spezifikationen. Dem Entwickler stehen u.a. die Technologien der folgenden Spezifikationen zur Verfügung:

Aus der Java Enterprise Edition:

  • CDI 2.0
  • Common Annotations 1.3
  • JAX-RS 2.1
  • JSON-B 1.0 und JSON-P 1.1

Aus dem MicroProfile:

  • MicroProfile Config 1.3
  • MicroProfile Fault Tolerance 2.0
  • MicroProfile Health Check 2.0
  • MicroProfile JWT Authentication 1.1
  • MicroProfile Metrics 2.0.0
  • MicroProfile OpenAPI 1.1
  • MicroProfile OpenTracing 1.3
  • MicroProfile Rest Client 1.3

Bei JEE übernimmt ein Application Server Infrastruktur Aufgaben wie z.B. Logging und Monitoring. Bei den Microservices sind bei diesen Aufgaben die Microservices stärker eingebunden.

Das Ziel des Microprofiles ist es, die passenden Features von JEE zu verwenden und um die fehlenden Funktionen zu erweitern.

In der neusten MicroProfile 3.0 Spezifikation vom 11. Juni 2019 wurde der Funktionsumfang der Features Health Check, REST Client und Metrics erweitert. Auf der Roadmap für zukünftige Versionen stehen u.a. Reactive Messaging, Reactive Streams und GraphQL.

3. Entwickeln von Microservices mit dem MicroProfile

Mit dem Microprofile Starter kann ein Projektgerüst für Microservices erzeugt und herunterladen werden. Der Starter unterstützt momentan die Laufzeitumgebungen Thorntail und Open Liberty.

Beta Version des MicroProfile Projekt Generators

Abbildung : Beta Version des MicroProfile Projekt Generators

Wie beim Spring Framework dient die Dependency Injection als Programmiermodell. Als DI-Framework wird das von Java EE bekannte Contexts and Dependency Injection kurz CDI verwendet.

4. Microprofile Implementierungen

Es gibt u.a. die folgenden Implementierungen:

Hammock KumuluzEE Thorntail Open Liberty Payara Micro TomEE Quarkus Launcher
Hersteller Hammock Kumuluz RedHat IBM Payara Tomitribe Red Hat (IBM) Fujitsu
Lizenz ASF 2.0 MIT ASF 2.0 EPF 1.0 CDDL ASF 2.0 ASF 2.0 CDDL
GitHub Sterne 102 246 372 632 717 315 2782 26
Besonderheit Abgekündigt zu Gunsten von Quarkus

5. Die MicroProfile Features

In den folgenden Abschnitten werden die einzelnen Features des MicroProfiles kurz vorgestellt.

5.1 MicroProfile Config

Das von den Projekten DeltaSpike und Apache Tamaya inspirierte Feature ermöglicht die Konfiguration von Anwendungen.

Services benötigen Konfiguration wie z.B. die Adresse einer Datenbank. Als Quellen für die Konfigurationen unterstützt das Microprofile:

  • Property Dateien
  • Systemproperties
  • Umgebungsvariablen
  • Datenbanken

Ferner ist es möglich, eigene Quellen für die Konfiguration ins System einzuklinken.

Ein Wert für eine Konfiguration z.B. die Portnummer kann mit einem Wert aus einer anderen Quelle überschrieben werden. Beginnend mit der höchsten Priorität werden die folgenden Quellen durchsucht:

System Properties > Umgebungsvariablen > Property Datei im Classpath

Über Injektion können dann die Werte aus der Konfiguration für die Anwendung zugänglich gemacht werden:

					@Inject
					@ConfigProperty(name = "meine.property")
					private String meins;

5.2 MicroProfile Health Check

Die Überprüfung des Gesundheitszustandes von Services ist eine essentielle Funktion von Cloud Systemen. Die Cloud hat die Aufgabe, die Einsatzbereitschaft ständig aufrecht zu erhalten. Services, die nicht funktionieren, müssen erkannt, gestoppt und neu gestartet werden. Eine gängige Methode, wie die Cloud einen Healthcheck durchführt, ist der Aufruf eines HTTP Endpunktes beim Service. Beispielsweise die Liveness und Readiness Probes bei Kubernetes. Services müssen für die Checks einen Endpunkt zur Verfügung stellen. Beim Aufruf dieses Endpunktes kann ein Service die Bereitschaft von seinen für den Betrieb notwendigen Ressourcen überprüfen, zum Beispiel die Funktionsbereitschaft der Datenbank oder den freien Plattenplatz.

MicroProfile Health bietet wie bei Kubernetes getrennte Endpunkte für die Readyness und Liveness Checks.

Das Listing unten zeigt die Implementierung eines eigenen Health Checks.

@Readiness
@ApplicationScoped
public class KitchenCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {

        return HealthCheckResponse
                .named("Küche")
                .withData("Problem","Die Kacke ist am dampfen.")
                .down()
                .build();

    }
}

Listing XX: Implementierung eines eigenen Health Checks

Der Check ist nur rein Beispiel und liefert immer ein “Down” zurück. Das nächste Listing zeigt den Aufruf des Health Endpunktes. Der gesamte Service ist down, da der eigene Check „Down“ zurückliefert:

curl http://localhost:8181/health 
{
  "checks": [
    {
      "data": {},
      "name": "ServiceHealthCheck",
      "status": "UP"
    },
    {
      "data": {
        "Problem": "Die Scheiße ist am kochen."
      },
      "name": "Küche",
      "status": "DOWN"
    }
  ],
  "status": "DOWN"
}

5.3 MicroProfile Metrics

Je mehr Microservices in Betrieb sind, desto besser müssen diese Überwacht werden. Das MicroProfile erlaubt die Sammlung von Metriken und deren Übertragung in die Monitoring Lösungen wie z.B. Prometheus.

Für das Monitoring von Kennzahlen stehen die folgenden Arten von Metriken zur Verfüng:

Timer
Erfasst die für eine Methode oder einen API Aufruf vergangene Zeit. Timer sind nützlich für die Überwachung der Service Qualität und für die Performanz Optimierung.

Counter
Zähler, der einen Wert nach oben zählt. Ein Beispiel wäre ein Aufruf Zähler für ein API.

Meter
Misst die Häufigkeit z.B. von API Aufrufen innerhalb einer bestimmten Zeitspanne. Beispielsweise die Anzahl der Aufrufe eines Endpunktes in den letzten 10 Minuten.

Histogram
Ein Histogramm erfasst die Verteilung von Werten in Abschnitten. Ein Beispiel wäre, die Dauer von API Aufrufen. Aus der Tabelle unten ist erkennbar, dass 95 Prozent der Aufrufe innerhalb von 170 Sekunden beantwortet werden konnten.

Prozent Dauer in Millisekunden
50 30
75 54
90 95
95 170
99 593

Die Spezifikation legt eine Reihe von Metriken im base-Scope fest, die ausgeliefert werden müssen. Darüber hinaus kann der Hersteller im vendor-Scope und die Anwendung Metriken im application-Scope ausliefern.

Scope Beschreibung
base Metriken, die alle Hersteller ausliefern müssen
vendor Hersteller spezifische Metriken
application Metriken, die die Anwendung selbst zur Verfügung stellt

Abrufbar sind die Metriken über einen Management Endpunkt. Ganz im REST Stil kann über ein HTTP-Header Feld ein Format angefordert werden. Unterstützt werden JSON und das Prometheus kompatibles Textformat OpenMetrics.

curl localhost:8181/metrics -H "Accept: application/json"

5.4 MicroProfile Fault Tolerance

Wenn Services andere Services aufrufen ergeben sich Abhängigkeiten. Fällt ein Service aus, so fallen die abhängigen Services ebenfalls aus. Um trotz der Abhängigkeiten zuverlässige Systeme zu entwickeln, bietet das Fault Tolerance Profile die folgenden Mechanismen:

Timeout
Ein Aufruf wird nach einer bestimmten Zeit abgebrochen um eine längere Wartezeit zu verhindern.

Retries
Ein Aufruf wird nach dem Scheitern wiederholt. Ein Scheitern kann auch das Überschreiten eines Timeouts sein. Die Retry Richtlinie kann konfiguriert werden mit:

  • Der Anzahl der erneuten Versuche
  • Die Zeit zwischen jedem Versuch
  • Einer zufälligen Abweichung zwischen den Versuchen

Interessant finde ich das Verhalten der Retries in Abhängigkeit der aufgetreten Exception zu konfigurieren. Dies ist nützlich, wenn ein Aufruf z.B. an einem Validierungsfehler scheitert. Ein Wiederholen würde hier nichts nützen, da bei einer Wiederholung die Daten ebenfalls nicht validiert werden können. Im Gegensatz dazu, macht es Sinn, bei einem Netzwerkfehler einen erneuten Retry auszuführen.

Fallback
Es wird auf eine alternative Methode ausgewichen, nachdem alle anderen Mechanismen wie Retries nicht zu einem Erfolg geführt haben.

Bulkhead
Zu deutsch „die Schotten“. Wenn die Schotten dicht sind, so sinkt nicht das gesamte Schiff. Ein Bulkhead limitiert die Anzahl der parallelen Aufrufe.
Die folgende Annotation begrenzt die Anzahl der gleichzeitigen Threads für die Methode berechne auf 10.

@Bulkhead(10)
public void berechne() {
}

Circuit Breaker
Ein Circuit Breaker, zu deutsch Überlastschutz, greift, wenn eine Überlastung droht. Eine Überlastung wird erkannt, wenn z.B. mehr als 20 Prozent der letzten 100 Aufrufe zu Fehlern geführt haben. Der Circuit Breaker öffnet dann den Schaltkreis, d.h. es werden keine Aufrufe mehr an den Empfänger durchgestellt, um diesem die Chance für eine Erholung zu geben. Nach einer konfigurierbaren Zeit, kann erneut ein Versuch unternommen werden. Ist dieser Versuch erfolgreich, so wird der Circuit wieder geschlossen.

Die folgende Tabelle zeigt die Zustände eines Circuit Breakers beim MicroProfile.

Status Beschreibung
Closed Arbeitszustand: Aufrufe werden durchgestellt.
Open Aufrufe werden nicht durchgestellt. Nach einer gewissen Zeit wird in Half Open gewechselt.
Half Open Mit einzelnen Aufrufen wird geprüft, ob der Aufruf wieder funktioniert, um dann entsprechend nach Closed oder Open zu wechseln.

Wird Fault-Tolerance zusammen mit dem Metrics Feature eingesetzt, so werden automatisch Metriken für Retries, Fallbacks, Timeout etc. angelegt. Die Retries erscheinen dann auch im Monitoring.

5.5 MicroProfile JWT Authentication

Mit JSON Web Tokens kann beim MicroProfile einfach eine Authentifizierung und Autorisierung von REST Aufrufen realisiert werden.
Ein Aufrufer besorgt sich zunächst ein JWT Token von einem Authentication Service, z.B. bei einem JBoss Keycloak Server. Wo und wie das Token besorgt wird, wird nicht vorgeschrieben und kann individuell gestaltet werden. Das ausgestellte JWT Token enthält u.a. die folgenden Daten.

{
    "iss": "https://idm.predic8.de/login",
    "jti": "3578928",
    "sub": "michael",
    "upn": "michael@predic8.de",
    "iat": 1571118087,
    "exp": 1571118387,
    "groups": [
        "kunde"
    ]
}

Die einzelnen Felder werden Claims genannt. Die Spezifikation Eclipse MicroProfile Interoperable JWT RBAC schreibt ein knappes Dutzend dieser Claims vor, die Implementierungen unterstützen müssen. Darüber hinaus können weitere Claims von anderen Standards verwendet werden. Das Token wird vom Herausgeber mit dessen privatem Schlüssel signiert und an den Aufrufer ausgehändigt. Der Aufrufer kann das Token mit jedem Aufruf an den Service übertragen. Das Beispiel unten zeigt eine HTTP-POST Anfrage mit dem base64 kodierten Token im Authorization Header.

POST /bestellen
Content-Type: application/json
Authorization: Bearer eyJraWQiO.iJcL3ByaXZhd.GVLZXku…

Der Service entnimmt dem Aufruf das Token und dekodiert dieses wieder nach JSON. Mit dem im Service hinterlegten öffentlichen Schlüssel des Token Herausgebers kann die MicroProfile Laufzeitumgebung die Signatur überprüfen. Ist die Signatur valide, so kann dem Inhalt des Tokens vertraut werden.

Die Annotation @RolesAllowed im folgenden Codebeispiel sorgt dafür, dass nur Aufrufer, die im Token die Gruppe kunde enthalten den Endpunkt /bestellen erfolgreich über POST aufrufen können.

                
@POST
@Path("/bestellen")
@RolesAllowed({"kunde"})
public Bestaetigung bestellen( Bestellung bestellung) {
	…
}

Felder, bzw. Claims aus dem JWT Token können in die Instanz des API Controllers per Injection übertragen werden.

					@Inject 
					@Claim(standard = Claims.email) 
					Optional<String> email;

Das MicroProfile erleichtert mit dem JWT Feature die rollenbasierte Authentifikation und Autorisation bei Microservices.

5.6 MicroProfile OpenAPI

Das OpenAPI Feature kann aus einer API Implementierung eine Swagger Beschreibung erzeugen. Es wird auch die Swagger UI-Oberfläche eingebunden.

5.7 MicroProfile OpenTracing

Rufen Services andere Services auf, dann kann der Überblick schnell verloren gehen. Verteiltes Tracing hilft Service Aufrufe besser nachzuvollziehen und Hotspots für das Performance Tuning zu finden. Das MicroProfile unterstützt den OpenTracing Standard und ermöglicht die Anbindung der Tracing Lösungen Zipkin und Jaeger.

5.8 MicroProfile Rest Client

Für den Aufruf eines anderen Service benötigt ein Service selbst einen REST Client. Das MicroProfile greift dafür auf das JAX-RS Client API der Java Enterprise Edition zurück. Der Client unterstützt deklarative Clients. Das Listing unten zeigt ein Interface mit Annotationen, die den Client Hinweise geben, wie ein Aufruf konkret aufzubauen ist.

@RegisterRestClient(baseUri="http://localhost:8081")
@Path("/preis")
public interface PreisClient {

    @Path("/{artikel}")
    @GET
    Preis getPreis(@PathParam("artikel") String artkel);

}

Wurde ein API als Rest Client beschrieben, kann über Injection darauf zugegriffen werden. Das Listing unten zeigt einen Ausschnitt aus einer Klasse, die das obige Interface für Service Aufrufe verwendet.

@Inject
@RestClient
PreisClient ps;


double getPreis(Position p) {
    return ps.getPreis(p.getArtikel()).getPreis() * p.getMenge();
}

6. Vor- und Nachteile

Der Einsatz des MicroProfiles bringt die folgenden Vor- und Nachteile mit sich:

Vorteile

  • Von JEE bekannte Technologien erleichtern den Einstieg.
  • Das MicroProfile ermöglicht die Standard basierte Entwicklung von Microservices
  • Es gibt eine Auswahl von Implementierungen
  • Da das MicroProfile neuer ist als Spring Boot bestand die Möglichkeit, die Features neu zu überdenken.

Nachteile

  • Ein Feature für ServiceDiscovery fehlt
  • Server wie Open Liberty und KumuluzEE bieten Erweiterungen, die die Interoperabilität zwischen den Implementierungen gefährden.
  • MicroProfile hat bisher noch keinen substanziellen Marktanteil erreicht
  • Durch die vielen Implementierungen ist der Markt stark fragmentiert. Die einzelnen Server teilen sich dadurch die Anwender.
  • Spring Boot bzw. Spring Cloud bietet derzeit wesentlich mehr Funktionen

7. Fazit

Das MicroProfile ist als Alternative zum verbreiteten Spring Boot zu begrüßen. Durch die Standardisierung können die Hersteller der Server in einen Wettbewerb treten, wovon die Anwender profitieren.

Die Features bieten die grundlegenden Funktionen für Microservices und die Cloud. Das Programmiermodell ist schlank und durchdacht. In kurzer Zeit können damit zuverlässige Microservices realisiert werden.

Deployment-Einheiten können ohne Abhängigkeiten ausgeliefert werden, da das Profile vorschreibt, welche Bibliotheken in welcher Version vom Server angeboten werden müssen. Die Größe der Deployment-Einheiten ist dadurch wesentlich kleiner als ein Deployment-Archiv das alle Abhängigkeiten enthält.

Details zeigen, dass das Microprofile konsequent für die Cloud und Kubernetes gemacht ist. Der Health Check ist z.B. in Liveness und Readyness aufgeteilt, so wie das bei Kubernetes erwartet wird.

Im Vergleich zu Spring Boot/Cloud unterstützt das MicroProfile weniger Technologien. Vor dem Einsatz des MicroProfiles sollte geklärt werden, ob die benötigten Technologien unterstützt werden. Außer den JEE und MicroProfile Features bieten einige Hersteller aber Unterstützung für weitere Technologien wie z.B. Apache Kafka. Selbstverständlich können auch ohne eine explizite Unterstützung des MicroProfiles in jedes Projekt weitere Technologien integriert werden.

Ob das MicroProfile oder Spring Boot, besser für ein Vorhaben geeignet ist hängt auch davon ab, ob Spring oder Java EE Erfahrung im Team vorhanden ist. Bei JEE und CDI Erfahrung bietet sich das MicroProfile an, da die Entwickler auf Bekanntem aufbauen können.

8. Quellen