Microfrontends – Konzept und Implementierung

Gastbeitrag von | 13.01.2022

Dass es sinnvoll ist, große Softwaresysteme in mehrere Services aufzuteilen, ist heute hinlänglich bekannt. Gegenüber monolithischen Anwendungen ergeben sich zahlreiche Vorteile. Sie können individuell entworfen, entwickelt und von unabhängigen Teams unterschiedlicher Zusammensetzung und Expertise umgesetzt werden. Services, die stärker beansprucht werden, können einzeln skaliert werden, Wartung und Testing werden erleichtert und Deployments können unabhängig stattfinden. Die Resilienz des Gesamtsystems erhöht sich, denn sollte ein Service einmal ausfallen, so sind die anderen weiterhin nutzbar. Und sollte durch neue Anforderungen oder Änderungen bei der Technologie ein Service neu geschrieben werden, so kann dieser unabhängig ersetzt werden ohne die anderen Teile anfassen zu müssen.

Verteiltes System mit unabhängigen Services

Verteiltes System mit unabhängigen Services

Der Frontend-Monolith

Leider ist es oftmals so, dass die genannten Vorteile beim Frontend enden. Meist existiert eine gesamtheitliche Oberfläche, die mit allen Services des ansonsten verteilten Systems kommuniziert und sämtliche UI-Logik in sich trägt. Die Probleme, die in den Backends erfolgreich überwunden wurden, treten dort gebündelt auf.

Im Laufe der Zeit wird das Frontend stetig größer und schwerer zu überblicken. Änderungen dauern immer länger. Die Fehleranfälligkeit steigt, und die Gefahr, versehentlich die ganze Anwendung zum Erliegen zu bringen, ist im Frontend-Team wieder präsent. Und sollte es nötig sein, das gewählte Framework zu updaten oder gar auszutauschen, kommt dies fast schon einem kompletten Rewrite des Frontends nahe.

Verteiltes System mit monolithischem Frontend

Verteiltes System mit monolithischem Frontend

Das Konzept Microfrontends

Das Konzept der Microfrontends möchte dem entgegenwirken. Als logische Fortsetzung verteilter Systeme im Frontend bietet es Lösungen an, wie eine UI aus vielen Einzelteilen zusammengesetzt werden kann. Die Grundidee ist, dass die Services alle UI-Elemente, die nötig sind, um ihre Fachlichkeit zu bedienen, selbst mit ausliefern.

Services liefern eigene UI-Elemente aus, diese werden in einer Oberfläche integriert

Services liefern eigene UI-Elemente aus, diese werden in einer Oberfläche integriert

Damit am Ende trotzdem eine einheitliche User Experience entsteht, gibt es verschiedene Möglichkeiten der Implementierung dieser Einzelteile und zur Integration in einer gemeinsamen Anwendung. Als Teil von Self Contained Systems sind Microfrontends besonders sinnvoll einzusetzen. Dieses Konzept möchte ich nun erläutern.

Self Contained Systems – Gekapselte Fachlichkeit

In einer klassischen Drei-Schichten-Architektur werden Anwendungen horizontal in Persistenz, Geschäftslogik und Nutzungsoberfläche geteilt. Es entstehen Datenbank-, Backend- und Frontend-Teams.

Self Contained Systems hingegen setzen auf einen vertikalen Schnitt entlang der Fachlichkeit. Dadurch entstehen crossfunktionale Teams, die ihre Anwendungen durch alle Schichten hindurch entwickeln und verantworten. Am Beispiel des E-Commerce wird dies nun erläutert.

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 „Next Level Commerce“ 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 Vertikalen „Suchen“, „Entdecken“, „Auswählen“, „Kaufen“ und „Begleiten“ aufgeteilt. Da in jedem Bereich ein anderes Kundenbedürfnis vorrangig ist, ergeben sich auch unterschiedliche Anforderungen an die interne Architektur.

Customer Journey im Webshop unterteilt nach Fachlichkeit

Customer Journey im Webshop unterteilt nach Fachlichkeit

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. Die interne Architektur innerhalb ihres Systems wählt es passend zu den eigenen Rahmenbedingungen. 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. Ebenso liegt die Auswahl des Frameworks im Frontend in seiner Hand.

Self Contained Systems werden vertikal nach Fachlichkeit geschnitten und umfassen von der Oberfläche bis zur Persistenz alle Schichten

Self Contained Systems werden vertikal nach Fachlichkeit geschnitten und umfassen von der Oberfläche bis zur Persistenz alle Schichten

Um die Unabhängigkeit der Vertikalen zu gewährleisten, sollten Self Contained Systems wann immer möglich Daten asynchronen austauschen. Das produzierende System stellt die Daten beispielsweise als Feed bereit, die von den Interessenten nach Belieben konsumiert und weiter verarbeitet werden können. Die Redundanz in der Datenhaltung wird dabei gerne in Kauf genommen, denn so hat die konsumierende Vertikale die Möglichkeit, die Daten aus einer eigenen Datenbank zu lesen anstatt durch synchrone Anfragen an ein anderes System eine Laufzeitabhängigkeit zu haben.

UI-Integration mit Microfrontends

Microfrontends basieren auf der Idee, dass verschiedene Services Seiten ausspielen, auf denen sowohl eigene Inhalte als auch UI-Elemente von anderen zusammengeführt werden. Um das zu erreichen, gibt es verschiedene Möglichkeiten der Implementierung. Im Folgenden möchte ich die Techniken vorstellen, die sich als besonders geeignet für Microfrontends erwiesen haben.

Custom Elements

Mit den Custom Elements aus dem Web Components-Standard ist es möglich, selbst HTML-Elemente zu definieren, die ein eigenes Aussehen und Verhalten mitbringen. Sie haben einen eigenen Lifecycle, der per Callback-Funktionen dafür genutzt werden kann, selbst Logik auszuführen. Das ist besonders praktisch, um beim Initalisieren API-Requests auszuführen oder auf äußere Änderungen von Attributen zu reagieren.

Custom Elements können selbständig eine API anfragen

Custom Elements können selbständig eine API anfragen

So ist es möglich, dass ein Team ein Custom Element eines anderen einbindet und dieses sich selbständig die Daten aus dem eigenen System beschafft, die es benötigt. Die Funktionalität ist stark gekapselt und das einbindende Team benötigt kein Wissen über Request-URLs oder die Interna des Elements. Durch den eigenen Lifecycle sind Custom Elements sehr reaktiv und können direkt auf User Input reagieren. Zu beachten ist, dass sie erst beim Verarbeiten des DOM im Client initialisiert werden und daher weniger für SEO geeignet sind.

Custom Events

Für die Benachrichtigung über Ereignisse in der Oberfläche eignen sich Custom Events. Diese werden an das Window gegeben, wo Interessenten durch Event Listeners auf sie reagieren. Nutzt man dafür Custom Elements, können diese wieder selbständig Daten abfragen und Logik ausführen, ohne dass das Event erzeugende Team Einfluss nehmen muss.

Beim Klick wird ein Custom Event erzeugt, auf den ein Event Listener reagiert

Beim Klick wird ein Custom Event erzeugt, auf den ein Event Listener reagiert

Gerade wenn teamübergreifend kommuniziert wird, ist zu beachten, dass ein Custom Event nur über das konkrete Ereignis informiert, aber keinen Datenaustausch betreibt. Der Payload des Events sollte also minimal gehalten werden, um keine Abhängigkeiten über das Eventsystem zu schaffen.

Server Side Includes

Bei Server Side Includes (SSIs) stellen die vertikalen Teams ihre einzubindenden UI-Elemente, die Fragmente, über eine URL bereit. Diese werden dann mit einem Include-Befehl in das Markup der Seite eingetragen. Die Einbindung erfolgt in drei Schritten:

  1. Beim Eintreffen des Request vom Client holt der vorgeschaltete Proxy das Markup für die Seite von Team A ab. Dieses enthält SSIs von Team B und C.
  2. Der Proxy fragt parallel die angegebenen URLs von Team B und C an. Das Markup aus den Antworten fügt er an die Stelle der SSI-Befehle in die Seite von Team A ein.
  3. Sobald alle Antworten eingegangen sind und eingefügt wurden, sendet er die zusammengefügte Seite an den Client zurück.

Hier gilt es zu beachten, dass die Antwort erst dann zurückgeschickt wird, wenn alle Antworten aus Schritt zwei erhalten wurden. Die langsamste Antwortzeit ist also ausschlaggebend für die Antwortzeit der gesamten Seite. Daher ist es wichtig, mit Timeouts und Fallbacks zu arbeiten.

Server Side Includes mit Request und Response

Server Side Includes mit Request und Response

Da die SSIs serverseitig aufgelöst werden, können mit ihnen sehr schnelle Ladezeiten erzielt werden, sie sind außerdem perfekt für Suchmaschinen geeignet. Auch liegt die hauptsächliche Rechenlast nicht auf den Devices der Nutzer*innen, was gerade bei mobilen Endgeräten angenehm ist. Auf der anderen Seite ist die Interaktivität bei Server Side Rendering begrenzt und um auf den Input der User*innen zu reagieren, muss der Server erneut angefragt und die Seite neu geladen werden.

SSIs und Custom Elements kombiniert

Server Side Includes und Custom Elements haben jeweils viele Vor- aber auch einige Nachteile. Um das Beste aus beiden Welten zu erhalten, können sie in Kombination verwendet werden.

Dazu stellt das Team das UI-Element, das eingebunden werden soll, sowohl als SSI-Fragment als auch als Custom Element bereit. Im Markup der Seite, die das Element beinhalten soll, wird um den SSI-Befehl herum ein Custom Element gelegt. Der Ablauf ist dann wie folgt: Zuerst wird das Server Side Include aufgelöst und die HTML-Antwort an den Client zurückgeliefert. Die initiale Seite ist dann schon bereit. Sobald der Client dann das DOM verarbeitet, wird das Custom Element initialisiert und das entstandene Inner-HTML ersetzt das Markup, das vorher vom SSI kam.

Auf diese Weise erhält man sowohl schnelle Ladezeiten und perfekte SEO-Eigenschaften als auch reaktive UI-Elemente, die schnell auf User Input reagieren können. Nach dem anfänglichen Setup hält sich der zusätzliche Aufwand beides zu implementieren in Grenzen, sodass eine sehr brauchbare Lösung für Microfrontend-Komponenten mit Universal Rendering entsteht.

Zusammenfassung

Microfrontends bieten sich erst ab einer gewissen Projektgröße und -laufzeit an, denn es handelt sich um eine eher komplexere Frontend-Architektur. Der Setup-Aufwand zu Beginn eines Projektes ist vergleichsweise hoch und es muss viel abgestimmt werden. Durch eine Umsetzung im Rahmen von Self Contained Systems ergeben sich Redundanzen in Logik, Datenhaltung, Betrieb und Infrastruktur. Doch von diesen Nachteile sollte man sich gerade in langlebigen Projekten und Systemlandschaften, die über mehrere Jahre existieren sollen, nicht abschrecken lassen. Viele Vorteile überwiegen diese Punkte.

Durch die Unabhängigkeit und Eigenverantwortung der crossfunktionalen Entwicklungsteams, ist ein schnelleres und freieres Arbeiten mit deutlich weniger Abhängigkeiten möglich, welche oft die größten Zeitfresser darstellen. Durch den begrenzten Rahmen, den die Entwickler*innen durch die fachliche Aufteilung überblicken müssen, reduziert sich der Cognitive Load und es gibt weniger Unbekannte, die bei Änderungen und Deplyoments zu Fehlern führen können.

Gleichzeitig wird der Aufbau fachlicher Expertise im jeweiligen Bereich gefördert und die interne Architektur kann bestmöglich an die Gegebenheiten angepasst werden. Nicht zu unterschätzen ist außerdem die Reduzierung der Abhängigkeit zu einem Framework in einer bestimmten Version. Denn in kleinerem Rahmen fällt das Updaten des Frameworks deutlich leichter und die Gefahr, nach einer Weile Wochen und Monate an Arbeitszeit in ein Versionsupdate zu stecken, reduziert sich drastisch.

Microfrontends optimieren auf Featureentwicklung und sind die logische Fortsetzung verteilter Systeme im Frontend, auf die wir so lange gewartet haben. Da es sich um ein architektonisches Konzept handelt, kann die konkrete Implementierung unterschiedlich ausfallen. Ausschlaggebend sind hier die Rahmenbedingungen des jeweiligen Kontexts.

Momentan scheint sich ein vielversprechender Trend zu entwickeln, der sicherlich dafür sorgen wird, dass in den nächsten Jahren noch einige weitere Möglichkeiten der Umsetzung und Erleichterungen in der Implementierung kommen werden.

 

Hinweise:

Wenn Ihnen der Beitrag gefällt oder Sie darüber diskutieren wollen, teilen Sie ihn gerne in Ihrem Netzwerk. Und falls Sie sich für weitere Tipps aus der Praxis interessieren, dann testen Sie gerne unseren wöchentlichen Newsletter mit neuen Beiträgen, Downloads und Empfehlungen.

Jennifer Pelz hat einen weiteren Beitrag im t2informatik Blog veröffentlicht:

t2informatik Blog: Chancen und Grenzen der Selbstorganisation

Chancen und Grenzen der Selbstorganisation

Jennifer Pelz
Jennifer Pelz

Jennifer Pelz ist Full Stack Softwareentwicklerin und machte ihren Master in Internationaler Medieninformatik in Berlin. Als Autorin von Fachartikeln und Speakerin auf Konferenzen befasst sie sich neben ihrer Leidenschaft für die Frontend-Entwicklung außerdem mit Themen rund um die agile Softwareentwicklung in Großprojekten, verteilte Systeme und Architekturen sowie Selbstorganisation in Unternehmen.

Jennifer arbeitet bei der Qudosoft, einer selbstorganisierten, und hierarchiefreien Softwareentwicklungs-Firma, die seit 2018 sogar ohne operativen Geschäftsführer zurechtkommt.