dark

MicroProfile für Microservices mit Java EE

Avatar

Die Entwicklung von Microservices schreitet in einem rasanten Tempo voran und stellt Entwickler vor immer neue und anspruchsvollere Herausforderungen. Nach der Meinung vieler ist Java-EE nicht geeignet, die gestiegenen Ansprüche zu erfüllen. Aus diesem Grund wurde das Eclipse-MicroProfile-Projekt ins Leben gerufen. Ziel ist es, einen Standard für die Entwicklung von Microservices auf Basis von Java zu definieren.

 

Was ist MicroProfile eigentlich?

Eclipse-MicroProfile ist, genauso wie Java-EE (jetzt Jakarta-EE), eine Sammlung von Spezifikationen, die zusammen eine API darstellen, welche auf Microservice-basierte Softwareentwicklung fokussiert ist. MicroProfile 2.1 beinhaltet beispielsweise 13 Spezifikationen[1] (Abb. 1).

 

Bestandteile des MicroProfile 2.1. (Abb. 1)

Die rot dargestellten Spezifikationen wurden von der Java-EE-8-Spezifikation[2] übernommen und sind mit dieser identisch (Abb. 1). So verwendet MicroProfile ebenso wie Java-EE 8 eine Implementierung von CDI für die Dependency-Injection. Die blau dargestellten Spezifikationen wurden speziell für MicroProfile entwickelt. Diese acht speziellen Spezifikationen unterscheiden sich von ihren Java-EE-Pendants unter anderem dadurch, dass es für sie keine Referenzimplementierungen gibt. Zwar existiert ein Projekt namens Smallrye, das diverse Implementierungen des Standards bietet, sich jedoch eher als Community-betriebene Sammlung versteht und keinen Referenzanspruch hat.

 

Die oben genannten APIs sind in Form von Bill-of-Materials-Definitionen (BOM) für das Build-Tool Apache-Maven erhältlich. Jeder MicroProfile-kompatibler Anbieter von Application-Servern (zum Beispiel Payara-Micro) sollte zu diesen APIs die entsprechenden Implementierungen liefern. Um die Kompatibilität mit den Standards sicherzustellen, muss jede Implementierung die Tests des zugehörigen Test-Compatibility-Kits (TCK) bestehen (siehe Beispiel[3]).

 

Einsatzansätze: MicroProfile schreibt nicht vor, wie genau ein kompatibler Application-Server organisiert sein soll, die alleinige Entscheidung liegt daher bei den Anbietern. (Abb. 2)

 

Es gibt zwei entgegengesetzte Ansätze. Bei Servern wie Payara-Micro sind alle MicroProfile-Implementierungen enthalten (Abb. 2). Dies ist die klassische Strategie, wie sie auch von den meisten Java-EE-Anwendungs-Servern verfolgt wird. Der zweite Ansatz verfügt hingegen nur über Implementierungen, die wirklich von der Anwendung gebraucht werden (Abb. 2). Ein Beispiel dafür ist Thorntail, ehemals Wildfly-Swarm von JBoss beziehungsweise RedHat.

 

Nach Meinung einiger Programmierer gehört dem zweiten Ansatz die Zukunft, da Microservices möglichst klein und so ressourcenschonend wie möglich sein sollten. Etablierten Anbietern könnte es allerdings Schwierigkeiten bereiten, diesen Ansatz auf ihren existierenden Plattformen schnell zu implementieren, da diese aus historischen Gründen eher monolithisch sind.

 

Diese Klassifizierung ist nicht ausschließlich schwarz-weiß zu sehen. Dazwischen positioniert sich zum Beispiel das Open-Liberty-Projekt, das aus dem Websphere-Application-Server von IBM hervorgegangen ist. Der Open-Liberty-Server beinhaltet zwar alle Implementierungen, diese können jedoch selektiv ein- und ausgeschaltet werden. Open-Liberty ist aus OSGi-Modulen aufgebaut[4], die sowohl aktiviert als auch deaktiviert werden können, was gegebenenfalls Ressourcen spart.

 

Prophezeiungen in Nostrastockerus erstellen

Nostrastockerus[5] ist eine Beispielanwendung, welche auf Open-Liberty und MicroProfile 2.1 basiert und für das Deployment auf Kubernetes gedacht ist. In dieser Anwendung können Benutzer Prophezeiungen erstellen. Ein Beispiel: Ein Benutzer sagt voraus, dass in vier Wochen die Aktie des Konzerns X den Wert Y in US-Dollar erreichen wird. Andere Benutzer können für oder gegen diese Annahme stimmen. Die Anwendung überprüft die Prophezeiungen nach Ablauf der vier Wochen und erstellt Statistiken über die Benutzer, beziehungsweise berechnet das Abschneiden ihrer Prophezeiungen. Dadurch kann die Anwendung zum Beispiel eine Aussage darüber treffen, welche Präzision der Benutzer X als Prophet hat. Die Präzision ist dabei das Verhältnis der Anzahl von erfüllten Prophezeiungen zu der Anzahl aller überprüften Prophezeiungen.

Prophezeiungsbeschränkungen. (Abb. 3)

 

Natürlich können für eine Prophezeiung auch Beschränkungen festgelegt werden, wie zum Beispiel die Dauer (Abb. 3). Der Zeitraum bis zum Eintritt der Prophezeiung soll mindestens Dmin und höchstens Dmax sein. Nachdem sie erstellt wurde, darf nur während der Abstimmungsdauer abgestimmt werden. Die Abstimmungsdauer einer Prophezeiung ist als Prozentanteil der ganzen Dauer definiert (zum Beispiel 80%). Alle diese Parameter (Dmin, Dmax und die Abstimmungsdauer als Prozentanteil) sollen konfigurierbar sein (die Anforderung A1).

 

Weitere, für Microservices typische Anforderungen sind:

  • A2: Die Anwendung soll eine rollenbasierte Zugriffskontrolle (RBZK) haben.
  • A3: Die Anwendung soll so zustandslos wie möglich sein, damit sie gut skalierbar bleibt.
  • A4: Es soll möglich sein, die Gesundheit der Anwendung per Monitoring zu überprüfen.
  • A5: Metriken zum Ressourcenverbrauch sollen verfügbar sein, zum Beispiel die CPU-Last.
  • A6: Die Anwendung soll eine Dokumentation der APIs für die öffentliche Verwendung zur Verfügung stellen. Es soll beispielsweise relativ einfach sein, von einem Android-Client die APIs zu konsumieren.
  • A7: Das Anwendungsverhalten soll per Tracing beobachtbar sein. Es soll beispielsweise möglich sein, die Verkettung von HTTP-Aufrufen nachzuvollziehen.
  • A8: Die Fehlerrobustheit soll relativ einfach eingebaut werden können. Bei einem Fehler wäre so beispielsweise die Wiederholungen von HTTP-Aufrufen problemlos möglich.

 

Anforderungen mittels MicroProfile erfüllen

Anwendung Nostrastockerus-Architektur. (Abb. 4)

 

Nostrastockerus besteht aus drei Microservices: user, prophecy und stats. Der user-Microservice speichert Benutzer und erstellt JSON-Web-Tokens (JWT)[6]. Der prophecy Microservice speichert Prophezeiungen. Der stats Microservice überprüft Prophezeiungen und berechnet die aggregierte Statistik über Benutzer und ihre Prophezeiungen. Zudem ruft stats einen externen stock Service auf, um die Werte von Aktien zu erhalten.

 

Neben diesen Microservices gibt es weitere Infrastruktur-Komponenten: Zipkin[7], CronJobs[8] und Prometheus[9] [10]. Zipkin empfängt Tracing-Daten von den Microservices und speichert sie, wenn zum Beispiel HTTP-Aufrufe eingehen (Abb. 4 – lila Pfeile). Prometheus frägt die Microservices regelmäßig ab, um ihre Metriken zu sammeln (Abb. 4 – rote Pfeile). Der CronJob ist ein Feature von Kubernetes und ruft periodisch via HTTP den stats Service auf (Abb. 4 – grüner Pfeil) und löst dadurch die Überprüfung der Prophezeiungen aus. Die Anforderung A1 wird über die MicroProfile-Config-API erfüllt. Die Config-API ist eine Kern-API von MicroProfile, da viele andere APIs die Config-API für ihre Konfiguration benutzen.

 

(Listing 1) – Definition des Parameters Dmin mittels Config-API

 

 

Der Parameter wird von der MicroProfile-Config-Implementierung gelesen und in das Feld minDifferenceBetweenExpectedAtAndCreatedAtSeconds übertragen. Dieser Wert wird in der init Methode der Einfachheit halber nach Duration umgewandelt. Anschließend wird eine Instanz der Config-Klasse in den anderen Komponenten der Anwendung injiziert. Dadurch können Konfigurationswerte über Getter-Methoden gelesen werden. Standardmäßig werden Parameter von diesen drei Quellen gelesen:

  • Systemeigenschaften
  • Umgebungsvariablen
  • Datei META-INF/microprofile-config.properties im Class-Path

 

Gelesen wird in der gelisteten Reihenfolge, das bedeutet die erste Quelle, die einen Parameter definiert, gewinnt. Da die Namen der Umgebungsvariablen nicht alle Symbole beinhalten dürfen, die als Parameter-Namen zulässig sind, werden die verbotenen Symbole in diesen Namen beim Einlesen als Umgebungsvariable einfach durch einen Unterstrich ersetzt. Der oben definierte Parameter mit dem problematischen Punkt kann beispielsweise über eine Umgebungsvariable config_minDifferenceBetweenExpectedAtAndCreatedAtSeconds definiert werden.

 

Gesundheitsüberprüfung und Metriken

Die Anforderung A4 wird durch MicroProfile-Health-Check erfüllt. Um Health-Check in Open-Liberty zu aktivieren, muss zunächst die entsprechende Abhängigkeit hinzugefügt werden:

 

(Listing 2)

 

Anschließend sollte das entsprechende Merkmal in der Server-Konfiguration von Open-Liberty (server.xml) eingeschaltet werden:

(Listing 3)

 

Und schließlich muss die Logik der gewünschten Überprüfung implementiert werden, wie (Listing 4) zeigt.

 

(Listing 4)

 

Das Health-Check-Merkmal sucht den Klassenpfad automatisch nach Implementierungen des Health-Check-Interfaces ab und meldet diese an. Das Merkmal fügt auch den Endpunkt /health zu der Anwendung hinzu. Dieser Endpunkt kann von außen abgerufen werden, um die Information über die Gesundheit der Anwendung zu erhalten:

 

(Listing 5)

 

 

Die entsprechende Überprüfung kann als eine sogenannte Liveness-Probe in Kubernetes hinzugefügt werden, über welche die Plattform automatisch überprüfen kann, ob die Anwendung gesund ist:

 

(Listing 6)

 

 

Mit den Metriken funktioniert es ähnlich. Das entsprechende Merkmal (mpMetrics-1.1) sollte als Abhängigkeit hinzugefügt und in der Server-Konfiguration aktiviert werden. Danach können die Metrikimplementierungen via Java-Annotationen definiert werden:

 

(Listing 7)

 

 

(Listing 7) definiert eine Metrik, welche die Ausführungsdauer der entsprechenden Methode misst. Genauso wie beim Health-Check-Merkmal wird ein Endpunkt /metrics automatisch hinzugefügt. Prometheus kann diesen Endpunkt abrufen. Dadurch ist die Anwendung an das Monitoring angebunden und die Anforderung A5 erfüllt. Wichtig dabei ist, dass die Metriken über den gleichen CDI-Scope der Anwendung verfügen (d. h. Metriken sollen @ApplicationScoped sein).

 

Das Merkmal fügt die übliche Metriken wie zum Beispiel CPU-Last, Verbrauch des Arbeitsspeichers der JVM und Anzahl der JVM-Threads automatisch hinzu. Es ist auch möglich, eine eigene spezifische Metrik zu implementieren. Die in (Listing 8) abgebildete Metrik misst zum Beispiel die Gültigkeitsdauer des erzeugten JWT-Tokens.

 

(Listing 8)

 

 

Authentifizierung mittels MicroProfile-JWT

Die Anforderung A2 wird durch das MicroProfile-Merkmal JWT-Authentication erfüllt. Es trägt auch zu der Anforderung A3 bei, da eine Token-basierte Authentifizierung, wie JWT sie bietet, zustandslos ist. Um ein JWT-Token zu überprüfen, werden keine Aufrufe zu einem Service benötigt. Dafür wird nur der entsprechende öffentliche Schlüssel des Herausgebers des Tokens benutzt. Ein Nachteil der Token-basierten Authentifizierung ist die Verteilung des entsprechenden öffentlichen Schlüssels. Als Lösung bietet sich die Verwendung einer öffentlichen Public-Key-Infrastruktur (PKI) an. Dafür kann zum Beispiel Let’s-Encrypt[11]genutzt werden. Alternativ können die entsprechenden öffentlichen Schlüssel auch über das Anwendung-Artefakt mit distribuiert werden, zum Beispiel in die Anwendungs-JAR oder in das Docker-Image.

 

Ein JWT-Token besteht aus drei Base64-kodierten Teilen, die durch Punkte getrennt sind:

 

<base64-teil1>.<base64-teil2>.<base64-teil3>

 

Der erste Teil ist ein Header in dekodierter Form:

 

(Listing 9)

 

 

 

Der Header weist auf den Token-Typ (JWT) hin und welcher Algorithmus (RS256 – RSA mit SHA-256) zur Überprüfung des Tokens genutzt werden soll. Der zweite Teil enthält die Nutzlast (Payload):

 

(Listing 10)

 

Die Nutzlast beinhaltet die sogenannten Ansprüche (Claims), wie zum Beispiel Benutzeridentifizierungsdaten sub (Subject) und upn (User-Principal), groups (Gruppen der Benutzer) die der Anforderung A2 zufolge nach Rollen für eine rollenbasierte Zugriffskontrolle abgebildet werden, den Herausgeber des Tokens iss (Issuer), den Erschaffungszeitstempel iat (issued-at) und den Gültigkeitszeitstempel exp (expires).

Der letzte und dritte Teil eines JWTs ist die Signatur. Die Signatur wird aus den beiden ersten Teilen erzeugt. Dazu wird der private Schlüssel des Herausgebers verwendet. Da Konsumenten der JWTs über den entsprechenden öffentlichen Schlüssel verfügen, können sie diese Signatur auf ihre Berechtigung hin überprüfen. Wenn zum Beispiel ein bösartiger Benutzer „Mallory“ versucht, zu den Gruppen seines JWTs admin hinzuzufügen, um sich zum Superbenutzer zu machen, wird die Signatur nicht stimmen. Um die neue valide Signatur zu berechnen, ist der private Schlüssel nötig, den Mallory natürlich nicht besitzt.

Um JWT-Tokens in Open-Liberty zu verwenden, sind folgende Komponenten nötig:

  • Eine Java-Keystore-Datei, welche ein asynchrones Schlüsselpaar enthält. Der private Schlüssel wird für die Erzeugung der Signaturen benutzt. Der öffentliche Schlüssel muss auf alle Microservices verteilt werden und dient der Überprüfung dieser Signatur.
  • Das MpJwt-1.1 Merkmal (es wird genauso aktiviert wie die anderen Merkmale)
  • Die Konfiguration einer JwtBuilder-Instanz (Open-Liberty-API).

 

Ein Java-Keystore und die Schlüssel können mithilfe von Keytools[12], einem Tool aus dem JDK, erzeugt werden. Keystore, das MpJwt-Merkmal und der JwtBuilder werden in der Server-Konfiguration eingestellt:

 

(Listing 11) – Einstellung Keystore in Server-Konfiguration

 

(Listing 12) – Nutzung der JwtBuilder im Code

 

 

Der JwtBuilder gehört zu der Open-Liberty-API und nicht zu der MicroProfile-JWT-Authentication-API.

 

(Listing 13) – Übertragung des erzeugten JWT im Authorization-HTTP-Header

 

 

Die Überprüfung eines eingehenden JWT-Tokens wird von Open-Liberty anhand der RolesAllowed- Annotationen und des bereitgestellten öffentlichen Schlüssels durchgeführt. Der Schlüssel kommt aus einem Java-Truststore. Der Truststore ist ebenfalls eine Keystore-Datei, die jedoch nur den öffentlichen Schlüssel enthält.

 

(Listing 14) – Einstellung Truststore in Open-Liberty

 

(Listing 15) – Absicherung von Endpoints im Code

 

In diesem Fall darf die POST-Prophecy-Anfrage nur mit einem Token durchgeführt werden, welches die Gruppen admin oder prophecy-creator enthält. Da ein JWT nur in dem CDI-Scope einer Anfrage existiert, ist es wichtig zu beachten, dass die Endpoints @RequestScoped sind. JWT kann als JsonWebToken injected werden (Listing 15).

 

Export und Konsum der APIs

 

Die Anforderung A6 wird durch MicroProfiles-OpenAPI erfüllt. Das entsprechende Open-Liberty- Merkmal mpOpenAPI-1.0 fügt den Endpunkt /openapi hinzu. Wenn aufgerufen, liefert der Endpunkt eine OpenAPI-Beschreibung der Anwendungs-Endpunkte in einer YAML-Form. (Listing 16)

zeigt, wie diese ungefähr aussehen kann:

 

Anhand dieser Beschreibung und des OpenAPI-Generators[13] können Clients für verschiedene Plattformen und Sprachen, wie zum Beispiel ein Swift-Client[14] erzeugt werden. Um eine korrekte und aussagekräftige OpenAPI-Beschreibung zu erhalten, müssen die Endpunkte, Parameter und Models (meistens POJOs) der Anwendung mit OpenAPI-Annotationen versehen werden, wie (Listing 17) zeigt:

 

(Listing 17):

 

 

(Listing 18) – Beispiel, wie Models versehen werden können

 

 

Anhand dieser Metainformation erzeugt die OpenAPI-Implementierung die entsprechende Beschreibung der API. Natürlich können Entwickler auch die umgekehrte Herangehensweise beziehungsweise einen API-first-Approach[15] wählen. Dabei wird zuerst eine OpenAPI-Beschreibung erstellt. Über den OpenAPI-Generator wird ein Server-Rumpf (Server-Stub) erzeugt, der um die konkrete Implementierung erweitert werden kann.

 

Beobachtbarkeit

Die Anforderung A7 wird durch MicroProfile-OpenTracing erfüllt. Das entsprechende Open-Liberty-Merkmal heißt mpOpenTracing-1.1. Dazu wird oft das Merkmal usr:opentracingZipkin-0.31 hinzugefügt, das als ein Adapter zum Zipkin-Service dient:

 

(Listing 19)

 

 

Das usr:opentracingZipkin-0.31 Merkmal ist in der Open-Liberty-Distribution nicht enthalten. Es muss manuell heruntergeladen[16] und in den Ordner /opt/ol/wlp/usr/extension der Open-Liberty-Installation kopiert werden.

 

Wenn diese zwei Merkmale eingeschaltet sind, können die HTTP-Aufrufketten in Zipkin beobachtet werden (Abb. 5):

 

 

Ein Beispiel von der Spur der Aufrufketten in Zipkin. (Abb. 5)

 

(Abb. 5) zeigt die HTTP-Aufrufketten statsuser und statsprophecy. Service stats ruft Service user, um Benutzerinformation zu bekommen (der Aufruf hat 232.164 Millisekunden gedauert). Danach wird Service prophecy aufgerufen, um sich die Prophezeiungen des Benutzers zu holen (der Aufruf hat 1.390 Sekunden gedauert).

 

Unter der Haube funktioniert das durch spezielle HTTP-Header für die Nachvollziehbarkeit von Aufrufketten. (Listing 20) zeigt ein Beispiel eines HTTP-Aufrufes mit entsprechenden Headern mit dem Namenspräfix X-B3-.

 

(Listing 20)

 

 

Diese Header werden von der Server-Engine von JAX-RS entdeckt und automatisch weiter verwendet, wenn in der Folge der Verarbeitung des Requests eine JAX-RS-Client-Verbindung benutzt wird. Das passiert natürlich nur, wenn das OpenTracing-Merkmal eingeschaltet ist.

 

 

 

Fehlerrobustheit

Die Anforderung A8 wird durch MicroProfile-Fault-Tolerance erfüllt. Das entsprechende Open-Liberty-Merkmal heißt mpFaultTolerance-1.1. Schlägt der Prophecy-Microservice mit einer Wahrscheinlichkeit von 50% fehl, kann dieser Service trotzdem benutzt werden, wenn der Aufruf im Fehlerfall wiederholt wird. (Listing 21) demonstriert, wie dieses Verhalten relativ einfach mit MicroProfile-Fault-Tolerance definiert werden kann:

 

 

(Listing 21)

 

 

Durch die @Retry Annotation wird ein Aufruf der Methode getPropheciesCreatedByUser abgefangen. Wenn dieser fehlschlägt, wird er automatisch in einer konfigurierbaren Anzahl wiederholt. Diese Konfiguration wird per MicroProfile-Config festgelegt. Für entsprechende Konfigurationsparameter gibt es folgendes Namensschema, das sich an Klassen- und Methodennamen der betroffenen Funktionalitäten orientiert:

 

<classname>/<methodname>/<annotation>/<parameter>

 

Verfügbare Parameter sind zum Beispiel: maxRetries (Anzahl Wiederholungen), delay (Zeit zwischen den Wiederholungen) und jitter (zufällige Variationsdauer der Zwischenzeit). Daraus ergeben sich bei längeren Paketnamen auch relativ lange Parameter-Namen, wie zum Beispiel: de.consol.labs.microprofilearticle.stats.integration.prophecy.ProphecyAPI/getPropheciesCreatedByUser/Retry/maxRetries.

 

Will man diese als Umgebungsvariablen setzen, wird es bezüglich Übersichtlichkeit nicht besser:

DE_CONSOL_LABS_MICROPROFILEARTICLE_STATS_INTEGRATION_PROPHECY_PROPHECYAPI_GETPROPHECIESCREATEDBYUSER_RETRY_MAXRETRIES.

 

Alternativ können diese Werte auch als Parameter direkt an der Annotation festgelegt werden.

 

Fazit:

Die Nutzung von MicroProfile als Standard ist für die Entwicklung von Microservices in Java ein großer Schritt nach vorne. Typische Anforderungen zu Microservice-basierten Anwendungen können hier über eine verhältnismäßig kleine Anzahl von dedizierten APIs erfüllt werden. Dennoch gibt es derzeit Herausforderungen, die noch zu meistern sind:

 

  • Es gibt derzeit keine Spezifikation für Persistenz. In der Praxis ist dies kein großes Problem, denn in der Regel ist JPA aus der Java-EE-Welt verfügbar. Dennoch muss man für diese Anforderung die MicroProfile-Welt verlassen. Ähnliches gilt auch für die JWT-Verwendung (JwtBuilder) und verschiedene Konfigurationen, bei denen Entwickler auf proprietäre Lösungen der Plattform ausweichen müssen.
  • Die Merkmale des Standards werden in Spezifikationen wie TCK manchmal zu isoliert betrachtet. Werden mehrere MicroProfile-Merkmale, wie zum Beispiel JWT-Authentication und Metrics, zusammen verwendet, kann es zu Definitions-Lücken kommen. Gewisse Fragen der Interoperabilität, beispielsweise wie sich die JWT-Authentifizierung auf den automatischen Metrik-Endpunkt auswirkt, beziehungsweise welche Berechtigungen dafür gelten, werden bislang nicht beantwortet. Wünschenswert wären hier klare Aussagen und standardisierte Konfigurations-Möglichkeiten. Momentan löst jeder Anbieter dieses Problem auf seine eigene, proprietäre Weise.
  • Es gibt einige Überschneidungen zwischen OpenAPI und Java-EE-Bean-Validation. Hier wäre es hilfreich, wenn die Constraints der Bean-Validation aus den OpenAPI-Beschreibungen generiert werden könnten. Momentan müssen diese jedoch separat festgelegt werden.

 

Alexander Ryndin ist bei der Consol Software GmbH als Software Engineer tätig. Er beschäftogt sich mit den Themen rund um Microservices, Java EE, MicroProfile, Kubernetes, AWS und Cloud-native Anwendungen.

 

Firmen-Website https://labs.consol.de

GitHub https://github.com/progaddict

 

Links/Quellen/Verweise:

 

[1] MicroProfile-2.1-Spezifikation – https://bit.ly/2HGcNWR

[2] Java-EE-8-Spezifikationen – https://bit.ly/2OtGZoy  

[3] MicroProfile Config TCK – https://bit.ly/2FCrUhu

[4] OSGi in Open Liberty – https://bit.ly/2CHvVPV

[5] Nostrastockerus – https://bit.ly/2CIrlRE

[6] JWT-Spezifikation – https://tools.ietf.org/html/rfc7519

[7] Zipkin – https://zipkin.io/

[8] Kubernetes CronJob – https://bit.ly/2LmztIN

[9] Prometheus Monitoring – https://prometheus.io/

[10] Prometheus Kubernetes Operator – https://bit.ly/2uxNnlo

[11] Let‘s-Encrypt-Projekt – https://letsencrypt.org/

[12] Java Keytool – https://bit.ly/2uxNnBU

[13] OpenAPI Generator – https://openapi-generator.tech/

[14] OpenAPI Swift Generator – https://bit.ly/2Wu0lwn

[15] API-Zuerst-Ansatz – https://bit.ly/2TCS7jR

[16] „usr:opentracingZipkin-0.31“-Merkmal – https://bit.ly/2OsSgWd

 

Total
0
Shares
Previous Post

Die Qual der Wahl

Next Post

Containerbasierte Testautomatisierung

Related Posts