Wie wir unsere Anwendungen auf die Micro-Frontend-Architektur bei Gcore migrieren
- Von Gcore
- June 8, 2023
- 7 Min.

Bei Gcore sind uns die Entwicklungen in der Technologiewelt nicht fremd. Wir haben vor kurzem ein neues Projekt gestartet: die Migration unserer gröĂtenteils Angular-basierten Anwendungen auf eine Micro-Frontend-Architektur. Aufgrund unserer umfangreichen Nutzung von Angular haben wir uns fĂŒr Module Federation als Strategie fĂŒr diesen Ăbergang entschieden.
Unsere Ziele
Als wir unsere Migrationsreise begannen, legten wir klare Ziele fest. Unsere Ambitionen beschrĂ€nkten sich nicht nur auf die Modernisierung unseres Technologie-Stacks, sondern zielten auch darauf ab, die Benutzererfahrung und unseren Entwicklungsprozess spĂŒrbar zu verbessern.
- Ladezeit reduzieren: Unsere erste PrioritĂ€t bestand darin, die Leistung unserer Anwendungen durch Reduzierung der Ladezeit zu verbessern. Schnellere Ladezeiten fĂŒhren direkt zu einer verbesserten Benutzerzufriedenheit und -einbindung.
- Module wiederverwenden: Unser Ziel war es, einen effizienteren Entwicklungsprozess zu etablieren, indem wir Module in verschiedenen Anwendungen wiederverwenden. Das minimiert nicht nur die Redundanz, sondern beschleunigt auch den Entwicklungszyklus und verbessert die Wartbarkeit.
- Subdomains zugunsten von Pfadnamen aufgeben: Wir wollten auf die Verwendung von Subdomains verzichten (natĂŒrlich nicht vollstĂ€ndig) und uns stattdessen fĂŒr Pfadnamen entscheiden. Diese Ănderung wird uns eine genauere Kontrolle ĂŒber das Routing ermöglichen und eine nahtlosere Benutzererfahrung bieten.
- Widget-Skript-Initialisierung optimieren: Und schlieĂlich verfĂŒgen wir ĂŒber ein Widget-Skript, das bei jeder Anwendung initialisiert wird. Wir haben beschlossen, dass sich das Ă€ndern muss. Anstatt das Skript mit jeder App einzeln zu laden, was wertvolle Zeit verschwendet, wollten wir, dass dieser Vorgang nur einmal beim Laden unserer Shell-Anwendung durchgefĂŒhrt wird.
Diese Ziele leiteten unsere Migration zur Micro-Frontend-Architektur. In unserer Geschichte geht es nicht nur um ein technologisches Upgrade, sondern auch um das Streben nach einer effizienteren, benutzerfreundlicheren digitalen Umgebung.
Module Federation
Bevor wir nĂ€her auf unsere Reise eingehen, wollen wir etwas Licht auf das entscheidende, von uns eingesetzte Tool werfen â Module Federation. Module Federation, eine in Webpack 5 eingefĂŒhrte Funktion, ermöglicht separate Builds zur Bildung verschiedener âMikro-Frontendsâ, die nahtlos zusammenarbeiten können.
Sie ermöglicht verschiedenen JavaScript-Anwendungen, Code aus einem anderen Build dynamisch auszufĂŒhren und dabei im Wesentlichen Bibliotheken oder Komponenten gemeinsam zu nutzen. Diese Architektur fördert die Wiederverwendung von Code, optimiert die Ladezeiten und steigert die Skalierbarkeit der Anwendung erheblich.
Mit einem besseren VerstĂ€ndnis fĂŒr Module Federation können wir nun untersuchen, welche entscheidende Rolle es in unserem Migrationsprozess gespielt hat.
ngx-build-plus
Im Angular-Ăkosystem hat ngx-build-plus die Implementierung von Module Federation grundlegend verĂ€ndert. Es handelt sich um eine Erweiterung fĂŒr die Angular CLI, die es uns ermöglicht, die Build-Konfiguration zu optimieren, ohne die gesamte Webpack-Konfiguration Ă€ndern zu mĂŒssen.
Wir können gemeinsame AbhĂ€ngigkeiten definieren und so sicherstellen, dass sie nur einmal im endgĂŒltigen Bundle enthalten sind. Im Folgenden finden Sie ein Beispiel fĂŒr eine Konfiguration, in der wir Angular-Bibliotheken, RXJS und einige benutzerdefinierte Gcore-Bibliotheken freigegeben haben:
hared: share({ '@angular/core': { singleton: true, requiredVersion: '^14.0.0' }, '@angular/common': { singleton: true, requiredVersion: '^14.0.0' }, '@angular/router': { singleton: true, requiredVersion: '^14.0.0' }, rxjs: { singleton: true, requiredVersion: '>=7.1.0' }, '@gcore/my-messages': { singleton: true, strictVersion: false, packageName: '@gcore/my-messages', }, '@gcore/my-modules': { singleton: true, strictVersion: true, requiredVersion: '^1.0.0', packageName: '@gcore/my-modules', }, '@gcore/ui-kit': { singleton: true, requiredVersion: '^10.2.0' }, }),
So, da habt Ihr es, Leute! Holen Sie sich ngx-build-plus, richten Sie Module Federation ein, konfigurieren Sie Ihre gemeinsamen AbhĂ€ngigkeiten und voilĂ , Sie sind ein Micro-Frontend-Meister. Herzlichen GlĂŒckwunsch!
Oh, warten SieâŠ
Kommunikation zwischen Anwendungen
Mit zunehmender KomplexitĂ€t unserer Anwendungen wuchs auch der Bedarf an einer effizienten Kommunikation zwischen diesen. ZunĂ€chst hatten wir auf jeder Anwendungsseite ein Widget-Skript geladen und die Kommunikation zwischen der Anwendung und dem Widget wurde ĂŒber das Fensterobjekt orchestriert. Es funktionierte, und uns wurde klar, dass wir es noch weiter optimieren konnten.
Geben Sie @gcore/my-messages ein, unseren ureigenen Ritter in glĂ€nzender RĂŒstung. Es handelt sich um einen gemeinsam nutzbaren Dienst. Es Ă€hnelt eher einem Nachrichtenbus, auĂer dass es sich nicht um einen Bus handelt, sondern um einen von rxjs unterstĂŒtzten Dienst.
Aber bevor wir uns zu Metaphern hinreiĂen lassen, wollen wir eines klarstellen: Dieser Dienst kennt Widgets und Anwendungen glĂŒcklicherweise nicht. Es handelt sich lediglich um Schnittstellen von Nachrichten und der Logik zum Senden dieser Nachrichten. GrundsĂ€tzlich handelt es sich bei diesen Schnittstellen um Konventionen. Dadurch bleiben sie schlank, effizient und unvoreingenommen, was sie zu einem perfekten Vermittler fĂŒr die Kommunikation unserer Anwendungen macht.
Und es geht noch weiter.
Wo stehe ich? Mangel an Selbstwahrnehmung der Statiker
Statiker sind sich ihrer Umgebung glĂŒcklicherweise nicht bewusst, und dieser Mangel an Selbstwahrnehmung kann echte Probleme verursachen.
Um diese existenzielle Krise zu lösen, haben wir einen Mechanismus geschaffen, der jede Micro-Frontend-App ĂŒber ihre eigene Herkunft informieren kann. Es hĂ€tten verschiedene Lösungen eingefĂŒhrt werden können, aber wir entschieden uns fĂŒr my-modules.
Stellen Sie sich @gcore/my-modules als ReisefĂŒhrer vor. Dieser wird in die Shell-Anwendung eingefĂŒgt und enthĂ€lt alle wesentlichen Informationen ĂŒber die Micro-Frontend-Apps. Dieses Umgebung erkennende Modul wird wĂ€hrend der Shell-CI/CD-Prozesse konfiguriert. Wodurch es dynamisch und dennoch zuverlĂ€ssig ist und wĂ€hrend der Shell-Initialisierung gefĂŒllt wird. So kann es jederzeit nach Ihren WĂŒnschen konfiguriert werden.
Ăber Module Federation kann my-modules gemeinsam genutzt werden, sodass andere Apps bei Bedarf auf diese wichtigen Informationen zugreifen können. Beachten Sie, dass Sie jedes Mal, wenn Sie eine neue Micro-Frontend-Anwendung hinzufĂŒgen, die ĂŒber Ihre Shell bereitgestellt werden soll, my-modules aktualisieren und richtig konfigurieren mĂŒssen. So gehen keine Anwendungen mehr verloren, jeder weiĂ, wo sie sich befinden.
Lokale Entwicklung (MF-App als Standalone)
Lassen Sie uns nun ĂŒber etwas sprechen, das Sie noch nicht gesehen haben â @gcore/my-panel. Sie haben es vielleicht noch nicht in der Konfiguration des Module Federation-Webpacks gesehen, aber es war die ganze Zeit da und hat unermĂŒdlich hinter den Kulissen gearbeitet.
Die Rolle von @gcore/my-panel besteht darin, uns bei der Initialisierung des Widgets zu helfen. Es verarbeitet Widget-Nachrichten, sendet sie ĂŒber Widget-Nachrichten und macht auch das Gegenteil. Und das ist noch nicht alles; @gcore/my-panel dient einer weiteren wichtigen Rolle wĂ€hrend der lokalen Entwicklung und ermöglicht es uns, unsere Micro-Frontend-Anwendung als eigenstĂ€ndige Anwendung auszufĂŒhren.
Also, wie funktioniert es? Nun, in einer Micro-Frontend-Anwendung sollten Sie es, Àhnlich wie in der Shell-Anwendung, wÀhrend des Initialisierungsprozesses initialisieren. So gehen wir in unserem app.init.ts vor::
export async function initApp( appConfigService: AppConfigService, myModulesService: MyModulesService, myPanelService: MyPanelService, ): Promise<unknown> { await appConfigService.loadConfig(); fillMyModulesService(myModulesService, appConfigService); return myPanelService.init(appConfigService.config.widgetUrl); }
Auf diese Weise ist es uns gelungen, @gcore/my-panel zu integrieren und effektiv in unseren Anwendungen zu nutzen, was es zu einem unverzichtbaren Bestandteil unserer Migration zu einer Micro-Frontend-Architektur macht.
Wenn Sie genau hinschauen, werden Sie in der Tat einen weiteren SchlĂŒsselvorgang sehen, der in unserer Funktion initApp stattfindet. Wir fĂŒllen unser myModulesService mit Einstellungen aus unserem appConfigService. Mit diesem wichtigen Schritt wird sichergestellt, dass unsere Widgets ordnungsgemÀà mit der erforderlichen Konfiguration ausgestattet sind, um in unseren Anwendungen optimal zu funktionieren. Sie können also in Ihrer MF-Anwendung auf der Ebene app.module APP_INITIALIZER bereitstellen:
export function initApp(myPanelService: myPanelService): any { return async (): Promise<void> => { await myPanelService.init(); }; }
Sie fragen sich vielleicht: âWo ist die umfangreiche Konfiguration, die wir normalerweise in initApp von init.app.ts sehen können?â Da sind Sie nicht allein! Der Ansatz hat sich tatsĂ€chlich geĂ€ndert. Lassen Sie uns das analysieren.
Initialisierung der Micro-Frontend-Anwendung
Wenn wir unsere Anwendung auf einer DomĂ€ne wie localhost:4001 bereitstellen, verhĂ€lt sie sich genau wie eine Standard-Angular-Anwendung â dank der Magie von myPanelService.init(). Diese Funktion ermöglicht es Entwicklern, in einer vertrauten Umgebung mit ihrer Anwendung zu arbeiten. Betrachten wir diese Anwendung als Mfe1App, die auf localhost:4001 gehostet wird.
Interessant wird es jedoch, wenn wir versuchen, unsere Micro-Frontend-Anwendung in unsere Shell-Anwendung zu laden. Webpack besucht localhost:4001/remoteEntry.js, um das Micro-Frontend-Modul abzurufen. Dies ist in app-routing.module.ts unserer Shell definiert:
{ path: 'mfe1App', loadChildren: () => loadRemoteModule({ type: 'manifest', remoteName: 'mfe1App', exposedModule: './Module', }).then((m) => m.Mfe1AppModule), },
Und in unserer mfe.manifest.json:
{ "mfe1App": "<http://localhost:4001/remoteEntry.js>" }
Mit unserer Webpack-Konfiguration von Mfe1App steht nur ein Modul zur VerfĂŒgung:
new webpack.container.ModuleFederationPlugin({ name: 'mfe1App', filename: 'remoteEntry.js', exposes: { './Module': './src/app/mfe1App/mfe1App.module.ts', }, library: { type: 'module', }, }),
Diese Konfiguration stellt das Modul mfe1App mit der Datei mfe1App.module.ts als Einstiegspunkt und der Datei remoteEntry.js als Datei zum Laden des Moduls bereit. Die Eigenschaft Type wird auf Modul gesetzt, um anzuzeigen, dass das Modul ES-Module verwendet. Und deshalb ist initApp unserer Mfe1App so prĂ€gnant â wir initialisieren alles in diesem Modul. DafĂŒr verwenden wir Guards.
Betrachten Sie initMfe1App.guard.ts:
// imports @Injectable() export class InitMfe1AppGuard implements CanActivate { constructor( private myMessagesService: myMessagesService, private configService: AppConfigService, private authService: AuthService, private widgetServive: WidgetService, private mfe1AppService: mfe1AppService, //... @Optional() private myModulesService: myModulesService, ) {} public canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot, ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { this.mfe1AppService.createPrimaryUrlFromRouteSnapshot(route); if (this.widgetServive.loaded) { return true; } return this.myMessagesService.messages$.pipe( filter((message: WMessage): message is WMessageWidgetLoaded | WGlobalConfigChanged => { if ( checkMyMessageType(message, W_MESSAGE_TYPE.GLOBAL_CONFIG_CHANGED) || checkMyMessageType(message, W_MESSAGE_TYPE.WIDGET_LOADED) ) { return true; } else { this.myMessagesService.sendMessage({ type: W_MESSAGE_TYPE.GLOBAL_CONFIG_REQUEST }); return false; } }), take(1), tap((message) => this.widgetService.load(message.data)), //... mapTo(true), timeout(1000 * 10), ); }
Dieser Guard ersetzt das gewohnte APP_INITIALIZER Token und bietet ein neues Zuhause fĂŒr die gesamte Initialisierungslogik.
Sie haben es getan! Sie können Ihre Micro-Frontend-Anwendungen starten. FĂŒr Ihre MF-App ist also APP_INITIALIZERfĂŒr die eigenstĂ€ndige Initialisierung und init.guard fĂŒr das MF-Module. Dieser neue Ansatz rationalisiert den Initialisierungsprozess und bietet Entwicklern ein vertrauteres, Angular-Ă€hnliches Erlebnis. Je mehr sich die Dinge Ă€ndern, desto mehr bleiben sie wie sie sind, oder?
Aber wie steht es, wenn etwas schief geht?
providedIn: ârootâ ist nicht mehr so freundlich
Wenn Sie Ihre Micro-Frontend-Reise beginnen, kann es zu Turbulenzen kommen, insbesondere wenn Ihre Anwendung aufgrund von Injection-Konflikten nicht so reibungslos startet. Dies kann passieren, weil die meisten Ihrer Anbieter in ârootâ bereitgestellt wurden, einer beliebten Technik, die im Angular-Bereich weit verbreitet ist.
Auch wenn dieser Ansatz im Allgemeinen immer noch eine gute Praxis ist, kann er in vielen FĂ€llen fĂŒr Ihre Micro-Frontend-Apps weniger geeignet sein. Insbesondere wenn einige Ihrer Dienste von anderen Diensten und Konfigurationen abhĂ€ngig sind, die jetzt im App-Init-Guard initialisiert werden, sollten Sie sie auf der Ebene der Micro-Frontend-Anwendung bereitstellen.
Davon abgesehen kann ProvideIn: ârootâ immer noch eine praktikable Wahl fĂŒr globale, nicht konfigurierbare oder wirklich globale Dienste sein. Sie sollten jedoch Ihre analytischen FĂ€higkeiten nutzen, um Dienstleistungen dort bereitzustellen, wo sie wirklich benötigt werden.
Vielleicht ist es an der Zeit fĂŒr eine kleine Umstrukturierung â erwĂ€gen Sie, einige dieser globalen Hilfsdienste lokal zu organisieren und sie direkt dort in Komponenten einzubinden, wo sie benötigt werden. Dieser Wandel kann die ModularitĂ€t und Wartbarkeit Ihrer Anwendung verbessern und sie robuster und einfacher navigierbar machen.
Fazit
Die Reise zu einer Mikro-Frontend-Architektur bei Gcore war voller einzigartiger Herausforderungen. Durch diesen Prozess haben wir eine stÀrkere und flexiblere Grundlage geschaffen, die es den Teams ermöglicht, sich auf die Entwicklung der bestmöglichen Anwendungen zu konzentrieren.
In einer Welt der Mikro-Frontends mĂŒssen Teams Ănderungen aus gemeinsam genutzten Bibliotheken nur dann ĂŒbernehmen, wenn sie ihren Anwendungen direkt zugute kommen. Das bedeutet weniger Unterbrechungen und mehr Zeit fĂŒr Innovation. Diese Freiheit erfordert jedoch eine klare und vereinbarte Integrationsstrategie, um die KohĂ€renz zwischen verschiedenen Anwendungen aufrechtzuerhalten und die HĂ€ufigkeit von Aktualisierungen zu reduzieren.
Unsere Erfahrung zeigt, dass es beim Ăbergang zu einer Micro-Frontend-Architektur nicht nur um die Ăberwindung technischer HĂŒrden geht. Es ist ein Sprung hin zu einer modulareren, effizienteren und skalierbareren Art, Frontends zu erstellen.
Es ist wichtig zu beachten, dass die Micro-Frontend-Architektur zwar immer beliebter wird, es sich jedoch nicht um eine Einheitslösung handelt. Genau wie wir sollten Sie die spezifischen Anforderungen Ihrer Situation berĂŒcksichtigen und die Vor- und Nachteile abwĂ€gen, bevor Sie den Schritt wagen. Nur weil es der neue Trend ist, bedeutet das nicht unbedingt, dass es auch fĂŒr Ihr Projekt oder Ihre Organisation geeignet ist.
Viel GlĂŒck!
Ăhnliche Artikel
Melden Sie sich fĂŒr unseren Newsletter an
Erhalten Sie die neuesten Branchentrends, exklusive Einblicke und Gcore-Updates direkt in Ihren Posteingang.