Suche
Kolumne: Die Angular-Abenteuer

Struktur für große Angular-Anwendungen: Microservices, Module, MonoRepo?

Manfred Steyer

©SuS_Media

In der Kolumne Die Angular-Abenteuer stürzt sich Manfred Steyer mitten ins Getümmel der dynamischen Welt der Webentwicklung. Tipps und Tricks für Angular-Entwickler kommen dabei ebenso zur Sprache wie aktuelle Entwicklungen im JavaScript-Ökosystem.

Angular wurde für große Unternehmensanwendungen entworfen. Allerdings stellt sich schnell die Frage, wie man die damit entwickelten Anwendungen strukturiert, damit sie langfristig wartbar bleiben. Nachdem ich in der letzten Ausgabe den Microservices-Ansatz vor dem Hintergrund von Angular beleuchtet habe, stelle ich hier ein paar zusätzliche Ideen vor, die sich in der Praxis bewährt haben. Dazu gehört der Einsatz von Angular-Modulen, aber auch das Schaffen von wiederverwendbaren npm-Paketen. Als Alternative zu npm-Paketen gehe ich auch auf den MonoRepo-Ansatz ein. Ihn setzen beispielsweise Google und Facebook erfolgreich ein.

Angular-Module schneiden

Das Bordmittel zum Strukturieren von Angular-Anwendungen sind Angular-Module. Sie bieten dem Angular-Compiler nötige Kontextinformationen, indem sie zusammengehörige Building-Blocks wie Komponenten oder Direktiven bündeln. Außerdem kann jedes Modul Services registrieren, die jedoch – auch wenn das verwundern mag – global zur Verfügung stehen. Wenn man mit einem neuen Angular startet, stellt sich sofort die Frage, wie die einzelnen Systembestandteile zu schneiden sind. Als Faustregel bietet sich die in Abbildung 1 gezeigte Modulstruktur an.

Abb. 1: Typische Modulstruktur

Die hier aufgezeigten Modularten helfen bei der gedanklichen Strukturierung. Angular selbst trifft keine Entscheidung, abgesehen davon, dass das Hauptmodul (hier Root Module) die Startkomponente bekannt gibt. Wie dieses Schema zeigt, erhält jedes Feature ein eigenes Featuremodul. Bei Geschäftsanwendungen stellt in erster Näherung jeder Use Case bzw. jede Userstory ein Feature dar. Ich habe es mir angewöhnt, zumindest mit dieser Kategorisierung zu starten und bei Bedarf das Modul in mehrere kleinere Module zu splitten.

Dabei orientiere ich mich an der aus der Psychologie bekannten 7+/-2-Regel, die besagt, dass man ungefähr eben so viele Elemente im Überblick behalten kann. Übersteigt die Anzahl meiner Deklarationen diese Regel, führe ich neue Module für Subfeatures ein. Das ist häufig problemlos, weil sich die meisten Use Cases mehr oder weniger natürlich untergliedern lassen: Flug buchen setzt sich zum Beispiel aus Flug auswählen, Passagierdaten erfassen und Zahlungsdaten erfassen zusammen.

Neben den Featuremodulen zeigt das hier präsentierte Schema auch ein Shared Module. Davon kann es eines oder auch mehrere geben. Die Idee dahinter ist es, featureübergreifende Aspekte wie allgemeine Validierungslogiken, Authentifizierung oder Logging unterzubringen. Manche Entwickler sehen daneben auch noch ein Core Module vor, das die Shell der Anwendung sowie globale Services bereitstellt. Mit Shell sind die Hauptkomponente und jene Komponenten, die das Drumherum der Anwendung repräsentieren, gemeint, also z. B. Menüs oder Fußzeilen.

For free: The iJS React Cheat Sheet

For free: The iJS React Cheat Sheet

You want to get started with React? Our Cheat Sheet includes all the most important snippets. Get the sheet for free!

 

API Summit 2018
Christian Schwendtner

GraphQL – A query language for your API

mit Christian Schwendtner (PROGRAMMIERFABRIK)

npm-Pakete als De-facto-Standard

Die Strukturierung mit Angular-Modulen ist zwar ein erster Schritt, reicht aber häufig nicht aus. Gerade bei großen Unternehmensanwendungen gilt es, anwendungsübergreifend Quellcode bereitzustellen. Daneben ist es häufig wünschenswert, dass einzelne Module oder Modulgruppen separat von unterschiedlichen Teams entwickelt und getestet werden können. Für beide Anforderungen bieten sich Pakete an, die sich über ein hausinternes oder öffentliche Registry verteilen lassen. Diese Pakete orientieren sich in der Regel, wie auch Angular selbst, am Paketstandard von npm, dem Package Manager von Node.js, der aufgrund seiner weiten Verbreitung einen De-facto-Standard darstellt. Technisch gesehen ist ein npm-Paket lediglich ein Ordner mit den in Tabelle 1 gezeigten Inhalten.

Datei/Ordner Beschreibung
node_modules Ordner mit abhängigen npm-Paketen
Inhalt Der zu teilende Inhalt, vorrangig in Form von JavaScript-Dateien. Eine beliebige Strukturierung mit Unterordnern ist möglich.
package.json Metadaten zum Projekt

Tabelle 1: Aufbau eines npm-Pakets

Die Metadaten in der package.json geben unter anderem Auskunft über den Namen des Pakets (Listing 1). Wenngleich dieser bei klassischen Projekten vernachlässigt werden kann, kommt ihm bei wiederverwendbaren Paketen eine besondere Bedeutung zu. Er ist es, der bei der Installation des Pakets anzugeben ist. Außerdem kommt dieser Name auch beim Importieren des Moduls in TypeScript-Dateien zum Einsatz.

Aufgrund der in der package.json verstauten Versionsnummer, die vor jeder Veröffentlichung zu erhöhen ist, erkennen Konsumenten, ob sie die neueste Version bereits haben oder sie von einer Registry beziehen müssen. Daneben verweist diese Datei auch auf andere Pakete, auf die sich das beschriebene abstützt, sowie auf Einstiegspunkte, also auf Dateien, die beim Referenzieren des Pakets zu laden sind.

Barrels als Einstiegspunkte

Einstiegspunkte sind in der Regel sogenannte Barrels. Das sind Dateien, die das öffentliche API eines Pakets oder zumindest eines Teils davon anbieten. Dazu exportieren sie Elemente aus den einzelnen Dateien des APIs:

export * from './src/demo.service';
export { LoggerService } from './src/logger.service';
	
@NgModule({ … })
export class LoggerModule {
}

Durch dieses Exportieren muss der Konsument nicht über die interne Struktur eines Pakets Bescheid wissen, sondern lediglich ein Barrel einbinden. Um den hier exportieren LoggingService zu nutzen, kann der Konsument somit nach dem Installieren des npm-Pakets darauf zugreifen:

import { LoggerService } from 'logger-lib';

Wie das letzte Beispiel zeigt, exportiert ein Barrel auch häufig ein Angular-Modul, das die Building Blocks des Pakets gruppiert.

Angular Package Format

Für Pakete, die problemlos mit Angular und allen angebotenen Optimierungen zusammenspielen, gibt es einige Konventionen. Beispielsweise sind die Kompilate in verschiedenen Formaten anzubieten, um möglichst viele Werkzeuge zu unterstützen. Zur Unterstützung des Angular-Compilers müssen auch Metadaten bereitgestellt werden.

Eine ausführliche Beschreibung all dieser Punkte, die es zu berücksichtigen gilt, findet sich in der Definition des Angular Package Formats , die das Angular-Team bereitstellt. Zur Vereinfachung bieten sich Generatoren an, die das Grundgerüst von Paketen samt eines Build-Prozesses im Einklang mit diesem Format erzeugen. Ich habe sehr gute Erfahrung mit dem Yeoman-Generator generator-angular2-library gemacht. Die beiliegende ausführliche Dokumentation zeigt, wie beim Generieren des Grundgerüsts sowie beim Bauen und Veröffentlichen der Bibliothek vorzugehen ist.

Veröffentlichen und installieren

Um ein Paket zu veröffentlichen, brauchen wir eine npm-Registry. Die bekannteste ist wohl die öffentlich verfügbare unter https://npmjs.org, die npm standardmäßig verwendet. Allerdings gibt es auch Produkte für hausinterne Registries. Beispiele dafür sind Artifactory , Nexus  oder die äußerst leichtgewichtige Lösung Verdaccio (Abb. 2) , die sich via npm beziehen lässt.

Abb. 2: Über Verdaccio angebotene npm-Pakete

Um ein gebautes Paket zu veröffentlichen, kommt lediglich der Befehl npm publish zum Einsatz. Zum Installieren nutzen wir hingegen das bekanntere Gegenstück npm install. Um anzugeben, dass die hausinterne Registry zu nutzen ist, lassen sich beide Anweisungen mit dem Schalter —registry aufrufen:

npm publish --registry http://...
npm install logger-lib --registry http://...

MonoRepo: alles unter einem Dach

Für den rein internen Gebrauch kann das ständige Bauen, Veröffentlichen und Installieren von neuen Paketversionen äußerst lästig sein. Außerdem erhöht ein Wildwuchs an eingesetzten Versionen die Komplexität. Aus diesem Grund setzen Firmen wie Google oder Facebook auf den sogenannten MonoRepo-Ansatz. Dieser sieht vor, dass sämtliche Anwendungen und Bibliotheken in einem einzigen Repository untergebracht werden. Auch wenn das im ersten Moment seltsam klingt, klappt diese Idee bei den genannten Firmen außerordentlich gut, wie es zum Beispiel der Artikel eines Angular-Core-Teammitglieds zeigt.

Wer nicht ganz so weit gehen möchte, beschränkt sich pro Repository erst einmal auf alle Anwendungen und Bibliotheken eines Projekts. Somit ist gewährleistet, dass jeder immer die neuesten Versionen sämtlicher Pakete nutzt. Das bringt zusätzlich ein hohes Maß an qualitätssichernden Maßnahmen mit sich, allen voran Testautomatisierung und CI. Außerdem können die einzelnen Anwendungen und Bibliotheken sehr einfach aufeinander zugreifen, da sie sich im Dateisystem nebeneinander befinden. Im Zusammenspiel mit TypeScript und dem Angular CLI braucht es hierzu ein paar Konfigurationseinträge. Diese stellen sicher, dass sich die einzelnen Projektbestandteile gegenseitig finden und auch separat kompiliert werden können.

Damit der Entwickler das nicht manuell machen muss, bieten sich auch hier Generatoren an. Ein populärer Generator ist nx , der von ehemaligen Mitgliedern des Angular-Core-Teams stammt. Er erzeugt einen sogenannten Workspace. Dabei handelt es sich um ein Angular-CLI-Projekt, das den MonoRepo-Ansatz verfolgt (Abb. 3).

Abb. 3: Nx Workspace für MonoRepo

Der Ordner apps beinhaltet alle Anwendungen, der Ordner libs alle Bibliotheken. Daneben stützen sich alle Projekte in diesem Namespace auf dieselben Node-Pakete im Ordner node_modules. Das verhindert Versionskonflikte. Durch die generierte TypeScript-Konfiguration, können alle Anwendungen über einen Alias, der einem Paketnamen entspricht, auf die Bibliotheken zugreifen:

import { AuthService } from '@flight-portal/auth';

Beim Scope @flight-portal handelt es sich hier um den Namen des Workspaces und das darauffolgende auth ist der Name der adressierten Bibliothek. Zum Erzeugen neuer Applikationen und Bibliotheken richtet Nx weitere CLI-Befehle ein:

ng generate app my-app
ng generate lib my-lib

Möchte der Entwickler Code für eine Anwendung oder Bibliothek generieren oder eine Anwendung ausführen bzw. bauen, ist ihr Name über den Schalter –app anzugeben:

ng generate component my-component --app myApp
ng serve --app myApp
ng build --app myApp

Bibliotheken und Microservices?

Eine weitere Möglichkeit, eine große Anwendung zu strukturieren ist, sie in Form vieler kleiner Microservices anzubieten. Eine Ausprägung dieser Idee wird im Kontext von Angular skizziert. Da Microservices ganz bewusst auf eine Entkopplung der einzelnen Anwendungen, aber auch der dahinterstehenden Teams setzen, haben geteilte Bibliotheken hier eine geringere Bedeutung. Geschäftslogiken wird man eher über Serviceschnittstellen oder andere Möglichkeiten der Interprozesskommunikation teilen.

Aber wenn es darum geht, Infrastrukturcode zu teilen, wie gemeinsame Frameworks oder Widgets, ist auch hier der Griff zu Bibliotheken sinnvoll. Auch wenn manche Projektteams in diesem Kontext vom Einsatz des MonoRepo-Ansatzes berichten, erscheint die klassische Nutzung von versionisierten Bibliotheken stimmiger. Die gewünschte Abschottung zwischen den Microservices und der damit einhergehenden Organisation wird hier besser erreicht.

Zusammenfassung

Um große Anwendungen in kleinere Teile aufzuspalten, die sich separat entwickeln und testen lassen, bieten sich unter anderem npm-Pakte an. Diese lassen sich mit einer Registry verteilen und versionisieren. Wem das mit zu viel Zeremonie einhergeht, könnte am MonoRepo-Ansatz Gefallen finden. Da hier alle Anwendungen und Bibliotheken in einem Repository zu finden sind, können sie sehr einfach aufeinander referenzieren, und es ist sichergestellt, dass jeder dieselbe Version nutzt. Bei Microservices-Architekturen bieten sich Bibliotheken zum Teilen von Infrastrukturcode, wie Frameworks oder Widgets, an. Dabei muss man sich jedoch im Klaren sein, dass das der hier angestrebten Unabhängigkeit der einzelnen Teams zuwiderläuft.

Geschrieben von
Manfred Steyer
Manfred Steyer
Manfred Steyer ist selbstständiger Trainer und Berater mit Fokus auf Angular 2. In seinem aktuellen Buch "AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript" behandelt er die vielen Seiten des populären JavaScript-Frameworks aus der Feder von Google. Web: www.softwarearchitekt.at
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: