Die Microservice-Architektur ist seit langem der De-facto-Standard bei der Entwicklung großer und komplexer Systeme. Es hat eine Reihe von Vorteilen: Es ist eine strikte Unterteilung in Module, schwache Konnektivität und Fehlerresistenz sowie der schrittweise Ausstieg aus der Produktion und die unabhängige Versionierung von Komponenten.
Wenn man von Mikroservice-Architektur spricht, wird oft nur die Backend-Architektur erwähnt, und das Frontend bleibt monolithisch. Es stellt sich heraus, dass wir einen großartigen Rückhalt gemacht haben, und die Vorderseite zieht uns zurück.
Heute werde ich Ihnen erzählen, wie wir den Microservice in unserer SaaS-Lösung durchgeführt haben und auf welche Probleme wir gestoßen sind.
Problem
Anfangs sah die Entwicklung in unserem Unternehmen so aus: An der Entwicklung von Microservices sind viele Teams beteiligt, von denen jedes seine eigene API veröffentlicht. Und es gibt ein separates Team, das SPA für den Endbenutzer unter Verwendung der API verschiedener Microservices entwickelt. Mit diesem Ansatz funktioniert alles: Microservice-Entwickler wissen alles über ihre Implementierung, und SPA-Entwickler kennen alle Feinheiten der Benutzerinteraktion. Aber es gab ein Problem: Jetzt sollte jedes Front-End alle Feinheiten aller Microservices kennen. Es gibt immer mehr Microservices, es gibt immer mehr Front-End-Anbieter - und Agile beginnt auseinanderzufallen, da die Spezialisierung innerhalb des Teams erscheint, dh Austauschbarkeit und Universalität verschwinden.
Also kamen wir zur nächsten Stufe - der modularen Entwicklung. Das Frontend-Team war in Unterbefehle unterteilt. Jeder war für seinen Teil der Anwendung verantwortlich. Es ist viel besser geworden, aber im Laufe der Zeit hat sich dieser Ansatz aus mehreren Gründen erschöpft.
- Alle Module sind heterogen und haben ihre eigenen Besonderheiten. Für jedes Modul ist eine eigene Technologie besser geeignet. Gleichzeitig ist die Wahl der Technologie unter den Bedingungen von SPA eine schwierige Aufgabe.
- Da die SPA-Anwendung (und in der modernen Welt bedeutet dies, dass sie zu einem einzigen Bündel oder zumindest einer Baugruppe kompiliert wird), kann nur die gesamte Anwendung gleichzeitig ausgestellt werden. Das Risiko jeder Auslieferung wächst.
- Das Abhängigkeitsmanagement wird immer schwieriger. Unterschiedliche Module benötigen unterschiedliche (möglicherweise spezifische) Abhängigkeitsversionen. Jemand ist nicht bereit, zur aktualisierten Abhängigkeits-API zu wechseln, und jemand kann aufgrund eines Fehlers im alten Abhängigkeitszweig keine Funktion erstellen.
- Aufgrund des zweiten Punktes muss der Freigabezyklus für alle Module synchronisiert werden. Jeder wartet auf die Nachzügler.
Frontend schneiden
Es kam ein Moment der Anhäufung kritischer Masse, und sie beschlossen, das Front-End in ... Front-End-Mikrodienste zu unterteilen. Definieren wir, was ein Front-End-Microservice ist:
- ein vollständig isolierter Teil der Benutzeroberfläche, der in keiner Weise von anderen abhängig ist; radikale Isolation; buchstäblich als eigenständige Anwendung entwickelt;
- Jeder Front-End-Microservice ist von Anfang bis Ende für eine Reihe von Geschäftsfunktionen verantwortlich, dh er ist an sich voll funktionsfähig.
- kann auf jeder Technologie geschrieben werden.
Aber wir gingen noch weiter und führten eine andere Teilungsebene ein.
Fragmentkonzept
Wir nennen ein Fragment ein Bundle, das aus
js + css +
. Tatsächlich ist dies ein unabhängiger Teil der Benutzeroberfläche, der eine Reihe von Entwicklungsregeln einhalten muss, damit er in einem allgemeinen SPA verwendet werden kann. Beispielsweise sollten alle Stile für das Fragment so spezifisch wie möglich sein. Es sollte kein Versuch unternommen werden, direkt mit anderen Fragmenten zu interagieren. Sie müssen über eine spezielle Methode verfügen, an die Sie das DOM-Element übergeben können, in das das Fragment gezeichnet werden soll.
Dank des Deskriptors können wir Informationen über alle registrierten Fragmente der Umgebung speichern und dann über die ID darauf zugreifen.
Mit diesem Ansatz können Sie zwei Anwendungen, die in unterschiedlichen Frameworks geschrieben sind, auf einer Seite platzieren. Außerdem können Sie universellen Code schreiben, mit dem Sie die erforderlichen Fragmente dynamisch auf die Seite laden, initialisieren und den Lebenszyklus verwalten können. Für die meisten modernen Frameworks reicht es aus, die „Hygienevorschriften“ zu befolgen, um dies zu ermöglichen.
In Fällen, in denen das Fragment nicht die Möglichkeit hat, mit anderen auf derselben Seite „zusammenzuleben“, gibt es ein Fallback-Skript, in dem wir das Fragment in einem Iframe zeichnen (die Lösung der damit verbundenen Probleme geht über den Rahmen dieses Artikels hinaus).
Ein Entwickler, der ein vorhandenes Snippet auf einer Seite verwenden möchte, muss lediglich Folgendes tun:
- Verbinden Sie das Microservice-Plattformskript mit der Seite.
<script src="//{URL to static cache service}/api/v1/mui-platform/muiPlatform.js"></script>
- Rufen Sie die Methode zum Hinzufügen eines Fragments zur Seite auf.
window.MUI.createFragment(
Für die Kommunikation zwischen Fragmenten gibt es außerdem einen Bus, der auf
Observable
und
rxjs
. Es ist auf NativeJS geschrieben. Darüber hinaus enthält das SDK Wrapper für verschiedene Frameworks, mit denen dieser Bus nativ verwendet werden kann. Ein Beispiel für Angular 6 ist eine Dienstprogrammmethode, die
rxjs/Observable
zurückgibt:
import {fromEvent} from "@netcracker/mui-platform/angular2-factory/modules/shared/utils/event-utils" fromEvent("<event-name>"); fromEvent(EventClassType);
Darüber hinaus bietet die Plattform eine Reihe von Diensten, die häufig von verschiedenen Fragmenten verwendet werden und in unserer Infrastruktur grundlegend sind. Dies sind Dienste wie Lokalisierung / Internationalisierung, Autorisierungsdienst, Arbeiten mit domänenübergreifenden Cookies, lokaler Speicher und vieles mehr. Für ihre Verwendung bietet das SDK auch Wrapper für verschiedene Frameworks.
Frontend kombinieren
Zum Beispiel können wir diesen Ansatz im SPA-Administrationsbereich betrachten (er kombiniert verschiedene mögliche Einstellungen von verschiedenen Microservices). Wir können den Inhalt jedes Lesezeichens als separates Fragment erstellen, das von jedem Microservice separat bereitgestellt und entwickelt wird. Dank dessen können wir einen einfachen „Header“ erstellen, der beim Klicken auf ein Lesezeichen den entsprechenden Microservice anzeigt.

Wir entwickeln die Idee eines Fragments
Die Entwicklung eines Lesezeichens mit einem Fragment ermöglicht es uns nicht immer, alle möglichen Probleme zu lösen. Es ist häufig erforderlich, einen bestimmten Teil der Benutzeroberfläche in einem Mikrodienst zu entwickeln, der dann in einem anderen Mikrodienst wiederverwendet wird.
Und hier helfen uns auch Fragmente! Da das Fragment lediglich ein DOM-Element zum Rendern benötigt, geben wir jedem Microservice eine globale API, über die er jedes Fragment in seinem DOM-Baum platzieren kann. Übergeben Sie dazu einfach die Fragment-ID und den Container, in den es gezeichnet werden soll. Der Rest wird von selbst erledigt!
Jetzt können wir eine „Verschachtelungspuppe“ jeder Verschachtelungsstufe erstellen und ganze Teile der Benutzeroberfläche wiederverwenden, ohne dass an mehreren Stellen Unterstützung erforderlich ist.
Es kommt häufig vor, dass auf einer Seite mehrere Fragmente vorhanden sind, die ihren Status ändern sollten, wenn einige allgemeine Daten auf der Seite geändert werden. Zu diesem Zweck verfügen sie über einen globalen (NativeJS) Ereignisbus, über den sie kommunizieren und auf Änderungen reagieren können.

Shared Services
In einer Microservice-Architektur erscheinen unweigerlich zentrale Dienste, Daten, die alle anderen benötigen. Zum Beispiel ein Lokalisierungsdienst, der Übersetzungen speichert. Wenn jeder Mikrodienst einzeln beginnt, nach diesen Daten zum Server zu klettern, erhalten wir während der Initialisierung nur eine Reihe von Anforderungen.
Um dieses Problem zu lösen, haben wir Implementierungen von NativeJS-Diensten entwickelt, die den Zugriff auf solche Daten ermöglichen. Dies ermöglichte es, keine unnötigen Anforderungen zu stellen und Daten zwischenzuspeichern. In einigen Fällen können Sie solche Daten sogar im Voraus auf einer HTML-Seite ausgeben, um Anforderungen vollständig zu entfernen.
Darüber hinaus wurden Wrapper für unsere Services für verschiedene Frameworks entwickelt, um deren Verwendung sehr natürlich zu gestalten (DI, feste Schnittstelle).
Vorteile von Front-End-Microservices
Das Wichtigste, was wir durch die Aufteilung eines Monolithen in Fragmente erhalten, ist die Möglichkeit, Technologien für jedes Team einzeln und ein transparentes Abhängigkeitsmanagement auszuwählen. Aber es gibt auch Folgendes:
- sehr klar geteilte Verantwortungsbereiche;
- unabhängige Ausgabe: Jedes Fragment kann seinen eigenen Veröffentlichungszyklus haben;
- Erhöhung der Stabilität der Lösung insgesamt, da die Ausgabe einzelner Fragmente andere nicht beeinflusst;
- die Möglichkeit, Features einfach zurückzusetzen und teilweise einem Publikum zugänglich zu machen;
- Das Fragment kann leicht in den Kopf jedes Entwicklers gelegt werden, was zu real führt
Austauschbarkeit der Teammitglieder; Darüber hinaus kann jedes Front-End alle Feinheiten der Interaktion mit dem entsprechenden Back-End besser verstehen.
Eine Lösung mit einem Microseris-Frontend sieht gut aus. Schließlich kann jetzt jedes Fragment (Microservice) selbst entscheiden, wie es bereitgestellt werden soll: ob Sie nur Nginx zum Verteilen von Statik benötigen, eine vollwertige Middleware zum Aggregieren von Anforderungen zum Sichern oder Unterstützen von Websockets oder eine andere Besonderheit in Form eines binären Datenübertragungsprotokolls innerhalb von http. Darüber hinaus können Fragmente ihre eigenen Assemblierungsmethoden, Optimierungsmethoden und mehr auswählen.
Nachteile von Front-End-Microservices
Auf eine Fliege in der Salbe kann man nie verzichten.
- Die Wechselwirkung zwischen Fragmenten kann nicht mit Standardröhrchenmethoden (z. B. DI) sichergestellt werden.
- Was tun mit gemeinsam genutzten Abhängigkeiten? Schließlich wächst die Größe der Anwendung sprunghaft, wenn sie nicht aus Fragmenten herausgenommen wird.
- Auf jeden Fall sollte nur einer für das Routing in der endgültigen Anwendung verantwortlich sein.
- Was tun, wenn eines der Fragmente nicht zugänglich ist / nicht gezeichnet werden kann?
- Es ist unklar, was mit der Tatsache zu tun ist, dass sich verschiedene Mikrodienste auf verschiedenen Domänen befinden können.
Fazit
Unsere Erfahrung mit diesem Ansatz hat seine Realisierbarkeit bewiesen. Die Ausgabegeschwindigkeit von Features in der Produktion hat sich erheblich erhöht. Die Anzahl der impliziten Abhängigkeiten zwischen Teilen der Schnittstelle wurde auf nahezu Null reduziert. Wir haben eine konsistente Benutzeroberfläche. Sie können Funktionen sicher testen, ohne eine große Anzahl von Personen einzubeziehen.
Leider ist es in einem Artikel sehr schwierig, die gesamte Bandbreite der Probleme und Lösungen abzudecken, die auf dem Weg zur Wiederholung einer solchen Architektur zu finden sind. Aber für uns überwiegen die Vorteile deutlich die Nachteile. Wenn Habr Interesse daran zeigt, Details der Implementierung dieses Ansatzes zu enthüllen, werden wir eine Fortsetzung schreiben!