13. Das programmierbare Web

Viele Apps und Websites werden heute nicht nur mit Bibliotheken und viel Code erstellt, sondern nutzen öffentlich verfügbare Programmierschnittstellen (APIs).

CLoud-Anbieter wie Microsoft, Amazon oder Google bieten ganze Sammlungen solcher Dienste, die miteinander verbunden (integriert) sind. Die führt dazu, dass auch komplexe Anwendungen in kurzer Zeit entstehen und damit die Anzahl der Anwendungen exponentiell zunimmt.

Neben diesen Angeboten kommt der Gesamtarchitektur große Bedeutung zu. Skalierbarkeit ist essenziell, denn Leistung ist eine Funktion wie alles andere auch. Jede Millisekunde Reaktionszeit zuviel kostet Benutzer.

13.1 Cloud-Dienste

Cloud-Dienste stellen zum einen Funktionen zur Verfügung. Zum anderen dienen Sie auch dazu, eigene Funktionen isoliert bereitzustellen. Man spricht dann von serverlosem Arbeiten.

Ein typisches Beispiel sind die Lambda-Dienste in Amazons AWS. Hiermit lassen sich einzelne Funktionen erstellen, die einen HTTP-Endpunkt haben. Andere Instanzen können diese Funktionen dann aufrufen. Lambda lässt sich mit Python, Java oder Node programmieren. Hier werden in Fortsetzung der vorherigen Kapitel die Nutzung mit Node und JavaScript betrachtet.

Die Art und Weise, wie eine Anwendung entsteht, unterscheidet sich fundamental von der klassischen Serverinstallation. Eine Anwendung zerfällt in eine Reihe einzelner Dienste, sogenannte Micro-Services. Das ist ideal, wenn du beispielsweise eine App für ein Smartphone entwickelst, die sich auf Dienste stützt. Hier wird keine Auslieferung von HTML oder CSS anstehen, denn dies passiert über den App-Store. Lediglich die persistierenden Dienste werden bereitgestellt. Vorteil der Cloud-Architektur ist das smarte Preismodell, das die tatsächliche Nutzung reflektiert und die teilweise extreme Skalierbarkeit. Amazon spricht deshalb auch von der Elastic Compute Cloud”.

Abbildung: Startseite von Amazons EC2
Abbildung: Startseite von Amazons EC2

Amazon ist nicht der einzige Anbieter. Neben den “großen” wie Microsoft oder Google sind viele kleinere Anbieter am Markt mit interessanten Angeboten. Aufgrund des Platzes im Buch und der leichten Erreichbarkeit stelle ich hier stellvertretend Amazon vor.

13.1.1 Fragmente einer Applikationsumgebung

Anwendungen benötigen meist einen immer ähnlichen Stapel von Bausteinen:

Darüberhinaus gibt es Spezialfunktionen wie Spracherkennung, Bildverarbeitung oder Videokompression. Solche Systeme programmiert niemand mehr selbst. Sie sind alle online jederzeit verfügbar.

13.1.2 Amazon S3 – Simple Storage Service

Dies ist der Speicher, hier wird die Anwendung abgelegt. Der Speicherplatz wird als Bucket bezeichnet.

Abbildung: Ein Bucket anlegen
Abbildung: Ein Bucket anlegen

Klick nun auf die Schaltfläche. Im nächsten Schritt werden zwei Dinge abgefragt:

Als Region wähle ich Frankfurt (das ist Frankfurt am Main) – der für mich nächste Platz. Der Name darf keine Sonderzeichen außer “-“ enthalten und muss kleingeschrieben werden. Er ist global eindeutig. Ich wähle für dieses Buch “joergisageek-bookapp”, aber dieser Name ist nun weg – nimm einen anderen.

Lade nun eine erste Datei hoch. Setze dabei die Zugriffsrechte und mache die Datei dann öffentlich sichtbar.

13.1.2.1 Automation

Nun werden häufig Änderungen an den Dateien nötig sein. Nutze dann das Werkzeug Gulp, das bereits vorgestellt wurde:

 1 var gulp = require('gulp'),
 2     awspublish = require('gulp-awspublish');
 3 
 4 var localConfig = {
 5   buildSrc: './build/**/*',
 6   getAwsConf: function (environment) {
 7     var conf = require('../../config/aws');
 8     if (!conf[environment]) {
 9       throw 'No aws conf for env: ' + environment;
10     }
11     if (!conf[environment + 'Headers']) {
12       throw 'No aws headers for env: ' + environment;
13     }
14     return { keys: conf[environment], headers: conf[environment + 'H\
15 eaders'] };
16   }
17 };
18 
19 gulp.task('s3:production', ['build:production'], function() {
20   var awsConf = localConfig.getAwsConf('production');
21   var publisher = awspublish.create(awsConf.keys);
22   return gulp.src(localConfig.buildSrc)
23     .pipe(awspublish.gzip({ ext: '' }))
24     .pipe(publisher.publish(awsConf.headers))
25     .pipe(publisher.cache())
26     .pipe(publisher.sync())
27     .pipe(awspublish.reporter());
28 });

Die persönlichen Zugangsdaten sind in einer weiteren Datei geschützt:

 1 module.exports = {
 2   'production': {
 3     'accessKeyId': 'Amazon_s3_production_access_key',
 4     'secretAccessKey': 'Amazon_s3_production_secret_key',
 5     'region': 'Amazon_s3_production_region',
 6     'params': {
 7         'Bucket': 'Amazon_s3_production_bucket_name'
 8     }
 9   },
10   'productionHeaders': {},
11 };

Mehr Details sind im Web zu finden.

Der Upload alleine reicht nicht. Ein weiterer Dienst ist nötig, der die Schnittstelle zum hin Nutzer bereitstellt: CloudFront.

13.1.3 CloudFront

Amazon CloudFront ist ein globaler CDN-Service (Content Delivery Network). Derartige Dienste stellen statische Dateien weltweit bereit und optimieren den Abruf, sodass globale Ressourcen bestmöglich ausgenutzt werden. Du kannst damit Daten, Videos, Anwendungen und APIs mit niedriger Latenz und hohen Übertragungsgeschwindigkeiten auf sichere Weise bereitstellen. CloudFront ist Teil der Amazon Web Service (AWS) und in diese integriert. Sowohl die physischen Standorte, die direkt mit der globalen AWS-Infrastruktur verbunden sind, als auch Software, die zusammen mit andern AWS-Services funktioniert. Einige interne Transfers zwischen diesen Diensten sind kostenfrei und belasten die zu zahlenden Transfervolumen nicht. Neben der Bereitstellung ist die Nähe zum potenziellen Kunden einer der großen Vorteile von CDNs. Wenn du nur lokale Kunden hast, werden sich hier eher weniger Vorteile ergeben. Ein klassischer Hosting-Dienstleister ist dann die bessere Wahl. Typische Kunden von CloudFront sind Spotify (Musik-Streaming), Samsung oder Vivono (Wein-Analyse und -Marketing).

13.2 Container mit Docker

Docker ist eine Ungebung zum Ausführen von Containern. Im Grunde handelt es sich dabei um eine Art virtuelle Maschine (VM), die genau eine Applikation ausführt. Der Vorteil besteht im Ressourcenverbrauch. Statt eine mehrere GByte große VM hochzufahren, wird lediglich eine kleine, schlanke Umgebung gestartet, die exakt das bietet, was eine Applikation braucht. Das ist meist nicht viel, denn Web-Anwendungen bringen die benötigten Bibliotheken und Frameworks mit. Vom Betriebssystem wird meist nur Festplatten- und Netzwerkzugriff benutzt.

Derart schlanke Container sind komplett isoliert und lassen sich auch einzeln aktualisieren. Da es keine anderen Awendungen gibt, muss man auf nichts Rücksicht nehmen. Ebenso einfach ist die Skalierung. Auf dem Speicherabdruck einer klassischen VM lassen sich oft dutzende Container starten. Container bilden Ketten, sodass Varianten von Funktionsbereitstellungen auf anderen Containern basieren. Container teilen sich überdies die Hardware des Hosts und abstrahieren sie nicht wie bei einer VM. Ohne diese Schicht (HAl, Hardware Abstraction Layer) sind Container in der Regel auch schneller.

13.2.1 Begriffe

Der wichtigste Begriff ist Container. Der Container enthält das auszuführende Programm und eine Beschreibung. Applikationen in einem Container laufen direkt auf dem Kern des Hosts. Der Zugriff auf Ressourcen, beispielsweise Netzwerkkarten, erfolgt direkt. Jeder Container ist für den Host ein vollständig isolierter Prozess mit eigenem Speicherbereich.

Die startbaren Container werden von einem (oder mehreren) Repositories abgerufen. Die gibt es auch öffentlich, beispielsweise für ein schnelles “Hello World”-Beispiel.

Während der Container die aktive Instanz ist, ist das Image quasi die Vorlage dafür. Man kann viele derartige Images haben und dann daraus imemr wieder neue Instanzen (Container) starten. Images leiten sich in der Regel von anderen Images ab, sie bilden also eine Art Hierarchie. Dadurch sind einzelne Images möglicherweise sehr viel kleiner, als es der Funktionsumfang des Containers erwarten lässt. Jedes Image beschreibt nur die Änderungen zum vorhergehenden. Das Image enthält alle Informationen, die zum Starten benötigt werden. Dies umfasst den Code der Applkikation, die Laufzeitumgebung, Bibliotheken, Konfigurationen und Umgebungsvariablen.

13.2.2 Container Infrastruktur

Applikationen in einem Container laufen direkt auf dem Kern des Hosts. Es gibt keine Virtualisierungsschicht. Dadurch sind Container schlanker und schneller. Das ist ein wesentlicher Unterschied zu virtellen Maschinen.

Abbildung: Docker und Virtuelle Maschine
Abbildung: Docker und Virtuelle Maschine

Virtuelle Maschinen laufen auf einem Gast-System, das im Host vewaltet wird. Dieses Prinzip führt zu einer starken Verflechtung von Betriebssystem, systemweiten Abhängigkeiten, Sicherheitseinstellungen und Patch-Leveln. Daraus ergeben sich viele Kombinationsmöglichkeiten, die leicht zu vergessen sind, jedoch nur schwer korrekt wiederherstellbar sind. Das Ergebnis sind ressourcenhungrige Umgebungen und vielfältige Fehlermöglichkeiten.

Bei Docker ist die Struktur anders. Es gibt nur einen Host, der für alle Container ein Betriebssystem liefert. Docker leitet die Anwendungsaufrufe direkt durch.

Abbildung: Docker und Virtuelle Maschine (Quelle: Docker-Dokumentation)
Abbildung: Docker und Virtuelle Maschine (Quelle: Docker-Dokumentation)

Die Abbildung von Containern erfolgt als Prozess.

13.2.3 Docker nutzen

Docker benötigt eine lokalen Dienst, der die Container erstellt und verwaltet. Des weiteren wird eine Verwaltungsumgebung erwartet – das Repository – wo Container angemeldet werden. Von dort werden die Abbilder (images) geladen und dann gestartet.

Wer schnell und einfach starten will, der nutzt Docker Hub. Dort kann man private oder öffentliche Repositories erstellen und seine Container ablegen. Alternativ geht es lokal auf einem Linux- oder Windows-System. Oder bei den bereits erwähnten Cloud-Anbietern, beispielsweise AWS oder Azure.

Eine typische Umgebung zum Testen und Entwickeln könnte nun so aussehen, dass die Entwicklung auf einem lokalen Ubuntu stattfindet. Hier hast du alles, was für eine Web-Anwendung benötigt wird. Als Repository wird Docker Hub benutzt. Wenn das Projekt eher kommerziell ist und eine Einbettung in eine Enterprise-Umgebung angedacht ist, wird ein Aufbau bei Microsoft Azure in Frage kommen. Dann wird das Repository über die Azure-Umgebung eingerichtet. Das passiert idealerweise über einen Windows Server, wo auch lokale Tests stattfinden. Dort kannst du ein lokales Test-Repository betreiben. So lässt sich der Weg von der Linux-VM auf den lokalen Host testen. Geht es in die Produktion, wird das öffentliche Repository benutzt.

Docker wird weitgehend über eine Kommandozeile bedient. Es gibt alternativ auch grafische Clients, jedoch sind praktisch alle Anleitungen im Internet auf die Kommandzeile ausgelegt, sodass die Adaption von Texten im Netz nur erschwert wird.

13.2.3.1 Erste Schritte

Um einen Container zum Laufen zu bekommen, wird zuerst ein Image benötigt. Dies wird mittels einer Konfigurationsdatei und einem Kommando erstellt. Die Konfigurationsdatei heißt üblicherweise DockerFile (ohne Dateierweiterung). Sie sieht beispielsweise folgendermaßen aus:

 1 FROM ubuntu:16.04
 2 
 3 MAINTAINER Jörg Krause version: 1.0
 4 
 5 RUN apt-get update && apt-get install -y apache2 && apt-get clean &&\
 6  rm -rf /var/lib/apt/lists/*
 7 
 8 ENV APACHE_RUN_USER www-data
 9 ENV APACHE_RUN_GROUP www-data
10 ENV APACHE_LOG_DIR /var/log/apache2
11 
12 EXPOSE 80
13 
14 CMD ["/usr/sbin/apache2", "-D", "FOREGROUND"]

Ein solche Konfigurationsdatei ist in erster Linie eine Folge von Befehlen, die sequenziell abgearbeitet werden. Der wichtigste ist FROM. Damit wird das Basis-Image bestimmt. Das Image kann auch manuell beschafft werden, indem folgende Kommando ausgeführt wird:

docker pull ubuntu:16.04

“ubuntu:16.04” ist hier nur ein Beispiel. Die Quelle ist die Image-Registrierung. Standardmäßig wird die öffentliche von Docker benutzt, genannt Docker Hub. Images lassen sich qualitativ nach Stars (Sternen) und Pulls (Downloads) beurteilen – je mehr je besser. Das Basis-Image für einen HTTP-Server, httpd beispielsweise, kommt auf 1400 Sterne und mehr als 10 Millionen Downloads. Es gibt derzeit (Ende 2017) knapp 2.500 Images. Jedes Images kommt in mehreren Varianten, den sogenannten Tags. Bei httpd wäre eine Varianten httpd:2.4. Der Tag-Name ist frei wählbar und nicht zwingend sinnvoll, im konkreten Fall aber zeigt er die benutzte Version des Apache-Webservers an. Ein minimalistisches DockerFile wäre dafür:

1 FROM httpd:2.4
2 COPY ./dist/ /usr/local/apache2/htdocs/

Der Kopier-Befehl COPY kopiert das Web-Projekt in das Verzeichnis, wo der Apache-Webserver zugreift. Die Quelle ist ein Unterverzeichnis des Ordners, in dem der Erstellungsbefehl ausgeführt wird:

1 docker build -f ./DockerFile --force-rm=true --tag="mysite"

Der Befehl erstellt ein Image und sendet es an die lokale Registrierung. Jede Dockerinstallation verfügt über eine solche und nimmt die Images auf. Neben der Verwaltung der Images befinden sich hier auch die Informationen, wo die Images physisch (auf der Festplatte) abgelegt werden. --force-rm entfernt Zwischenschritte, die Aufgrund der Hierarchie erstellt werden. Solche “halben” Images sind sinnvoll bei der Fehlersuche; läuft dagegen alles, nehmen sie nur unnötig Platz weg. Das Tag (--tag) beschreibt die konkrete Version weiter, damit man bei mehreren ähnlichen Images nicht die Übersicht verliert.

Falls ein Container dieses Images bereits existiert, wäre nun ein Aufräumbefehl sinnvoll:

1 docker rm mycontainer

Nun kann ein neuer Container erstellt werden.

Da der Container die aktive Ausprägung eines Images ist, wird der Befehl run benutzt:

1 docker run -p 127.0.0.1:8080:80 --name mycontainer mysite

Es gibt natürlich viel mehr Optionen, aber die wichtigsten sind:

Am Ende des Befehls folgt der Name des Images. Nun läuft der Container. Ob er einen Befehl ausführt und sofort wieder endet oder ob er endlos läuft, hängt von seinem Innenleben ab. Kleine Container für isolierte Aktionen sind durchaus üblich. Ganze Schwärme langlaufender Container ebenso.

13.2.3.2 Weitere Kommandos

Dieser Text kann und soll nicht die offizielle Dokumentation ersetzen. Für die ersten Schritten wird jedoch nur eine kleine Auswahl benötigt.

Folgendermaßen lassen sich aktive Prozesse leicht durch ein Kommando abfragen:

1 docker ps

https://www.projectatomic.io/blog/2015/07/what-are-docker-none-none-images/

13.3 Freie Dienste

Die beste Quelle für Dienste ist Das programmierbare Web. Es listet derzeit (2017) über 600 APIs. Darunter so bekannte wie Youtube oder Flickr, aber auch seltener benutzte wie BBC oder spezielle wie FedEx.

13.3.1 Wie man eine API anprogrammiert

Alle Dienste zu zeigen ist schlicht unmöglich. Aber es ist ein bisschen wie Autofahren. Kannst du einen, kannst du alle. Ich schreibe reglmäßig über meine Projekte. Der Text dazu ist auf meinem Blog im Internet zu finden. Als Startpunkt einer, der die Dienste von Amazons Alexa und einen von Huyqvarna nutzt:

13.4 Architektur

Wer mal schnell etwas entwickeln will, der wird dazu neigen, eines der fertige Templates zu benutzen und dann Funktionen hinzuzufügen. Das geht nur eine zeitlang gut. Mit jeder Funktion steigt die Gefahr, Chaos anzurichten und Fehler einzubauen, deren Beseitigung zu weiteren Fehlern führt. Steigt die Last, werden Mängel in der Architektur als Leistungsschwäche sichtbar. Mehr als wegwerfen und neu bauen bleibt oft nicht. Deshalb sollte eine gute Architektur von vornherein bedacht werden.

13.4.1 Aspekte der Architektur

Zwei Aspekten kommt dabei eine besondere Bedeutung zu:

13.4.1.1 Skalierbarkeit

Einfach nur Cloud und mehr Server sind selten eine passende Antwort auf steigenden Bedarf. Denn der Bedarf wirkt sich innerhalb der Umgebung ganz unterschiedlich aus. Stell dir vor, ein umfangreiches Shop-System wird aufgebaut. Neben einfachen Produkten sind auch komplexe Konfigurationen von individualisierten Artikeln im Angebot. Nun werden am Anfang sehr viele Benutzer sich den Shop ansehen, durch die Artikel blättern und mal das eine oder andere in den Warenkorb legen. Vielleicht ist das Produkt aber doch noch nicht so erfolgreich, oder die Preise nicht konkurrenzfähig. Jedenfalls entsteht eine hohe Last in einem Teil der Anwendung. In anderen Teilen, wie der Bezahlfunktion oder der Lagerbuchung, passiert eher wenig. Die Skalierung müsste im Idealfall stark asymmetrisch laufen, vor allem, um wertvolle Ressourcen (=Geld) schonend einzusetzen.

Später entdeckt der Shop-Betreiber, dass die Individualisierung und Verfolgung besser angenommen wird als eine sehr breite Produktauswahl. So werden zwar deutlich weniger Anforderungen im Artikelbereich gemessen, dafür aber viele komplexe Operationen in der Geschäftslogik und in der Datenbank. Die Skalierung verschiebt sich quasi nach hinten.

Einer guten Architektur ist das eigentlich egal, sie kann auf alle Bedürfnisse reagieren und der Betreiber kann Ressourcen so verteilen, wie es der bedarf gerade erfordert. Cloud-Anbieter stellen isolierte Ressourcen bereit, aus denen sich der Betreiber bedienen kann, je nach Bedarf. Nur die Software muss dazu auch passend geschrieben worden sein.

13.4.1.2 Flexibilität

Ein anderes Szenario könnte in einem Shop ebenfalls auftreten. Der Betreiber stellt fest, dass die Käufer gern auf Aktionen reagieren – zwei Artikel zum Preis von einem oder 15% Rabatt am Sonntag-Abend. Der Fantasie sind keine Grenzen gesetzt. Nun kann man schlecht jedesmal am Shop herumprogrammieren. Das Risiko, das dann etwas nicht geht und damit das reguläre Geschäft in Mitleidenschaft gezogen wird, ist zu groß. Das Aktionsmodul muss also isoliert laufen. Es muss jederzeit austauschbar sein, es muss mehrfach existieren können und es muss sich nahtlos in alle Bereich eingliedern.

Wenn ein externer Anbieter – beispielsweise ein Lieferant – mit eingebunden werden soll, so ist die Datenquelle der Aktion womöglich extern. Nun kann man sich nie auf externe Ressourcen hundertprozentig verlassen. Die Integration muss also weich erfolgen. Steht der Anbieter mit seinen Diensten zur Verfügung, wird die Vergünstigung den Kunden angeboten. Wenn nicht, läuft der reguläre Verkauf weiter. Auf keinen Fall wird dem Kunden eine Fehlermeldung gezeigt oder der Verkauf verweigert. Das System reagiert flexibel auf die Umgebung.

13.4.2 Techniken

Moderne Architekturen sind dienstorientiert. Über eine Dienstschnittstelle – das kann REST aber auch SOAP sein – werden isolierte Funktionen bereitgestellt. Die Technologie dahinter ist hier erstmal nicht relevant.

Eine wichtige Terminologie in diesem Zusammenhang sind “Microservices”. Damit ist ein funktional begrenzter, isolierter Dienst gemeint, der skalierbar ist anderen Diensten Leistungen bereitstellt.

“Microservices sind ein Architekturmuster der Informationstechnik, bei dem komplexe Anwendungssoftware aus unabhängigen Prozessen komponiert wird, die untereinander mit sprachunabhängigen Programmierschnittstellen kommunizieren. Die Dienste sind weitgehend entkoppelt und erledigen eine kleine Aufgabe. So ermöglichen sie einen modularen Aufbau von Anwendungssoftware.” Quelle: Wikipedia

13.4.3 Vergleich der Architekturen

Der Unterschied zu klassischen Vorgehensweisen wird am Besten klar, wenn man sich zuerst eine typische, partiell skalierbare Architektur anschaut:

Abbildung: Mehrschichtarchitektur einer Web-Anwendung
Abbildung: Mehrschichtarchitektur einer Web-Anwendung

Diese Anwendung ist bezüglich der Clients flexibel (Web-Browser, Tablet-App, Smartphone, Desktop, …) und skalierte linear über alle Schichten. Das Frontend ist weitgehend statisch. Der Anspruch der Flexibilität ist kaum erfüllt.

Aus Sicht des Betriebs sieht der Bedarf anders aus. Das folgende Bild zeigt eine Architektur, die hinsichtlich Sicherheit und Skalierbarkeit sehr leistungsfähig ist:

Abbildung: Mehrzonen-Sicherheitsarchitektur
Abbildung: Mehrzonen-Sicherheitsarchitektur

Neben der Sicherheit kann die Anwendung auch hervorragend auf unterschiedlichen Bedarf reagieren und entweder stärker im Frontend (mehr Benutzer) oder im Backend (mehr Rechenoperationen einzelner Benutzer) skalieren – oder beides.

Das alles bedient jedoch nicht unbedingt die Forderungen nach Flexiblität. Hier gilt es nun, in die vorgestellten Modelle Microservices einzubauen.

Abbildung: Microservices im Backend
Abbildung: Microservices im Backend

Nachfolgend die Erläuterung zu diesem Bild:

13.4.4 Microservices im Frontend

Die bisherigen Ausführungen betrafen im Wesentlichen das Backend – die Server-Seite. Nun sind in modernen Web-Anwendungen aber wesentliche Teile der Funktionen im Frontend angebildet. Deshalb sind auch hier weitere Überlegungen zur Architektur notwendig. Ob mehr Aufwand auf der einen oder anderen Seite getrieben wird, hängt vom Ziel ab. Es sit beispielsweise von Bedeutung, ob nur Websites betrieben werden oder auch Apps. Je mehr Clients in der Rechnung auftauchen, desto eher wird die Logik auf dem Server zu finden sein, um die Entwicklung weiterer Clients zu vereinfachen. Geht es nur um eine komplexe Website, erreicht man im Frontend meist eine bessere Benutzererfahrung, wenn die lofik dort abläuft – die Reaktionszeiten der Site sind schlicht besser.

Die Technologie im Frontend unterliegt auch einer größeren Dynamik. Wer ein Jahr später eine weitere Funktion integriert, wird möglicherweise hier ein anderes Framework nutzen als beim ersten Mal. Dies alles muss eine moderne Architektur verkraften.

Abbildung: Micro-Apps komplementieren Microservices
Abbildung: Micro-Apps komplementieren Microservices

Die Applikation verfügt möglicherweise nur über eine statische Startseite, von der aus auf verschiedene Teilanwendungen verwiesen wird. Diese sind mit verschiedenen Techniken entwickelt, teilen sich aber möglicherweise Programmteile.

Der am Anfang erwähnte Microservice, der ein Aktionsmodul im Shop abbildet, muss in der Sicht des Benutzers irgendwelche Ausprägungen haben. Nun könnte ein Entwickler dies leicht integrieren, indem er eine Komponente schreibt, die auf das Aktionsmodul reagiert. Die Komponente wird in React erstellt. Sie hat folgende Aufgaben:

Andere Komponenten reagieren darauf, beispielsweise indem sie den zentralen Dienst abfragen, der Zustände verwaltet (siehe Redux oder @ngrx in vorherigen Kapiteln). Der Warenkorb kann nun Rabatte abziehen oder Katalog kann Banner anzeigen, die auf Rabatte hinweisen. Die Komponenten Warenkorb oder Katalog haben dabei keinerlei Logik für Aktionen. Sie “wissen” nicht, warum ein Rabatt angezeigt werden soll – sie zeigen nur einen anderen Preis an. Dies kann ein kundenspezifischer Nachlass sein, eine Aktion, eine Werbaktion eines Lieferanten, was auch immer. Gibt es nichts, wird die Komponente unsichtbar und verhält sich transparent.

Im Frontend gibt es nur JavaScript und CSS. Die Verbindungsschicht ist reines JavaScript (beim Entwickeln natürlich TypeScript) und möglichst schlank. Man spricht dann bei den Modules vom Micro-Frontend.

Im Extremfall sieht das so aus (Quelle: https://micro-frontends.org/):

Abbildung: Frontend mit drei Technologien
Abbildung: Frontend mit drei Technologien

Die Kernideen sind:

Es verbleibt die Frage, wie die Integration konkret erfolgen kann. Der beste Weg sind Web Components. Diese werden zwar noch wenig nativ untestützt, aber die Browserhersteller sind zumindest auf dem Weg dahin. Es gibt glücklicherweise für alle Browser Polyfills, am besten eignet sich derzeit Polymer. Und so sieht eine Komponente bei der Benutzung aus (aus der Polymer-Dokumentation:

1 <contact-card starred>
2   <img src="profile.jpg" alt="Joerg's photo">
3   <span>Joerg Krause</span>
4 </contact-card>

Das kann im Kern dann nativ implementiert sein, mit React, mit Angular oder wie auch immer. So bleibt die nötige Flexibilität erhalten.

13.4.4.1 Adaptionen

Die Integration von Frontend-Microapps ist derzeit eine weit größere Herausforderung als die komplexe Microservice-Architektur im Backend. Deshalb können serverseitige Techniken mit in Betracht gezogen werden, die hier helfend eingreifen. Eine davon sind Server Side Includes. Damit werden Fragmente auf dem Server zusammengefügt. Dies ist eine Funktion der Webserver, die prakisch jeder professionelle Webserver beherrscht (die NodeJs-Bastler, die alles selber schreiben, müssen dafür ein Modul laden, beispielsweise ssi).