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:
- Statische übergreifende Dinge wie Layouts kann man im Build oder Deployment integrieren.
- 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.
- 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.
Ü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.
Leider ergeben sich bei genauerem Hinsehen dann aber schon einige technische Probleme:
- Ö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.
- 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:
- Man hat eine strikte Trennung zwischen Backends und dem einen Frontend.
- Monolithische Single-Page-Apps sind „State of the Art“.
Contra:
- Man erbt alle Nachteile einen Monolithen sowie alle Nachteile davon, dass man trotzdem mehrere Teams hat.
- SPA-Technologien sind kurzlebig. Man macht sich von einem Framework-Hersteller extrem abhängig.
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.
Pro:
- Man kann sich noch immer ein SPA-Framework herauspicken und verlässt dessen Welt so gut wie gar nicht.
- Die organisatorischen Implikationen des monolithischen Ansatzes lassen sich teilweise lösen.
Contra:
- Letzen Endes handelt es sich bei einem solchen System um einen strukturierten Monolith, mit vielen Nachteilen, die ein Monolith auch im Fall eines Backends hat. So war in den dem Autor bekannten Umsetzungen dieser Variante ein Upgrade des gemeinsamen Frameworks auch nur für einen kleinen Versionssprung so gut wie unmöglich.
„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.
Pro:
- Dieser Ansatz ist besonders für Dashboards oder kleinere Menü-Widgets geeignet.
Contra:
- Der Appell „die SPA sollte schnell in die Systeme verzweigen“ wird in der Praxis vermutlich schnell verhallen und die SPA naturgemäß eher wachsen.
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.
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:
- Komponenten sind optimalerweise völlig unabhängig voneinander. Damit sind sie insbesondere austauschbar oder selbstständig weiterzuentwickeln.
Contra:
- Die Komponenten erweitern einen fremden DOM, um sich selbst darzustellen. Der Umgang mit Abhängigkeiten, also CSS, ist nicht trivial. Die neue API ShadowDOM kann zwar helfen, indem sie das CSS isoliert, induziert aber dadurch auch neue Fragestellungen.
- Gängige Frameworks eignen sich aufgrund des großen Footprints leider oft noch nicht dazu solche Komponenten zu entwickeln.
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.
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.
Beantworten muss man in der jeweiligen Architektur zusätzlich noch die folgenden Fragen:
- Soll es nur einen Rücksprungparameter geben oder mehrere (zum Beispiel für den Erfolgsfall und den Abbruch)?
- Soll eine Redirect-URI auch wiederum eine Redirect-URI beinhalten können (Kaskadierung)?
- Will man ggf. URI-Templates verwenden, um in der Rücksprungs-URI dem Ursprungs-System Daten mitgeben zu können?
Pro:
- Links sind das vermutlich meistverwendete Integrationsinstrument der Welt. So sind Links und Rücksprung-Parameter in fast jeder Umgebung einfach umzusetzen. Mehr als ein halbwegs RESTful agierendes serverseitiges HTML-Frontend braucht man dafür nicht.
Contra:
- Die User-Experience der Integration via Links genügt meist nicht den Anforderungen unseres Jahrhunderts.
- Systemwechsel per Links bzw. per Rücksprung ermöglichen die „Übergabe“ eines Frontend-Prozesses von einem System ins andere. Was es leider meist nicht ermöglicht, ist die Übergabe von Daten, die aber bei den meisten Übergängen auch notwendig sind.
- Kaskadierung von Rücksprung-URIs birgt eine leichte Spaghetti-Gefahr für Arbeitsabläufe.
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.
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:
- Eine Übertragung auch von Systeminternen Daten bei Systemwechseln rein durch das Frontend wird damit möglich, ohne direkte Aufrufe zwischen den Backends zu benötigen.
Contra:
- Signaturberechnung von HTML-Formular-Werten müssen spezifiziert und in allen Systemen gleich umgesetzt werden. Empfänger von solchen Formular-Daten müssen zwingend die Signaturen prüfen.
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.
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:
- Technisch genügt für clientseitige Transklusion eine winzige clientseitige Komponente.
- Die Backends müssen im Allgemeinen nicht viel anders programmiert werden als in der einfachen „Link“-Variante und benötigen keine zusätzlichen JSON-APIs oder Ähnliches.
Contra:
- HTML von anderen Systemen landet strukturell ungefiltert im DOM. Die Entwickler dieses „fremden“ HTML müssen daher Annahmen beispielsweise über das im „Rahmen“ vorliegende CSS und JavaScript machen. Dadurch wird die Versionierung der einzubettenden Inhalte schwierig, da bei Updates der transkludierten Inhalte sichergestellt werden muss, dass der Rahmen noch passt.
Weitere Links:
- Ein Blog-Post vom Autor, der diesen Ansatz detailliert beleuchtet.
- Ein Vortrag meiner Kollegin Franziska Dessart zum Thema.
- Gustaf Nilsson Kotte hat einen englischen Artikel mit ähnlichem Fokus verfasst.
- ROCA h-embed ist eine einfache CustomElements Komponente für Link-Replacement, also das ersetzten von Links durch ihren Inhalt.
- pjax ist die klassische Komponente für Transklusion.
- Turbolinks ist ein aus Ruby on Rails extrahierter etwas anderer Ansatz.
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.
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:
- IFrames kapseln sogar die JavaScript-Runtime des Zielsystems. Ob man dann serverseitiges HTML, SPAs, JSF-Anwendungen oder was auch immer vom Fremdsystem integriert, spielt überhaupt keine Rolle.
- Sogar mehrschrittige serverseitige Abläufe lassen sich damit ohne Weiteres integrieren.
Contra:
- IFrames sind allein schon durch die Isolation der Ressourcen schwergewichtig. Sehr viele Integrationen auf einer Seite beispielsweise von kleineren UI-Komponenten per IFrame sind also keine Option.
- Responsive IFrames sind kein triviales Problem. So beziehen sich CSS Media-Queries auf die Breite des IFrames, was in erster Betrachtung auch logisch erscheint. Will man die gesamte Seite aber wie üblich mit festen Breakpoints designen, so bedeutet dies, dass innerhalb des IFrames (wenn dieser nicht 100% breit ist) andere Breakpoints gelten als im Rest des Browser-Fensters.
- IFrames haben keine automatische innere Höhe. Nicht scrollende IFrames mit nicht fixem Seitenverhältnis sind also leider nicht trivial.
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.