This article is also available in English

Systemgrenzen sind aus Nutzersicht tendenziell ein Ärgernis. So ist die Modularisierung von Systemen ein rein technisch motiviertes Thema, das reibungslosen Arbeitsabläufen ohne größere Kontextverluste nicht im Wege stehen sollte.

Haben wir also “aus Gründen” unsere Systeme zerschnitten, so müssen wir diese für die Nutzer zumindest im Frontend auch wieder zusammen führen. Dies kann an verschiedenen Stellen passieren:

  1. Statische übergreifende Dinge wie Layouts kann man im Build oder Deployment integrieren.
  2. Bei dynamischen Inhalten kann man im jeweiligen Backend dafür sorgen, dass sich dieses die notwendigen Informationen aus anderen Systemen besorgt. Dabei kann es sich zum einen um direkte Aufrufe zu einem anderen System zur Bearbeitung des unmittelbaren Requests handeln. Man benötigt in diesem Fall also eine Antwort eines anderen Systems, um den eigenen Request zu beantworten. Es kann sich zum anderen aber auch um von Requests losgelöste Interaktionen handeln, wie beispielsweise eine Feed-basierte Replikation von Daten.
  3. Der Königsweg ist aber aus Sicht des Autors die Integration im Frontend, genauer im Browser der Benutzer. Diese Art der Integration ist meist leichtgewichtiger, performanter und hat den grundsätzlichen Vorteil, dass nicht die eigenen Systeme (also die Backends) auf das jeweils andere System warten müssen und damit ihre eigene Antwort verzögern, sondern die Integration im Endgerät des Nutzers erfolgt.
Abbildung 1: Problem der Integration von mehreren Backends
Abbildung 1: Problem der Integration von mehreren Backends

Über die ersten beiden Optionen wurde bereits viel gesagt. Daher soll es hier um die dritte Option, die Frontend-Integration, gehen. Dafür gibt es, wie so oft in der IT, nicht nur eine einzige Möglichkeit der Umsetzung, sondern viele. Im Folgenden soll versucht werden, einige der Optionen aufzulisten (ohne Anspruch auf Vollständigkeit).

Beginnen soll die Auflistung mit dem Quasi-Standard von heute. Dabei handelt es sich um die monolithische Single-Page-App (SPA) im jeweiligen Trend-Framework des Entstehungsjahrs der App.

Monolithische SPA

Der laufende Microservices-Hype war in meinen Augen primär ein Backend-Hype. Das bedeutet, dass viele Microservice-Architekturen in einer reinen Backend-Perspektive entstanden sind. Findet man dann Zeit sich um das “kleine Problem” des Frontends zu kümmern und nimmt man dann noch den gleichzeitigen Single-Page-Hype hinzu, so erscheint es logisch, die Backends auf Ebene Ihrer APIs zu integrieren.

Man baut also eine Single Page App (SPA), typischerweise mit einem speziellen Team von UI-/SPA-Experten. Diese App macht dann in der Regel Ajax-Aufrufe gegen die REST-APIs der Backend-Teams.

Überzeugend ist bei diesem Konzept, dass das Web-Frontend ein völlig eigenständiges System ist. Es reiht sich damit nahtlos neben anderen Consumer-Systemen wie dem Mobil-Client für iOS oder den Aufrufen eines Partner-Unternehmens ein, wie man sich das auf dem Papier bzw. dem Flipchart wünscht.

Abbildung 2: Integration in einer monolithischen SPA
Abbildung 2: Integration in einer monolithischen SPA

Leider ergeben sich bei genauerem Hinsehen dann aber schon einige technische Probleme:

  1. Öffentliche APIs müssen allesamt Querschnittsthemen wie Authentifizierung oder ganz grundsätzlich Security adressieren. Dies lässt sich aber in der Regel mit Infrastruktur irgendwie lösen.
  2. Die Orchestrierung von Service-Aufrufen gegen unterschiedliche Backends über das Internet ist, wenn man es naiv angeht, alles andere als performant. Insbesondere, wenn es sich um mobiles Internet handelt. Hierfür gibt es dann keine einfachen technischen Lösungen mehr, sondern nur noch Architektonische: Das Backend for Frontend-Pattern hilft, aus indem man dem Frontend-Team erlaubt sich ein (typischerweise auf NodeJS) basierendes Backend-System zu bauen. Dieses spezielle Backend-System übernimmt die Orchestration der eigentlichen Backends und kann dadurch deutlich kompakter und performanter mit dem Frontend kommunizieren.

Das Hauptproblem bei diesem Ansatz ist aber kein technisches, sondern ein organisatorisches: Dadurch, dass man ein spezielles Querschnitts-Frontend-Team erfunden hat, verstößt man eigentlich gegen die Grundidee von Microservices. Diese besagt nämlich Teams und Systeme rund um Geschäftsbereiche zu schneiden, was bei einem Querschnitts-Team einfach niemals der Fall sein kann (sonst wäre es kein Querschnitts-Team).

In der Konsequenz ist es etwa schwer vorstellbar, dass ein neues Feature allein von einem Team umsetzbar sein könnte. Denn in der Regel benötigt jede neue Funktionalität im Backend eine Entsprechung um Frontend, um sie überhaupt benutzen zu können und umgekehrt.

Pro:

Contra:

Modulare SPA

Das organisatorische Problem einer monolithischen SPA ist allerdings mit technischen Mitteln und einigem Aufwand teilweise adressierbar. Dazu zerteilt man die SPA in mehrere Module, die von den jeweiligen Fach-Teams bereitgestellt werden. Das bedeutet es gibt noch immer eine einzige Single-Page-App in genau einer Framework-Version. Diese App lädt ihre Funktionalität aber in Form von Modulen optimalerweise direkt aus den unterschiedlichen Systemen und integriert diese über Framework spezifische Schnittstellen.

Eine Möglichkeit der Umsetzung eines solchen Vorgehens sind Angular lazy feature modules. Hierbei verteilt man die Komponenten einer Anwendung in diverse sogenannte Feature Module und lädt diese erst dann, wenn sie benötigt werden. Es ist auch möglich, diese Module von unterschiedlichen Quellen zu laden. Man kann den Mechanismus also dazu benutzen erst zur Laufzeit des Clients die Feature-Module von unterschiedlichen Backends zu laden. Bei einem ordentlichen Modul-Schnitt bekommen dann die Teams dadurch wieder die Hoheit über ihre Funktionalität zurück und sind in der Lage selbstständig neue Features zu entwickeln und auszurollen, ganz im Sinne von Microservices.

Bei Webpack-basierten Projekten geht dies auch via Lazy Loading. Im Prinzip hält jeder moderne Modul-Bundler und jedes moderne SPA-Framework irgend einen Mechanismus dafür bereit. Beim Framework Polymer beispielsweise ist ein solches Vorgehen geradezu die Grundidee.

Abbildung 3: Module der SPA werden von unterschiedlichen Backends geladen
Abbildung 3: Module der SPA werden von unterschiedlichen Backends geladen

Pro:

Contra:

“Flache” SPA

Damit eine monolithische Single-Page-App einfach nicht zu riesig wird, könnte man auch versuchen den Funktionsumfang dieser SPA möglichst gering zu halten. Eine SPA beispielsweise für ein Dashboard könnte durch ein entsprechendes Dashboard-Team gebaut werden und sich direkt an den JSON-Backend-APIs der anderen Systeme bedienen. Diese SPA sollte dann aber möglichst “flach” sein, also für weitere Aktionen möglichst schnell in die eigenen Frontends der jeweiligen zuständigen Systeme verzweigen.

Abbildung 4: Eine flache SPA verzweigt schnell in die Teilsysteme
Abbildung 4: Eine flache SPA verzweigt schnell in die Teilsysteme

Pro:

Contra:

Komplexe fachliche DOM-Komponenten

Single-Page-Ansätze beanspruchen üblicherweise die Hoheit über die gesamte Seite. Was wäre aber, wenn man zwar JavaScript für die Entwicklung von komplexen Frontend-Komponenten verwendet, diese aber komplett eigenständig wären, also ohne den Anspruch auf einen “Rahmen” oder Routing drumherum? Was wäre also, wenn wir, wie im Web eigentlich üblich, eben gerade keine Applikation / App bauen, trotzdem aber ein hohes Maß an fachlicher Logik Clientseitig ausführen wollen?

Anbieten würde sich für hierfür der Web Components Standard CustomElements. Hiermit lassen sich Komponenten definieren, die sich automatisch auf dem DOM initialisieren, ohne dass ein Framework dafür notwendig wäre. Eine solche Komponente könnte dann beispielsweise per JSON-APIs vom Backend Daten nachladen, DOM manipulieren, Events an andere verschicken, was auch immer …

Instanziiert werden diese Komponenten im einfachsten Fall durch serverseitiges HTML. Die einzelnen Backend-Systeme könnten also einfach selbst HTML ausliefern und ihre eigenen Komponenten sowie die Komponenten anderer Systeme dadurch integrieren, dass sie diese in ihrem HTML referenzieren.

Abbildung 5: Komplexe Komponenten werden über den DOM integriert
Abbildung 5: Komplexe Komponenten werden über den DOM integriert

Komponenten dieser Art können auch mithilfe von SPA-Technologien wie Angular, vue oder React gebaut werden. Allerdings lohnt sich dies nur, wenn sie trotzdem schnell laden und keinen allzu großen “Footprint” haben. Außerdem sind moderne SPA-Frameworks wie Angular leider nicht in diesem Sinne gebaut und beanspruchen oft die Hoheit über die gesamte Seite oder zumindest über die URL.

Andere Frameworks wie Polymer oder Stencil sind geradezu um diese Idee herum gebaut. Welches dieser Frameworks allerdings infrage kommt, wird zumindest für öffentliche Seiten vermutlich stark durch die Größe der jeweiligen Komponenten beeinflusst werden. Diese kann beispielsweise bei Polymer ungezippt durchaus im dreistelligen Kilobyte-Bereich liegen.

Pro:

Contra:

Links

Mit den “komplexen fachlichen Komponenten” haben wir im Vergleich zu den SPA-Ansätzen einen wichtigen Schritt getan: Wir integrieren, zumindest potenziell, über serverseitig gerendertes HTML der Backend-Systeme. Stellen alle Backends ihre Funktionalität größtenteils über Serverseitiges HTML bereit, so ergibt sich noch eine viel einfachere Methode der Integration: der Hyperlink.

Möchte man also in dem jeweiligen System die Funktionalität eines anderen Systems einbinden, so verlinkt man dieses einfach. Der Benutzer wechselt also in das andere System und kommt nach Erledigung der Arbeiten dort einfach wieder zurück. Sieht das Look & Feel des Zielsystems gleich aus und sorgt der Link dafür, merkt der Benutzer nicht einmal, dass gerade das zuständige System gewechselt wurde. Wichtig ist aber natürlich, dass er Link entsprechend “tief” ins Zielsystem zeigt, sodass der Benutzer direkt an die Stelle des Systems gelangt, an der das Weiterarbeiten auch möglich ist.

Abbildung 6: Links übergeben den Benutzerflow in ein anderes System
Abbildung 6: Links übergeben den Benutzerflow in ein anderes System

Die spannende Frage hierbei ist aber wohin genau der Benutzer wieder zurückgeleitet werden soll. Eine naive Antwort könnten fest konfigurierte “Einsprungpunkte” pro Anwendung sein. Allerdings würde dies in der Regel die Arbeitsabläufe der Nutzer wohl nicht unterstützen, da die Nutzer im Rücksprung-System zuerst ihren Kontext wieder herstellen müssen.

Besser ist es für diesen Zweck einen speziellen Parameter in den URIs und Formularen durchzureichen, den alle beteiligten Systeme kennen und korrekt behandeln müssen. Dieser Parameter enthält meist die Rücksprung-URI, an die man nach erfolgreicher Abarbeitung des Teil-Prozesses im Ursprungs-System zurück “redirecten” soll.

Abbildung 7: Rücksprung-URIs ermöglichen nach Ablauf der Arbeit einen nahtlosen Rücksprung ins Ursprungs-System ohne erneuten Kontext-Aufbau
Abbildung 7: Rücksprung-URIs ermöglichen nach Ablauf der Arbeit einen nahtlosen Rücksprung ins Ursprungs-System ohne erneuten Kontext-Aufbau

Beantworten muss man in der jeweiligen Architektur zusätzlich noch die folgenden Fragen:

Pro:

Contra:

Hypermedia mit Datenübertragung

Will man das bei Systemwechseln mit Links oder Redirects nicht vollständig lösbare Problem der Übergabe von Daten zwischen Systemen via Frontend lösen, so kann man dafür HTML-Formulare verwenden. Ein Backend kann also Daten, die es an ein anderes Backend übergeben will, in ein Formular verpacken, das auf das andere Backend “zeigt”. Schickt der Nutzer dieses Formular ab so wird der Systemwechsel vollzogen und das Ziel-System gelangt an die Daten.

Daten, die der Benutzer nicht manipulieren können soll, wie beispielsweise IDs oder interne URIs, könnten in solchen Formularen in hidden fields eingebettet werden. Allerdings muss dann sichergestellt werden, dass diese Daten nicht durch den Benutzer geändert werden, da sie im Browser vor dem Abschicken beispielsweise mit den Entwicklerwerkzeugen beliebig änderbar sind. Es muss also zusätzlich noch serverseitig eine Signatur mit in das Formular verpackt und auf dem Ziel-System validiert werden.

Abbildung 8: Das Ursprungssystem bettet seine Daten in `hidden fields` ein und signiert sie für das Zielsystem
Abbildung 8: Das Ursprungssystem bettet seine Daten in hidden fields ein und signiert sie für das Zielsystem

Besteht ein solches Formular ausschließlich aus hidden fields, soll also keine zusätzliche Eingabe von Daten in dem Formular durch den Benutzer stattfinden, so kann man für dieses Vorgehen auch wieder Links verwenden und die Informationen in entsprechenden URI-Parametern verpacken. Auch hier bleibt allerdings die Notwendigkeit der Überprüfung der Integrität der Daten beispielsweise durch Signaturen. Ein solches Vorgehen findet man oft bei Verfahren zum Austausch von Tokens in Single-Sign-On Lösungen.

Wenn der Benutzer die zu übertragenden Inhalte nicht einmal kennen darf, so muss zusätzlich noch die Vertraulichkeit der Daten sichergestellt werden. Dafür müssen entsprechend in den Formularen bzw. Links verschlüsselte Daten hinterlegt werden.

Pro:

Contra:

Clientseitige Transklusion

Reichen einfache Links nicht aus, so könnte man versuchen, die Funktionalität anderer Systeme in Form von HTML in das eigene HTML zu integrieren. Dies wird oft mit clientseitiger Transklusion bezeichnet.

Man lädt hierfür Inhalte von anderen Systemen mittels eines kleinen generischen JavaScript-Schnipsels im Browser in den “eigenen”, also in den meisten Fällen vom Server gerenderten, DOM. Konzeptionell ersetzt man also einen Link auf ein Fremdsystem durch den Inhalt der Antwort, wenn man auf diesen Link ein HTTP-GET ausführt.

Abbildung 9: Transklusion sieht den komplexen Komponenten ähnlich, integriert aber HTML anstelle von JavaScript-Komponenten
Abbildung 9: Transklusion sieht den komplexen Komponenten ähnlich, integriert aber HTML anstelle von JavaScript-Komponenten

So einfach das Konzept technisch umzusetzen ist, so viele Fragen bleiben offen. So genügt zur Darstellung eines HTML-Inhalts nicht bloß das HTML selbst, sondern in der Regel werden auch CSS- und JavaScript-Ressourcen benötigt. Kann also das eingebettete System erwarten, dass im einbettenden System sämtliches CSS- oder JavaScript einfach bereits vorhanden ist? Oder muss es Mechanismen geben, wie das eingebettete System CSS und JavaScript in den Browser bekommt?

Diese Fragen sind aber nach einigen Abwägungen bezüglich der vorliegenden Anforderungen in der Regel relativ schnell zu beantworten (siehe Referenzen).

Pro:

Contra:

Weitere Links:

IFrames

Die Haupt-Komplexität der Transklusion ergibt sich dadurch, dass man HTML-Inhalte unterschiedlicher Quellen ohne jegliche Isolation in einen gemeinsamen DOM wirft. Will man dies vermeiden, so gibt es eine im HTML-Standard verankerte Lösung: IFrames. IFrames betten ein komplett eigenständiges Browser-View innerhalb eines anderen Browser-Fensters ein. CSS und insbesondere auch JavaScript sind voneinander isoliert und haben eigene Ressourcen.

Abbildung 10: IFrames stellen die höchste Form der Isolation von auf einer Seite integrierten Inhalten dar
Abbildung 10: IFrames stellen die höchste Form der Isolation von auf einer Seite integrierten Inhalten dar

Die Verwendung von IFrames ist im World Wide Web bei Einbettung von “Fremd-Inhalten” eigentlich Standard. So werden beispielsweise eingebettete Google Maps zwar mit einem JavaScript-Aufruf innerhalb des einbettenden Browser-Fensters initialisiert, dieser Aufruf öffnet aber unmittelbar ein IFrame, in dem alles Weitere dann abläuft.

IFrames können mittels der standardisierten postMessage Nachrichten mit anderen IFrames oder dem einbettenden Fenster austauschen.

Pro:

Contra:

Fazit

Viele Wege führen nach Rom, keiner ist trivial und keiner automatisch der richtige. Wie immer in der IT ist der, den man am Ende wählt, der falsche. Mehr als einen Weg zu wählen, erscheint also nicht die schlechteste Idee.

Der Autor verspricht sich am meisten von den komplexen fachlichen Komponenten. Die Integration über den Standardmechanismus CustomElements erscheint hierbei relativ optimal, da dies offen lässt, ob die Komponente durch serverseitiges HTML oder gar durch eine andere Komponente initialisiert wird. Außerdem ermöglicht es zeitgemäße wartbare UIs zu bauen, ohne sich dabei in die Sackgasse eines einzigen SPA-Frameworks zu begeben.