20170526-JavaSpektrum-Microservces-testautomation-1
Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
Petri Heil!<br />
Testautomation einer<br />
Microservice-<br />
Architektur – Teil 1<br />
Rudolf Grötz, Dominik Kukacka, Nebojsa Nikolic<br />
<br />
IntegrationsSpektrum<br />
In Zeiten der Digitalisierung ist es für dynamische Unternehmen notwendig,<br />
technische Strukturen zu etablieren, die den immer schneller<br />
werdenden Marktanforderungen gerecht werden. Der digitale Darwinismus<br />
fordert Strukturen, die sowohl agil als auch schnell genug sind,<br />
diesen Anforderungen gerecht zu werden.<br />
Charles Darwin hat in seinen Werken einen wichtigen<br />
E Punkt erarbeitet: Es sind nicht die Stärksten, die überleben,<br />
und auch nicht die Intelligentesten. Es sind vielmehr jene,<br />
die sich am schnellsten den neuen Gegebenheiten anpassen<br />
können.<br />
Microservices als Antwort auf den digitalen<br />
Darwinismus<br />
Unternehmen wie Netflix, Amazon und Google haben schnell<br />
erkannt, die notwendigen technischen Strukturen anzupassen<br />
und zu implementieren. Geschafft wurde das durch den Einsatz<br />
von Microservices als neues Architekturprinzip. Weg vom<br />
Monolithen, hin zu kleinen, schlanken, unabhängigen Services,<br />
die über standardisierte APIs kommunizieren. Statt einer<br />
einzelnen monolithischen Anwendung, die die gesamte Geschäftslogik<br />
enthält, erledigt ein flexibles Netzwerk aus Microservices<br />
alle komplexen Vorgänge.<br />
In dieser 2-teiligen Artikelserie gehen wir auf die Entwicklung<br />
einer mobilen Anwendung ein. Diese basiert auf einer<br />
Microservice-Architektur, die innerhalb einer Docker basierten<br />
Systemumgebung abläuft.<br />
Wir widmen uns der Entwicklung der Microservices mit<br />
NODE.JS und Python sowie der Orchestrierung dieser Services<br />
mit Docker Images und Docker Container. Schließlich zeigen<br />
wir auf, wie diese Microservices innerhalb einer Continuous<br />
Delivery Pipeline* automatisiert getestet werden, um sicherzustellen,<br />
dass die Dienste wie erwartet funktionieren.<br />
* Ein Wort in eigener Sache: Um die Lesbarkeit dieses Artikels zu erhöhen, werden<br />
englische Fachbegriffe nur übersetzt, sofern es möglich ist und nicht zu Missverständnissen<br />
oder Unworten führt. Es wird also aus einer Continuous Delivery Pipeline<br />
niemals eine Kontinuierliche Lieferungsrohrleitung und aus einem Consumer<br />
Contract nie ein Verbrauchervertrag, aber aus einem Testcase durchaus ein Testfall.<br />
Microservices<br />
Der Trend in der Systemarchitektur geht weg vom Monolithen,<br />
hin zu Microservices, um durch kleine überschaubare Programmeinheiten<br />
Continuous Delivery zu ermöglichen und Features<br />
schneller dem Markt zur Verfügung stellen zu können.<br />
Microservices ist ein Architekturmuster, bei dem die Anwendung<br />
in Services unterteilt wird und nicht in Schichten (s.<br />
Abb. 1). Die Microservices sind dabei entkoppelt und laufen in<br />
einem eigenen Prozess, damit diese leichter austauschbar sind.<br />
JS-3-17 – grötz – k1<br />
Microservices sind auch technologisch voneinander unabhängig,<br />
da sie durchaus mit verschiedenen Programmiersprachen,<br />
Frameworks oder Datenbanken realisiert werden können.<br />
Martin Fowler bezeichnet „Microservices“ als „yet another<br />
new term on the crowded streets of software architecture” [Fow14].<br />
Sie lassen sich unter anderem durch folgende Eigenschaften<br />
beschreiben:<br />
Abb. 1: Architekturmuster<br />
H Die Anwendung wird in kleine Services unterteilt, die entkoppelt<br />
sind, und verwendet diese als Komponenten. Diese<br />
Services sind IT-Repräsentationen von in sich abgeschlossenen<br />
fachlichen Funktionalitäten.<br />
H Microservices enthalten alle Schichten (Datenbank, Middleware<br />
und Frontend) einer Funktionalität. Anwendungen<br />
werden somit nicht in Schichten unterteilt, sondern in Funktionalitäten.<br />
H Microservices werden eigenständig und nicht als gesamte<br />
Anwendung deployed. Dadurch entsteht selbst bei der Implementierung<br />
keine Abhängigkeit zu anderen Services.<br />
H Die Kommunikation von Microservices erfolgt meist über<br />
HTTP-basierte Schnittstellen, wie beispielsweise RESTful-<br />
APIs.<br />
Die Idee von Microservices entspricht der Unix-Philosophie<br />
„Schreibe Computerprogramme so, dass sie nur eine Aufgabe<br />
erledigen und diese gut machen.“ [Wiki]<br />
Anforderung mobiler Anwendungen<br />
Die Einführung einer auf Microservices basierenden Architektur<br />
bedarf einiger spezifischer Überlegungen, wenn es darum<br />
geht, die Inhalte für mobile Anwendungen zur Verfügung zu<br />
stellen. Die wichtigste, die Performanz, werden wir in diesem<br />
Artikel auch berücksichtigen.<br />
Webanwendungen werden in der Regel auf Geräten ausgeführt,<br />
die über WLAN oder Ethernet mit dem Internet verbunden<br />
sind, also mit geringer Latenz. Mobile Geräte verbinden<br />
sich oft über 3G, Edge oder auch GPRS, also mit hoher Latenz.<br />
Daher ist es gerade bei solchen Verbindungen wichtig, die Anzahl<br />
der Aufrufe über das Netzwerk gering zu halten.<br />
Da die Anzahl der Aufrufe wachsen kann, trägt jede HTTP-<br />
Transaktion in der Gesamtreaktionszeit zu einem exponentiellen<br />
Wachstum bei. Daher ist es ratsam, hier einen eigenen<br />
Microservice – ein API Gateway [NGINX] – zur Verfügung<br />
zu stellen, der die Daten aller Zugriffe sammelt und in einer<br />
Transaktion an die aufrufende Anwendung übergibt.<br />
Beispiel einer mobilen Microservice-Landschaft<br />
In vorherigen Abschnitt führten wir den Begriff Microservice<br />
ein und präsentierten einige Überlegungen für mobile Anwen-<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
www.javaspektrum.de 1
IntegrationsSpektrum<br />
JS-3-17 – grötz – k1<br />
<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
dungen. Nun werfen wir einen Blick auf die Entwicklung von<br />
Microservices. Das Ganze am Beispiel einer mobilen Anwendung<br />
für Hobby-Angler.<br />
Die Probleme eines Hobby-Anglers<br />
Es ist ein großer Vorteil für Angler zu wissen, wann die Sonne<br />
auf und wann die Sonne untergeht. Warum? Weil auf manchen<br />
Gewässern das Nachtfischen verboten ist. Nun stellt sich die<br />
Frage, von wann bis wann ist es eigentlich Nacht? Die Antwort<br />
steht im Fischereigesetz: eine Stunde nach Sonnenuntergang<br />
bis eine Stunde vor Sonnenaufgang. Daher muss man wissen,<br />
wann die Sonne auf und untergeht.<br />
Dann gibt es ja auch noch die Wetterfrage.<br />
Was zieht der Angler an? Welche<br />
Kleidung muss berücksichtigt werden?<br />
Überrascht mich wieder das Wetter und<br />
ich muss den Anglertrip abbrechen?<br />
All diese Fragen beantwortet unsere mobile<br />
App SunRiseSet (s. Abb. 2). Sie liefert<br />
für einen eingegebenen Ort den Sonnenauf-<br />
und Untergangszeitpunkt, die Wetterdaten<br />
und die Landkarte. Ausgehend von<br />
den Millionen von registrierten Fischereikartenbesitzern<br />
(und noch mehr Schwarzfischern),<br />
wird diese App sicher ein großer<br />
Erfolg. Aus diesem Grund müssen sich Benutzer<br />
registrieren, damit wir mit den Kundendaten<br />
Geld verdienen können.<br />
Abb. 2: Startscreen Soweit die Theorie. Doch beschäftigen<br />
der App<br />
wir uns nun mit der Praxis: der Microservice-Architektur,<br />
die alle diese Daten ermittelt,<br />
speichert und zur Verfügung steht.<br />
SunRiseSet – Microservice-Architektur<br />
Unser System besteht auf der Backend-Seite aus sechs Microservices,<br />
die in Docker-Containern laufen. Der Location-<br />
Service und der Weather-Service greifen auf externe APIs zu.<br />
Die Services sind ein Mix aus NODE.JS und Python und haben<br />
folgende Funktionalitäten:<br />
H API-Gateway dient als Facade. Nur über dieses Gateway<br />
kann auf die dahinterliegenden Service zugegriffen werden.<br />
H Auth dient zu Authentifizierung des User und der Tokenverwaltung.<br />
H MariaDB dient zum Persistieren der Userdaten.<br />
H Location dient zur Ermittlung der Geolocation via externen<br />
Webservice.<br />
H Weather ermittelt die Wetterdaten für die Geolocation via<br />
externen Webservice.<br />
H Sun berechnet Sonnenauf- und Sonnenuntergang für die<br />
Geolocation.<br />
Jedes Service läuft in einem eigenen Docker-Container und<br />
liefert JSON zurück.<br />
Ablauf<br />
Das API-Gateway nimmt Anfragen von Clients (Mobile und<br />
Web) entgegen, führt die Authentifizierung durch und greift<br />
auf die dahinterliegenden Services zu. Abbildung 3 enthält das<br />
Komponentendiagramm, das Sequenzdiagramm in Abbildung<br />
4 zeigt die Details des Login-Vorgangs.<br />
Abb. 3: Übersicht der Docker-Container & Microservices<br />
Abb. 4: Login-Vorgang<br />
Der Login-Service<br />
Unser erster Microservice ist für das Login über das API-Gateway<br />
zuständig. Er besteht gerade mal aus 23 Zeilen (20 ohne<br />
Leerzeilen) NODE.JS-Code, verwendet Port 8080, HTTP und<br />
liefert JSON zurück.<br />
20 Zeilen Code – in Österreich würde man sagen „Ned<br />
schlecht Herr Specht“, oder anders ausgedrückt „Gut gemacht,<br />
bemerkenswert!“<br />
Zur Vereinfachung werden wir uns im Folgenden nur um<br />
diesen Microservice kümmern. Das gesamte Projekt finden Sie<br />
auf GitHub [GitLab]. Wir werden keine Zeit damit verbringen,<br />
den Code der Microservices zu diskutieren.<br />
var express = require('express');<br />
var request = require('request');<br />
var cors = require('cors');<br />
var app = express();<br />
app.use(cors());<br />
app.get('/login', function(req, res) {<br />
request('http://auth:8080/token?username=' + req.query.username +<br />
'&password=' + req.query.password, function(err1, r1, data) {<br />
var _data = {};<br />
try {<br />
var _data = JSON.parse(data);<br />
if(_data.success === true) {<br />
return res.send(_data);<br />
} else {<br />
return res.status(403).send(_data)<br />
}<br />
} catch(e) {<br />
console.error('could not parse auth response', e);<br />
return res.sendStatus(500);<br />
}<br />
});<br />
});<br />
Listing 1: Authentication-Service<br />
Der Aufruf des Service im Browser via<br />
http://localhost:8000/login?username=rgroetz&password=test123<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
2<br />
JavaSPEKTRUM 3/2017
IntegrationsSpektrum<br />
JS-3-17 – grötz – k1<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
sollte folgenden JSON-String zurück liefern:<br />
{"success":false,"errorCode":4000,"message":"invalid credentials"}<br />
Momentan aber liefert der Aufruf noch die Fehlermeldung:<br />
localhost refused to connect.<br />
zurück, da die Services noch nicht in den Docker-Containern<br />
laufen. Zu Docker und wie wir die Microservice-Architektur<br />
zum Laufen bringen, kommen wir im nächsten Abschnitt.<br />
Dockerized Microservice – Build, Ship, Run<br />
Im Vergleich zu klassischen monolithischen Anwendungen<br />
sind die einzelnen Mikroservices für sich gesehen jeweils nicht<br />
sonderlich komplex (siehe 20 Zeilen Code). Einen großen Overhead<br />
bringt jedoch die für die Mikroservices benötigte Infrastruktur<br />
mit sich. Anstatt einer Systemumgebung (Application<br />
Server, Datenbank, …) für die gesamte Anwendung muss für<br />
jeden Microservice eine eigene Systemumgebung installiert,<br />
konfiguriert und betreut werden.<br />
Mit Docker [Tie15] können die einzelnen Microservices inklusive<br />
der benötigten Abhängigkeiten in leichtgewichtige<br />
Container gepackt werden. Docker macht es einfach, die Software<br />
zu paketieren, zu verteilen und auszuführen. Mit Docker<br />
können Tests im Container ausgeführt und in verschiedensten<br />
Umgebungen isoliert getestet werden.<br />
Zusammen mit Cloud-Plattformen besteht jetzt eine einfache<br />
Möglichkeit, das Testen auf mehreren Plattformen vorhersehbar<br />
zu machen. Dies gibt das Vertrauen, das, wenn etwas<br />
auf einer Systemumgebung funktioniert, es auch in der Produktionsumgebung<br />
funktioniert. Die Aussage „Works on my<br />
machine!“ bekommt dadurch gleich wieder eine andere Bedeutung.<br />
Erstellung eines Docker Image<br />
Ein Docker Image ist ein Softwarepaket, bestehend aus allen<br />
Abhängigkeiten und deren Binärdateien. Der Start eines Docker<br />
Image instanziiert einen Docker Container, welcher dann<br />
eine komplette Laufzeitumgebung, wie vom Ersteller des<br />
Image definiert, zur Verfügung stellt.<br />
Anhand eines Dockerfile, welches alle Kommandos enthält,<br />
die zum Bau des Image notwendig sind, kann die Erstellung<br />
des Docker Image automatisiert werden. Listing 2<br />
zeigt das Dockerfile für unser Image, das in weiterer Folge<br />
die Laufzeitumgebung für den Login-Microservice zur Verfügung<br />
stellt.<br />
FROM node:6.9-alpine<br />
COPY ./src /data<br />
WORKDIR /data<br />
RUN npm install && \<br />
npm cache clean && \<br />
rm -rf /tmp/*<br />
EXPOSE 8080<br />
CMD ["node", "server.js"]<br />
Listing 2: Das Dockerfile<br />
Das Dockerfile aus Listing 2 weist Docker an:<br />
H Baue das Image initial mit einem NODE.JS 6.9 Image.<br />
H Kopiere das Verzeichnis ./src/data in das aktuelle Image-<br />
Verzeichnis.<br />
H Definiere /data im Image als das Arbeitsverzeichnis.<br />
H Installiere die NODE.JS-Applikation (unser Microservice)<br />
aus dem Arbeitsverzeichnis.<br />
H Mache Port 8080 nach außen verfügbar.<br />
H Starte die Applikation server.js (unser Microservice).<br />
Mit dem Docker-Kommando:<br />
$ docker build -t facade-login .<br />
starten wir den Bau des Docker Image im lokalen Arbeitsverzeichnis.<br />
Mit Parameter -t geben wir dem Image einen Namen.<br />
So, jetzt haben wir ein Docker Image, das wir in weiterer Folge<br />
zu einem Docker Container machen und in unsere Registry<br />
(registry.gitlab.com/devopsbusters) hochladen und dann für unser<br />
Docker-Compose brauchen. Zur Vereinfachung werden wir<br />
uns nur um den Bau dieses Docker Image in diesem Artikel<br />
kümmern.<br />
Start eines Docker-Containers<br />
Die Schritte, die zum Installieren und Starten eines Mikroservice<br />
in einem Docker-Container notwendig sind, werden über<br />
eine Docker-Compose-Datei automatisiert. So ist es mit Docker-Compose<br />
möglich, die Erstellung der Images zu automatisieren,<br />
und die Erstellung von Docker-Containern kann in jedes<br />
Build-Tool integriert werden.<br />
Die Aufgabe von Docker-Compose ist das Management einer<br />
Gruppe von Docker-Containern. Die Deklaration einer<br />
Gruppe wird in der Datei docker-compose.yml abgelegt. Mit<br />
dem Werkzeug können nun einzelne Container oder Gruppen<br />
erzeugt, verwaltet und gestartet werden.<br />
Eine detaillierte Einführung in Docker, seine Tools und sein<br />
Build-, Ship-, Run-Konzept finden Sie hier: www.docker.com/.<br />
Listing 3 zeigt die Docker-Compose-Datei unserer Microservice-Landschaft.<br />
version: '2'<br />
services:<br />
sun:<br />
build: ../service-sun<br />
image: registry.gitlab.com/devopsbusters/service-sun:master<br />
ports:<br />
- "8001:8080"<br />
location:<br />
build: ../service-location<br />
image: registry.gitlab.com/devopsbusters/service-location:master<br />
ports:<br />
- "8002:8080"<br />
weather:<br />
build: ../service-weather<br />
image: registry.gitlab.com/devopsbusters/service-weather:master<br />
ports:<br />
- "8003:8080"<br />
auth:<br />
build: ../service-auth<br />
image: registry.gitlab.com/devopsbusters/service-auth:master<br />
ports:<br />
- "8004:8080"<br />
login:<br />
build: ../facade-login<br />
image: registry.gitlab.com/devopsbusters/facade-login:master<br />
ports:<br />
- "8005:8080" <br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
www.javaspektrum.de 3
IntegrationsSpektrum<br />
JS-3-17 – grötz – k1<br />
<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
links:<br />
- auth<br />
api:<br />
build: ../facade-api<br />
image: registry.gitlab.com/devopsbusters/facade-api:master<br />
ports:<br />
- "8000:8080"<br />
links:<br />
- auth<br />
- sun<br />
- location<br />
- weather<br />
Listing 3: Docker-Compose-Datei<br />
Mit dem Befehl:<br />
docker-compose build<br />
bauen wir die Container für unserer Microservice-Umgebung.<br />
Mit dem Befehl:<br />
docker-compose up -d<br />
werden die Container gestartet und die Services zur Verfügung<br />
gestellt.<br />
Up & Running?<br />
So, die Microservices sind codiert, die Docker Images gebaut,<br />
die Microservices laufen in den Docker-Containern. So unsere<br />
Vermutung. Doch stimmt unsere Vermutung? Mit:<br />
docker ps<br />
prüfen wir, welche Docker-Container gestartet sind. Und siehe<br />
da, sechs Microservices laufen wie definiert (s. Abb. 5).<br />
Abb. 5: Die laufenden Microservices<br />
Nun prüfen wir noch auf leichte Art und Weise via Browser,<br />
dass die Services laufen und korrekt zusammenarbeiten:<br />
http://localhost:8005/login?username=rgroetz&password=test123<br />
Und siehe da, unsere Microservices laufen (s. Abb. 6)! Im Gegensatz<br />
dazu liefert der Aufruf mit einem gültigen User folgende<br />
JSON-Nachricht zurück (s. Abb. 7).<br />
Nachdem wir unsere Services nicht immer manuell testen<br />
wollen, kümmern wir uns jetzt um automatisierte Tests, die in<br />
unserer Continuous Delivery Pipeline [Fow13] laufen sollen.<br />
Testen von Microservices<br />
Eine Microservice-Architektur besteht aus unabhängig einsetzbaren<br />
Diensten, die es erschweren, nur altbekannte Testansätze<br />
in der Pipeline anzuwenden.<br />
Durch das Zerteilen eines Systems in kleine, eigenständige<br />
Services werden zusätzliche Schnittstellen sichtbar, die in einer<br />
Abb. 6: Login-Versuch mit ungenügenden Berechtigungen<br />
Abb. 7: Login-Versuch mit ausreichenden Berechtigungen<br />
monolithischen Architektur nicht vorhanden waren. Zusätzliche<br />
Schnittstellen, nicht nur in technischer, sondern auch in<br />
organisatorischer Hinsicht, sind hilfreich, sind doch nun meistens<br />
mehrere Teams an der Entwicklung beteiligt.<br />
Die Zusammenarbeit mehrerer Services, sowohl aus Consumer-<br />
als auch Provider-Sicht, und die reibungslose Integration<br />
der Services untereinander sind daher ein wichtiger Aspekt.<br />
Diesem Umstand wird durch zusätzlichen Tests, besser bekannt<br />
unter dem Begriff „Consumer-Driven Contracts“, begegnet.<br />
In der Welt der Softwaretests gibt es viele Bezeichnungen<br />
für die verschiedensten Arten von Tests. Eine strikte Trennung<br />
nach Zielen und Methoden ist sehr schwer. Das ISTQB-Glossar<br />
[ISTQB] alleine unterscheidet fünf verschiedene Arten von<br />
Acceptance-Tests. Diverse Testpyramiden, verschiedenster<br />
Testspezialisten [Scott, Ash14] tragen nicht dazu bei, hier Unklarheiten<br />
auszuräumen. Zum Test von Microservice-Umgebungen<br />
sollten unserer Meinung nach folgende Tests Anwendung<br />
finden.<br />
Komponententests<br />
Unit- oder Komponententests sind immer noch anwendbare<br />
Testansätze, denn innerhalb seiner Grenzen ist jeder Service noch<br />
sehr zusammenhängend. Diese Tests durchlaufen kleine Code-<br />
Abschnitte, also Klassen oder Methoden. Abhängige Komponenten<br />
werden dabei durch Mocks ersetzt, um den Service in einer<br />
isolierten Umgebung unabhängig testen zu können.<br />
Integrationstests<br />
Durch die zusätzlichen Schnittstellen werden Integrationstests<br />
noch wichtiger für das Gesamtgefüge. Wurden bisher die Daten<br />
innerhalb eines Monolithen ausgetauscht, kommt durch<br />
Microservices der Datenaustausch, in den meisten Fällen via<br />
HTTP, ins Spiel. Die Integrationstests sollen sicherstellen, dass<br />
dieses Zusammenspiel funktioniert.<br />
Consumer-Driven Contracts<br />
H it Consumer-Driven Contracts (CDC) ist ein fast vergessener<br />
Testansatz wieder in den Mittelpunkt gerückt.<br />
Thoughtworks hat aufgrund des Microservice-Hypes Consumer<br />
Driven Contracts wieder auf den Technology Radar<br />
[ThoughtWorks] zurückgeholt:<br />
„… We’ve decided to bring consumer-driven contract testing back<br />
from the archive for this edition even though we had allowed it to<br />
fade in the past. The concept isn’t new, but with the mainstream<br />
acceptance of microservices, we need to remind people that consumerdriven<br />
contracts are an essential part of a mature microservice testing<br />
portfolio, enabling independent service deployments …“<br />
Consumer-Driven Contracts betrachten Microservices aus<br />
der Perspektive der aufrufenden Einheit. Der Contract beschreibt,<br />
wie die Antwort des Providers aufgebaut sein muss.<br />
Im Grunde genommen definiert der Consumer eines Service<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
4<br />
JavaSPEKTRUM 3/2017
IntegrationsSpektrum<br />
JS-3-17 – grötz – k1<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
zusammen mit dessen Provider einen Kontrakt und prüft in<br />
einem Test, ob dieser eingehalten wird. Stellt der Provider nun<br />
eine neue Version des Service zur Verfügung, stellt er durch<br />
den Contract-Test aller Consumer sicher, dass alle Serviceaufrufe<br />
der Consumer noch funktionieren.<br />
End-to-End-Tests<br />
End-to-End-Tests kommen zum Einsatz, um die volle Qualität<br />
des Systems zu gewährleisten. Diese Tests prüfen, ob die<br />
Geschäftsfälle, wie sie ein Anwender durchführt, das richtige<br />
Ergebnis liefern. Es wird dabei sichergestellt, dass Systemabhängigkeiten<br />
und Datenintegrität zwischen verschiedenen<br />
Systemkomponenten und Systemen beibehalten werden.<br />
Lasttests<br />
Lasttests sind eine Unterkategorie von Performanztests und prüfen,<br />
ob die Leistung der Software unter ungünstigen und extremen<br />
Bedingungen zufriedenstellend ist. Diese Bedingungen<br />
können als Folge von starkem Netzwerkverkehr, Prozessorauslastung<br />
oder Überlastung einer bestimmten Ressource auftreten.<br />
Fazit<br />
Der Trend in der Systemarchitektur geht weg vom Monolithen,<br />
hin zu Microservices, um durch kleine überschaubare<br />
Programmeinheiten Continuous Delivery zu ermöglichen und<br />
Features schneller dem Markt zur Verfügung stellen zu können.<br />
Durch das Zerteilen eines Systems in kleine, eigenständige<br />
Services werden zusätzliche Schnittstellen sichtbar, die in einer<br />
monolithischen Architektur nicht vorhanden waren. Diesem<br />
Umstand muss mit altbekannten und neuen Arten von Tests<br />
innerhalb der Continuous Delivery Pipeline begegnet werden.<br />
Im nächsten Teil dieser Artikelserie widmen wir uns dieser Herausforderung<br />
und gehen näher auf die Implementierung dieser<br />
automatisierten Tests ein.<br />
Links<br />
[Ash14] St. Ashman, Post, 28.12.2014,<br />
http://qa-matters.com/2014/12/28/layers-of-test-automation/<br />
[Fow13] https://martinfowler.com/bliki/DeploymentPipeline.html<br />
[Fow14] https://martinfowler.com/articles/microservices.html<br />
[GitLab] https://gitlab.com/devopsbusters<br />
[ISTQB] https://www.astqb.org/glossary<br />
[NGINX] API-Gateway Pattern, https://www.nginx.com/blog/<br />
building-microservices-using-an-api-gateway/<br />
[Scott] Blog von A. Scott,<br />
https://watirmelon.blog/tag/testing-pyramid/<br />
[ThoughtWorks] https://www.thoughtworks.com/radar/techniques/<br />
consumer-driven-contract-testing<br />
[Tie15] F. Tiersch, Docker – Teil 1, 17.8.2015,<br />
https://www.ab-heute-programmieren.de/docker-teil-1-was-ist-docker/<br />
[Wiki] https://de.wikipedia.org/wiki/Unix-Philosophie<br />
Rudolf Grötz, seit 30 Jahren in der IT unterwegs<br />
und passionierter Softwaretester, ist seit 2016 als<br />
Testarchitect/Technical Test Analyst bei s-ITSolutions in<br />
Wien im Bereich Softwaretests tätig und lebt den Leitspruch<br />
„Testautomation is not an act, Testautomation<br />
is a habit!“ Neben Fachartikeln in diversen Magazinen<br />
versorgt er die Community auch mit Konferenzauftritten<br />
und organisiert das Vienna Agile Testautomation<br />
Meetup. E-Mail: rudolf.groetz@gmail.com<br />
Dominik Kukacka, bekennender „Clean Coder“,<br />
entwickelt schon seit seiner frühesten Kindheit mit<br />
Begeisterung Software. Seit 2015 verantwortet er als<br />
CTO bei apilayer in Wien die Planung und Umsetzung<br />
der kompletten Service-Landschaft.<br />
Nebojsa Nikolic ist seit mehr als 20 Jahren als<br />
Oracle-DBA und Systemadministrator tätig. Seit 2016<br />
bei SVC designing E-Health in Wien angestellt, gehört<br />
es dort zu seinen Aufgaben, Microservices und Virtualisierung<br />
voranzutreiben, um dem DevOps-Gedanken<br />
gerecht zu werden. Privat ist er leidenschaftlicher<br />
Angler.<br />
Die Meinung in diesem Artikel ist die Meinung der Autoren und<br />
gibt nicht unbedingt die Meinung deren Arbeitgeber wieder!!!<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
9<br />
10<br />
11<br />
12<br />
13<br />
14<br />
15<br />
16<br />
17<br />
18<br />
19<br />
20<br />
21<br />
22<br />
23<br />
24<br />
25<br />
26<br />
27<br />
28<br />
29<br />
30<br />
31<br />
32<br />
33<br />
34<br />
35<br />
36<br />
37<br />
38<br />
39<br />
40<br />
41<br />
42<br />
43<br />
44<br />
45<br />
46<br />
47<br />
48<br />
49<br />
50<br />
51<br />
52<br />
53<br />
54<br />
55<br />
56<br />
57<br />
58<br />
59<br />
60<br />
61<br />
62<br />
63<br />
64<br />
65<br />
www.javaspektrum.de 5