Von den Vorteilen verteilter Systeme muss heutzutage kaum jemand mehr überzeugt werden, sie sind allgegenwärtig. Umfangreiche Produkte, Plattformen und Webseiten profitieren von der Aufteilung in einzelne Services und Verantwortungsbereiche. So können die Services individuell entwickelt, entworfen und von unabhängigen Teams unterschiedlicher Zusammensetzung und Expertise umgesetzt werden.

Gerade in Cloud-Umgebungen kommen die Vorteile besonders zum Tragen. Für jeden Teilbereich des Systems können passende Technologien und virtuelle Hardware gewählt werden. Die einzelnen Anwendungen sind flexibel und individuell nach Bedarf skalierbar und können einzeln und unabhängig deployt werden. Der Ausfall eines Services führt nicht mehr dazu, dass die ganze Anwendung unbenutzbar wird. Und soll ein Service gar komplett ersetzt werden, ist dabei die Komplexität und Fehleranfälligkeit durch die lose Kopplung zu den anderen Teilen deutlich reduziert.

Abbildung - Verteiltes System mit Frontend-Monolith

Verteiltes System aus Microservices

 

Der Frontend-Monolith

Doch sehr häufig endet das verteilte System beim Frontend. Die Probleme, die in den Backends erfolgreich überwunden wurden, treten dort gebündelt auf. Im Laufe der Zeit wird das Frontend immer größer. Änderungen dauern länger, es wird immer schwerer zu überblicken. Die Fehleranfälligkeit steigt, und die Gefahr, versehentlich die ganze Anwendung zum Erliegen zu bringen, ist im Frontend-Team wieder präsent. In größeren und über mehrere Jahre gewachsenen Anwendungen kommt ein Update des gewählten Frameworks fast schon einem kompletten Rewrite des Frontends nahe.

Abbildung - Verteiltes System mit Frontend-Monolith

Verteiltes System mit Frontend-Monolith

Glücklicherweise gibt es inzwischen Konzepte und technische Möglichkeiten, wie User Interfaces aus verschiedenen, unabhängigen Bausteinen zusammengesetzt werden können. Microfrontends in Kombination mit Self Contained Systems haben sich als besonders mächtig herausgestellt.

Self Contained Systems – Gekapselte Fachlichkeit

Anstatt größere Anwendungen horizontal anhand der Schichten zu trennen und dadurch beispielsweise ein Frontend-, ein Backend- und ein Datenbank-Team zu kreieren, setzt das Konzept der Self Contained Systems (SCS) auf einen vertikalen Schnitt anhand der Fachlichkeit. Dadurch entstehen crossfunktionale Teams, die ihre Anwendungen durch alle Schichten hindurch entwickeln und verantworten.

Bei der Entwicklung von Webshops hat sich die Ausrichtung der Teams entlang der verschiedenen Schritte der Customer Journey als nützlich erwiesen. Im Beispiel unseres Projekts in der Klingel-Gruppe, in der wir für die etwa 50 Shops in neun europäischen Ländern auf Self Contained Systems und Microfrontends setzen, wurde die Customer Journey in die folgenden Bereiche aufgeteilt: „Suchen“, „Entdecken“, „Auswählen“, „Kaufen“ und „Begleiten“.

Abbildung - Fachlich unterteilte Customer Journey am Beispiel eines Webshops

Fachlich unterteilte Customer Journey am Beispiel eines Webshops

Die Bedürfnisse einer Kund*in sind unterschiedlich, je nachdem, an welcher Stelle sie sich im Shop gerade befindet. Wenn sie etwas in die Suche eingibt, möchte sie möglichst nur relevante Ergebnisse erhalten. Kommt sie über eine externe Suche direkt zum Produkt, hat sie sich womöglich schon dafür entschieden und wünscht sich vor allem einen unkomplizierten Kaufprozess. Besucht sie einen Tag später den Mein Konto-Bereich, will sie genau über den Status ihrer Bestellung informiert werden. Diese unterschiedlichen Kundenbedürfnisse führen zu unterschiedliche Anforderungen an die Teilsysteme.

Die Bedeutung der Fachlichkeit für die Architektur

Während für das Entwicklungsteam, auch Vertikale genannt, im Bereich „Suchen“ Schnelligkeit und die Relevanz der Ergebnisse ausschlaggebend ist, befasst sich „Auswählen“ damit, die Produktinformationen in möglichst hoher Qualität und Übersichtlichkeit bereitzustellen. Für die Vertikale „Begleiten“ sind vor allem Datensicherheit und -konsistenz ausschlaggebend, da sie beispielsweise beim Anzeigen offener Beträge keine Fehler machen darf.

Infolgedessen unterscheiden sich auch die Architekturen innerhalb der Vertikalen grundlegend. Eine große, einheitliche Gesamtlösung oder festgelegte Entscheidung etwa über die Datenbank-Technologie wäre hier kontraproduktiv. Jedes Team kann selbst am besten einschätzen, welche Technologie ihr Ziel am besten unterstützt. Stellt sich außerdem heraus, dass die aktuelle Lösung nicht mehr genügend Flexibilität bietet, um auf die sich ändernden Anforderungen der Kund*innen zu reagieren, muss das Team die Freiheit haben, die eigenen Systeme zu ändern und neue Entscheidungen zu treffen – und das möglichst schnell.

Abbildung - Vertikal geschnittene Self Contained Systems mit eigenen Frontends

Vertikal geschnittene Self Contained Systems mit eigenen Frontends

Solche Team-Entscheidungen können sämtliche Schichten und Bereiche des jeweiligen Softwaresystems betreffen. Von der Wahl der Programmiersprachen und Entwurfsmuster in jedem verantworteten Service über eine Aufteilung in Microservices oder doch lieber größere Einheiten bis hin zum Frontend-Framework. Ausschlaggebend sind immer die individuellen Anforderungen an die Architektur.

Um die Unabhängigkeit der Vertikalen zu gewährleisten, sollten Self Contained Systems wann immer möglich einen asynchronen Datenaustausch gewährleisten. Anstatt Schnittstellen synchron anzufragen, sorgen Techniken wie Feeds und Snapshots dafür, dass eine Vertikale auch ohne Laufzeitabhängigkeit auf Daten anderer zugreifen kann. Das produzierende System stellt die Daten bereit, die von den Interessenten nach Belieben konsumiert und weiter verarbeitet werden können. Diese asynchrone Datenübertragung sichert die Entkopplung: Es gibt keinen Aufruf einer API, sondern einen asynchronen Konsum eines fortlaufenden Datenstroms. Die dadurch entstehende Redundanz in der Datenhaltung beim Konsumenten wird dafür gerne in Kauf genommen.

Integration durch Microfrontends

Die beschriebenen Vorteile, die eine Organisation in Self Contained Systems einerseits mit sich bringen, führen auf der anderen Seite zu einer großen Herausforderung: der Integration. Letztendlich sollen die Kund*innen überhaupt nicht bemerken, dass sie sich in unterschiedlichen Bereichen bewegen, die von verschiedenen Teams verantwortet werden. Das Nutzungserlebnis soll einheitlich sein. Die große Heterogenität der verwendeten Technologien und Frameworks darf am Ende nicht mehr spürbar sein.

Microfrontends eigenen sich für diese Ausgangslage sehr gut. Die Vertikalenteams liefern jeweils die Seiten des Webshops aus, die in ihrer Verantwortung liegen. Dabei bringen sie selbst die von ihnen benötigten CSS- und JavaScript-Assets mit. Da eine Seite jedoch in den meisten Fällen aus einer Zusammensetzung von Elementen verschiedener Vertikalen besteht (die Hauptnavigation liefert „Suchen“, das Warenkorb-Icon „Kaufen“ usw.), werden verschiedene Techniken eingesetzt. Die wichtigsten werden nun vorgestellt:

Custom Elements

Durch Custom Elements aus der Web Components Spezifikation ist es möglich, eigene HTML-Elemente zu definieren. Diese können per JavaScript mit einem Verhalten angereichert werden, das über vordefinierte Lifecycle Hooks wie constructor oder attributeChangedCallback(attrName, oldVal, newVal) angesteuert wird. Im HTML-Dokument werden sie dann mit ihrem selbstgewählten Namen als Tag eingebunden.

Im Fall der Microfrontends können die verschiedenen Vertikalenteams ihre gekapselten UI-Komponenten inklusive Funktionalität in Form von Custom Elements bereitstellen. Das Team, das die aktuelle Seite ausspielt und damit auch verantwortet, welche Elemente darin wo angezeigt werden, bindet das Custom Element in sein HTML ein. Beim Laden der Seite wird das Custom Element dann vom Browser angezeigt. Parameterwerte können in Form von Attributen an das Custom Element gegeben werden. Änderungen des Wertes können dann wiederum über den attributeChangedCallback behandelt werden.

Server Side Includes

Es gibt viele Anwendungsfälle, in denen eine reine clientseitige Integration den Anforderungen nicht genügt. Serverseitig gerenderte Seiten haben immer einen Performancevorteil, auch die Suchmaschinenoptimierung (SEO) spielt sehr häufig eine ausgesprochen wichtige Rolle. Hier kommen die Server Side Includes (SSI) ins Spiel.

Für das Server Side Include stellt ein Team seine Komponenten als gerenderte Custom Elements über einen Endpunkt bereit. Auf der umgebenden Seite wird an der gewünschten Stelle ein Include-Befehl mit der entsprechenden URL eingefügt. Ein Reverse Proxy, beispielsweise ein nginx Server, verarbeitet diesen Befehl, sendet ein Request an den Endpunkt und fügt die so vom Server des Teams gerenderte Antwort an dieser Stelle ein. Parameter, die im Custom Element in Form von Attributen übergeben werden, sind in diesem Fall Query-Parameter.

Besonders die Verbindung von Custom Element und Server Side Include ergibt eine mächtige Kombination. So erhält man beim ersten Laden einer Seite den Geschwindigkeitsvorteil von serverseitigem Rendering. Metriken wie „First Contentful Paint“ und „Largest Contentful Paint“, die vom Google-Ranking in der Performance-Bewertung von Webseiten inzwischen hoch gewertet sind, können damit recht schnell bedient werden. Im Hintergrund werden dann die Custom Elements registriert. Wird auf der Seite dann ein interaktives Element benutzt, greift die Implementierung des Custom Elements, das Anfragen an das Backend als clientseitigen Request abschickt und das Ergebnis sofort im DOM aktualisiert, ohne dass ein kompletter Page Reload nötig ist.

Custom Events

Custom Events kommen vor allem in zwei Anwendungsfällen zum Einsatz: Wenn eine Information an ein übergeordnetes Element übergeben, also beispielsweise ein Parent-Element benachrichtigt werden muss und wenn eine anderen Vertikale über ein Ereignis informiert werden soll. Im Sinne der Unabhängigkeit der Vertikalen soll darauf verzichtet werden, große Datenmengen direkt auszutauschen. Auch ein Shared State im Browser, auf den mehrere Teams zugreifen, ist nicht erwünscht.

Stattdessen kann eine Vertikale ein Custom Event erzeugen. Dieses selbst definierte DOM-Event wird an das Window Object des Browsers gesendet. Vertikalen, die an der Information interessiert sind, können dafür Event Listener registrieren und beim Auftreten des Events ihren Teil der Anwendung aktualisieren, Daten nachladen und Elemente neu rendern. Für die genaue Umsetzung sind sie wieder selbst verantwortlich und nicht abhängig von einer großen, synchronen Schnittstelle zwischen den Beteiligten.

Anwendungsbereich

Die Vorzüge von Microfrontends kommen dann besonders zum Tragen, wenn in größeren Projekten mehrere Teams eine gemeinsame Anwendung entwickeln. Sie führen das Konzept der verteilten Systeme konsequent auch in der Frontend-Schicht weiter. Ihre Erstellung bringt einigen Aufwand im anfänglichen Setup mit sich, der sich erst nach einer gewissen Zeit amortisiert. Dann können die Vorzüge aber einen enormen Gewinn bringen.

Der Gedanke dabei ist, dass sich vor allem der organisatorische Vorteil schon bald lohnt: Features bilden üblicherweise eine fachliche Anforderung ab. Verläuft die Unterteilung von Teams allerdings horizontal entlang der Schichten, ist es zwingend notwendig, dass mehrere Teams sich absprechen müssen. Neben der zeitlichen Abhängigkeit, die oft mit Wartezeiten verbunden ist, steigt die Wahrscheinlichkeit für Missverständnisse und daraus resultierenden Unmut. Zudem muss in jedem Team die gesamte Fachlichkeit ausreichend verstanden werden, was bei einem über Jahre wachsenden System für die beteiligten Personen irgendwann nicht mehr zu überschauen ist und damit fehleranfällig wird.

Sobald das technische Setup steht und die Microfrontends von den Vertikalenteams erstellt werden, sind die Vorzüge schnell zu spüren. Features können von einem cross-funktionalen Team von der Datenbank bis zum Frontend umgesetzt werden. Neben der technischen Weiterentwicklung ist auch das Erlangen von fachlicher Expertise ein wichtiges Element. Der ganze Entwicklungszyklus liegt in der Verantwortung des Teams, und so kann es von der Idee über die Planung, die Umsetzung, das Testen und das Ausrollen des Features seine Entscheidungen selbst treffen sowie individuelle Qualitätsziele für ihren fachlichen Kontext definieren und auswerten.

Die vertikale Aufteilung führt zu Herausforderungen an anderer Stelle. Im Folgenden werden einige von ihnen aufgegriffen.

Best Practices

Beim Verwenden von Self Contained Systems und Microfrontends gilt es wie bei allen Architekturen, einen Weg zu finden, wie das theoretische Konzept in der Praxis umgesetzt werden kann. Dabei sind immer die Rahmenbedingungen und Anforderungen an die Anwendung ausschlaggebend. Die folgenden Best Practices haben sich in unserem Projektkontext bewährt und können anderen Projekten als Anregung dienen.

Naming Conventions

Wenn mehrere unabhängige Subsysteme existieren, ist es sinnvoll, sich gleich zum Projektstart auf einige Naming Conventions zu einigen. Es ist besonders hilfreich, wenn beim Aufrufen von URLs oder dem Einbinden von Custom Elements der Name des Teams direkt erkennbar ist. Ebenso sinnvoll ist ein fest definiertes Schema für die Bezeichnung beispielsweise von Custom Elements wie sie in den Beispielen zu sehen sind. Gut eignet sich hierfür die fachliche Bezeichnung der Vertikale. Eine Begrenzung auf die ersten Buchstaben hilft, URLs kurz zu halten. So ist bei Analysen oder Nachfragen die Zuständigkeit sofort ersichtlich, auch das Proxying von Requests und das Präfigieren von CSS-Klassen kann anhand der Vertikalenbezeichnung einfach und präzise erfolgen.

Performance

Bedenken bezüglich der Performance einer so zusammengesetzten Frontend-Architektur liegen nahe. Jedes Vertikalenteam liefert seine eigenen Assets aus, der Code der verschiedenen Frameworks muss geladen und gerendert werden, die Seiten sind aus vielen Teilen zusammengesetzt. Um dennoch gute Werte zu erzielen, gibt es neben der Möglichkeit, die vom Framework benötigten Assets über ein hoch performantes CDN auszuliefern, außerdem die Option, anstatt der eher großen Frameworks wie React ähnliche und leichtgewichtigere Alternativen wie beispielsweise Preact oder Hyperapp zu nutzen, die teilweise mit nur einem Kilobyte an Größe auskommen.

Bei einer in diesem Jahr durchgeführten Performance-Analyse der 50 umsatzstärksten Online-Mode-Shops Deutschlands belegten die so im Projekt mit Microfrontends umgesetzten Shops vier der ersten fünf Plätze und ließen damit zahlreiche Branchengrößen hinter sich.

User Experience und Design

Um trotz unterschiedlicher Teams und Frameworks ein einheitliches Design und Nutzungserlebnis zu gewährleisten, ist eine klar definierte Richtlinie essenziell. Hier kann ein geteiltes Design System, eine Pattern Library, ein wichtiger Bestandteil sein. Dort werden UX- und Design-Prinzipien verschriftlicht und mit Beispielen angereichert, die von allen Teams umgesetzt werden. Es ist zudem ratsam, direkt alle grundlegenden CSS-Styles wie Farben, Schriften, Abstände und viele weitere Eigenschaften über die Pattern Library auszuliefern.

Je nach Ausbaustufe kann es auch sehr sinnvoll sein, ganze Komponenten bereitzustellen, die wie Bausteine von allen Teams in der Komposition ihrer eigenen Elemente genutzt werden. Über eine Versionierung des entstehenden Pakets wird weiterhin gewährleistet, dass die Vertikalen unabhängig voneinander auf die neuste Version updaten und gegebenenfalls auf Breaking Changes selbständig zu einem für sie passenden Zeitpunkt reagieren können.

Abbildung - Pattern Library auf Basis von UIengine, die Design- und UX-Elemente dokumentiert und bereitstellt

Pattern Library auf Basis von UIengine, die Design- und UX-Elemente dokumentiert und bereitstellt.

Deployment und Infrastruktur

Als Bestandteil verteilter Systeme sind Microfrontends geradezu prädestiniert für einen Betrieb in der Cloud. Die Bereitstellung der Services, die Seiten sowie Custom Elements und Fragmenten für Server Side Includes ausliefern, geschieht üblicherweise in Form von Docker-Containern, die zum Beispiel einen Node.js-Server beinhalten. Gerade in Großprojekten kann zudem die Verwendung einer Containerorchestrierungsplattform wie Kubernetes sinnvoll sein.

Ein Prozess im Sinne von Continuous Deployment kann in etwa so aussehen: Bei einem Push der Codeänderungen in die Versionsverwaltung von Git wird ein automatisierter Build-Prozess ausgelöst. Die Anwendung wird gebaut und Unit- und Integrationstests ausgeführt. Die Server werden in Docker-Containern verfügbar gemacht und in die Cloudumgebung deployt. Üblicherweise arbeitet man mit mehreren Stages, sodass auf einem Testsystem, das bereits das integrierte System aller beteiligten Vertikalen abbildet, geprüft werden kann, ob alles wie erwartet funktioniert. Idealerweise in Form von automatisierten End-to-End-Tests, die tatsächlich die UI bedienen, bevor die Änderung anschließend die Livesysteme und damit die Endnutzer*innen erreicht. Tritt innerhalb der Pipeline ein Fehler auf, wird der Prozess abgebrochen und das Entwicklungsteam erhält eine Benachrichtigung.

Abbildung - Beispiel einer Continuous Deployment Pipeline auf gitlab

Beispiel einer Continuous Deployment Pipeline auf gitlab

Microfrontends – Fazit

Microfrontends eignen sich besonders für mittlere und größere Projekte, die kontinuierlich über einen längeren Zeitraum entwickelt werden. Trotz des Setup-Aufwands ist der Effizienzgewinn durch die unabhängige Entwicklung von Features innerhalb eines crossfunktionalen Teams dann besonders attraktiv. Sie sind eine Softwarearchitektur, die sich besonders im Cloudbetrieb von skalierbaren, verteilten Systemen anbietet. Inzwischen setzen immer mehr und gerade auch größere Unternehmen auf Microfrontends, sodass das Thema in den nächsten Jahren sicherlich noch an Prominenz gewinnen wird.

Jennifer Pelz

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Die von Ihnen hier erhobenen Daten werden von der Host Europe GmbH zur Veröffentlichung Ihres Beitrags in diesem Blog verarbeitet. Weitere Informationen entnehmen Sie bitte folgendem Link: www.hosteurope.de/AGB/Datenschutzerklaerung/