Svenja Sorger, Author at Angular Camp https://angular-camp.de/author/svenja-sorger/ Wed, 04 Sep 2024 09:30:59 +0000 de-DE hourly 1 https://wordpress.org/?v=6.5.2 Implizite Bibliotheken mit Nx – leichtgewichtige Angular-Architekturen https://angular-camp.de/blog/implizite-bibliotheken-mit-nx/ Tue, 03 Sep 2024 07:52:20 +0000 https://angular-camp.de/?p=7896 Die Build-Lösung Nx [1] unterstützt seit Jahren beim Aufbau großer Projekte und Monorepos. Ab Werk unterstützt sie Angular und React sowie einige Node.js-basierte Frameworks.

The post Implizite Bibliotheken mit Nx – leichtgewichtige Angular-Architekturen appeared first on Angular Camp.

]]>

Durch ein Plug-in-Konzept lassen sich auch andere Frameworks wie Vue.js integrieren. Nx beschleunigt Build-Prozesse durch Caching und Parallelisierung und erlaubt das Einschränken von Zugriffen zwischen Programmteilen, um eine lose Kopplung zu erzwingen. Beides erfolgt in der Regel auf der Ebene von Bibliotheken, die sich mit einem Dependency Graph visualisieren lassen (Abb. 1).

steyer_kolumne_1
Abb. 1: Nx Dependency Graph

Da Bibliotheken in Nx nicht nur zur Schaffung von wiederverwendbarem Code, sondern auch zur Strukturierung der gesamten Lösung zum Einsatz kommen, weist ein Nx-basiertes Monorepo in der Regel eine Vielzahl an Bibliotheken auf. Jede Bibliothek hat wiederum eine Vielzahl an Konfigurationsdateien (Abb. 2).

steyer_kolumne_2
Abb. 2: Bibliothek mit Konfigurationsdateien

Auch wenn Nx diese Dateien generiert, empfinden sie Entwickler:innen immer wieder als lästigen Overhead, der von der eigentlichen Arbeit ablenkt. Genau diesen Kritikpunkt nehmen implizite Bibliotheken ins Visier. Die Idee stammt von Angular GDE Younes Jaaidi, der sie in einem Blogartikel [2] ausführlich beschrieben hat. Um die Konfigurationsdateien loszuwerden, leitet er die Konfiguration der einzelnen Bibliotheken mittels Konventionen her.

In diesem Artikel gehe ich auf diese Idee ein. Das verwendete Beispiel, das auf den Ideen aus [2] basiert, findet sich unter [3].

Architekturmatrix

Große Nx-Projekte werden häufig sowohl vertikal als auch horizontal untergliedert (Abb. 3). Aus der vertikalen Untergliederung gehen Anwendungsbereiche, z. B. Subdomänen, hervor. Die horizontale Untergliederung beschreibt technische Schichten. Je nach Projekt sind auch noch weitere Dimensionen denkbar, z. B. eine Untergliederung in server- und clientseitigen Quellcode.

steyer_kolumne_3
Abb. 3: Architekturmatrix

Durch diese Vorgehensweise wird der Code besser strukturiert und es ergeben sich weniger Diskussionen darüber, wo bestimmte Programmteile abzulegen bzw. zu finden sind. Außerdem lassen sich nun Architekturregeln einführen, wie z. B., dass jeder Layer nur auf Layer darunter Zugriff bekommt. Eine weitere Regel könnte festlegen, dass eine Subdomäne nur ihre eigenen Bibliotheken und jene aus dem Bereich shared nutzen darf. Eine sich im Lieferumfang von Nx befindliche Linting-Regel kann solche Einschränkungen sicherstellen.

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

Jeder Kreuzpunkt in der oben gezeigten Matrix entspricht in Nx einer eigenen Bibliothek. Abbildung 4 zeigt eine mögliche Ordnerstruktur für diese Bibliotheken.

steyer_kolumne_4
Abb. 4: Architekturmatrix in Ordnerstruktur abgebildet

Implizite Bibliotheken mit Project Crystal

Um zu verhindern, dass jede Bibliothek die eingangs erwähnte Vielzahl an Konfigurationsdateien erhält, nutzt die Idee von impliziten Bibliotheken ein Nx-Plug-in, dass die Konfigurationen der Bibliotheken herleitet. Möglich macht das das sogenannte Project Crystal. Dabei handelt es sich um eine Neuerung in Nx, die es erlaubt, Projektkonfigurationen programmatisch mit einem Graph zu beschreiben.

Plug-ins lassen sich entweder in npm-Paketen oder direkt im Nx-Projekt ablegen. Das hier betrachtete Beispiel nutzt letztere Variante und platziert das Plug-in unter tools/plugins/implicit-libs/src/index.ts. Diese Datei exportiert ein Tupel createNodesV2, das Nx zum Ermitteln der impliziten Bibliotheken und deren Konfigurationen einsetzt (Listing 1).

Listing 1

export const createNodesV2: CreateNodesV2 = [
  'libs/**/index.ts',
  async (indexPathList, _, { workspaceRoot }): Promise<CreateNodesResultV2> => {

    […]

  }
];
 

Der erste Eintrag definiert einen Glob. Jedes Match erkennt das Plug-in als Einsprungpunkt in eine Bibliothek. Demnach handelt es sich bei Bibliotheken um Ordner unterhalb von libs, die eine index.ts aufweisen.

Der zweite Eintrag legt eine Funktion fest. Nx übergibt die ermittelten Einsprungspunkte an den ersten Parameter indexPathList. Die Variable workspaceRoot verweist auf das Stammverzeichnis des gesamten Nx-Workspace.

Die Aufgabe dieser Funktion besteht darin, die Konfigurationen der einzelnen Bibliotheken zu erstellen und in Form des Typs CreateNodesResultV2 zurückzuliefern. Das verlinkte Beispiel konfiguriert pro Bibliothek ESlint sowie Vitest zum Ausführen von Unit-Tests.

Außerdem leitet es aus der Ordnerstruktur eine Kategorisierung für die Bibliotheken ab. Diese Kategorisierung spiegelt die Position in der Architekturmatrix (Abb. 3) wider. Eine Featurebibliothek in der Domäne „Tickets“ bekommt zum Beispiel die Kategorien type:feature und scope:tickets zugewiesen. Die so ermittelten Kategorien sind die Basis für die oben erwähnten Architekturregeln, die der Linter erzwingt. Sie finden sich in der ESlint-Konfigurationsdatei im Stammverzeichnis des Nx-Workspace (Listing 2).

Listing 2

[…]
rules: {
  '@nx/enforce-module-boundaries': [
    'error',
    {
      enforceBuildableLibDependency: true,
      allow: [],
      depConstraints: [
        {
          sourceTag: 'scope:checkin',
          onlyDependOnLibsWithTags: [
            'scope:checkin',
            'scope:shared'
          ]
        },
        {
          sourceTag: 'scope:luggage',
          onlyDependOnLibsWithTags: [
            'scope:luggage',
            'scope:shared'
          ]
        },
        {
          sourceTag: 'scope:tickets',
          onlyDependOnLibsWithTags: [
            'scope:tickets',
            'scope:shared'
          ]
        },
        {
          sourceTag: 'type:feature',
          onlyDependOnLibsWithTags: [
            'type:feature',
            'type:ui',
            'type:domain',
            'type:util'
          ]
        […]
    ]
  }
}
[…]
 

Um Nx zu veranlassen, das Plug-in aufzugreifen, ist es in der Datei nx.json, die sich ebenfalls im Stammverzeichnis des Monorepos befindet, zu referenzieren:

"plugins": [
  "./tools/plugins/implicit-libs/src/index.ts"
]
 

Neben dem Plug-in enthält [3] auch einen Generator, der Path-Mappings für sämtliche implizite Bibliotheken einrichtet:

nx g @demo/implicit-libs:update-tsconfig-paths
 

Diese Path-Mappings ermöglichen den Zugriff auf die einzelnen Bibliotheken über logische Namen:

import { TicketsService } from '@demo/tickets-data';
 

ABTAUCHEN IM DEEP DIVE

Im Fortgeschrittenen Camp tauchen Sie ab unter die Oberfläche einer modernen Angular-Anwendung.
→ Nächster Termin: 2. - 4. Dezember, Berlin

Daemon und Cache deaktivieren

Nx macht sämtliche Projektkonfigurationen und Informationen über Abhängigkeiten zwischen Projekten über einen Daemon zugänglich. Außerdem platziert es das Ergebnis einzelner Build-Aufgaben in einem Build-Cache.

Während diese Maßnahmen die Performance von Nx erheblich verbessern, können sie beim Entwickeln von Plug-ins zum Verhängnis werden. Um zu verhindern, dass Ergebnisse des Plug-ins während der Entwicklung im Cache landen, empfiehlt es sich, beide Mechanismen zu deaktivieren. Das lässt sich zum Beispiel bewerkstelligen, indem man die Umgebungsvariablen NX_DAEMON und NX_CACHE auf false setzt. Unter Windows lassen sich dazu die folgenden Anweisungen nutzen:

set NX_DAEMON=false
set NX_CACHE=false
 

Implizite Bibliotheken in Aktion

Um eine implizite Bibliothek anzulegen, ist lediglich ein entsprechender Ordner unter libs einzurichten und mit einer index.ts zu versehen (Abb. 5).

steyer_kolumne_5
Abb. 5: Struktur einer impliziten Bibliothek

Um zu prüfen, ob Nx die implizite Bibliothek erkennt, bietet es sich an, mit ng graph einen Dependency Graph zu erzeugen. Alternativ dazu lässt sich Nx auch dazu veranlassen, die Namen sämtlicher Bibliotheken auf der Konsole auszugeben: nx show projects. Um herauszufinden, wie das Plug-in die einzelnen Bibliotheken konfiguriert hat, kann die folgende Anweisung genutzt werden:

nx show project tickets-feature-booking
 

Daraufhin generiert Nx eine Seite, die die abgeleitete Konfiguration beschreibt (Abb. 6).

steyer_kolumne_6
Abb. 6: Abgeleitete Konfiguration einsehen

Hier ist zum Beispiel zu sehen, dass die Bibliothek tickets-feature-booking die Kategorien (Tags) type:feature und scope:tickets erhalten hat sowie Linting und Unit-Tests unterstützt. Demnach lassen sich die folgenden Befehle aufrufen:

nx lint tickets-feature-booking
nx test shared-ui-common
 

Die Kategorisierung fließt in die Linting-Rules zum Erzwingen der Architekturvorgaben ein. Versucht man beispielsweise, aus der Ticketing-Domäne heraus auf die Luggage-Domäne zuzugreifen, erhält man eine Fehlermeldung (Abb. 7).

steyer_kolumne_7
Abb. 7: Erkennen einer Zugriffsverletzung

Fazit

Implizite Bibliotheken, deren Konfigurationen ein Nx-Plug-in mittels Konventionen herleiten, vereinfachen die Arbeit mit Nx enorm. Um eine neue Bibliothek anzulegen, ist lediglich ein Ordner mit einer index.ts einzurichten. Möglich macht das Project Crystal, mit dem sich Nx-Projekte in Form eines Graphs beschreiben lassen.

 

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

 

The post Implizite Bibliotheken mit Nx – leichtgewichtige Angular-Architekturen appeared first on Angular Camp.

]]>
Authentifizierung für Microfrontends und Frontend-Modulithen https://angular-camp.de/blog/authentifizierung-fuer-microfrontends-und-frontend-modulithen/ Thu, 15 Sep 2022 07:24:13 +0000 https://angular-camp.de/?p=7113 Authentifizierungs-Gateways übernehmen transparent die Authentifizierung von Benutzern. Das gestaltet die Implementierung einfacher, aber auch sicherer. Dank Tokenaustausch haben sie für jede Domäne das richtige Access-Token parat.

The post Authentifizierung für Microfrontends und Frontend-Modulithen appeared first on Angular Camp.

]]>
Microfrontend-Architekturen untergliedern eine große Lösung in mehrere Frontends. Das hilft beim Reduzieren der Komplexität und steigert die Flexibilität. Allerdings möchte sich der Benutzer zur Laufzeit nicht bei jedem einzelnen Microfrontend erneut anmelden müssen. Tokenbbasierte Authentifizierung kann hier helfen. Aber wie genau lässt sich dieses Konzept für Microfrontend-Architekturen implementieren?

In diesem Artikel zeige ich zunächst ein paar Varianten auf und diskutiere deren Konsequenzen. Basierend darauf wählen wir eine Variante, die sich gerade bei Microfrontend-Architekturen, aber auch bei anderen großen Lösungen, z. B. Modulithen (modulare Monolithen), besonders gut zu eignen scheint. Dabei veranschauliche ich den Einsatz eines Authentifizierungs-Gateways – eine generische Lösung, die alle schwierigen Aspekte der Authentifizierung kapselt und sowohl die (Micro-)Frontends als auch die adressierten APIs entlastet. Außerdem veranschaulicht das Beispiel die Nutzung des Tokenaustauschs, sodass wir für jede Domäne ein eigenes Access-Token nutzen können.

Als Identity Provider kommt Keycloak zum Einsatz. Dieselben Konzepte lassen sich aber auch mit anderen Lösungen, die die Standards OAuth 2 und OpenID Connect unterstützen, umsetzen. Der Quellcode des Gateways findet sich unter [1] und der Quellcode der Microfrontends unter [2] (Kasten: „Andere Identity Provider“).

Andere Identity Provider

Unter [1] findet sich auch eine Gateway-Konfiguration, die nicht nur erfolgreich mit Keycloak, sondern auch mit anderen Identity-Lösungen getestet wurde. Eine davon ist das Cloud-basierte Azure Active Directory. Hier basiert der Tokenaustausch nicht wie bei Keycloak auf [2], sondern lose auf [3]. Unterm Strich sind beide Implementierungen zwar sehr ähnlich, zumal der Tokenaustausch über eine POST-Anfrage erfolgt. Im Detail unterscheiden sich die Parameter jedoch. Deswegen wurde der hierfür verwendete Service austauschbar gestaltet.

Auth0 bietet derzeit keine Möglichkeit zum Tokenaustausch an. Hier müsste man sich anders behelfen. Eine prinzipielle Umsetzung des Gateways mit einer Beispielkonfiguration für Auth0 findet sich jedoch unter auch [1].

Der mittlerweile kommerzielle Identity Server [4] kommt mit einer eigenen Gateway-Lösung, die die hier besprochenen Use-Cases abdeckt. Hierbei ist von einem Backend for Frontend (BFF) [5] die Rede. Ein Tokenaustausch lässt sich hier als Erweiterung [6] mit ein paar Zeilen Code einbringen.

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

Optionen

Große Geschäftsanwendungen verwalten in den wenigsten Fällen Ihre Benutzerkonten selbst, sondern interagieren mit existierenden Identity Providern wie Keycloak oder Active Directory. Üblicherweise stellt der Identity Provider Securitytokens aus, die den Client über die Identität des Benutzers informieren sowie Zugriff auf das Backend geben.

Für die Nutzung von Tokens in Microfrontend-Architekturen ergeben sich mehrere Möglichkeiten (Abb. 1). Zum einen können die Tokens direkt im Browser oder in einer Sitzung im Backend verwaltet werden. Die direkte Verwaltung im Browser ist sehr geradlinig. Allerdings ist sie auch anfällig für Angriffe, zumal der Browser keine sichere Möglichkeit zum Verstauen von sensiblen Informationen wie Tokens bietet. Ein Angreifer könnte die Tokens somit via XSS entwenden und daraufhin im Namen des Benutzers auftreten.

steyer_authentifizierung_1.tif_fmt1.jpg
Abb. 1: Optionen für den Einsatz von Securitytokens

Zum andern stellt sich die Frage, wie feingranular die Rechte sein sollen, die sich aus einem Token ableiten. Im einfachsten Fall teilen sich alle Microfrontends ein Token, das Zugriff auf sämtliche Ressourcen im Backend gewährt. Auf diese Weise lässt sich Single-Sign-on für sämtliche Microfrontends sehr einfach umsetzen. Allerdings erhalten somit auch Angreifer, die das Token erfolgreich entwenden, Vollzugriff auf alle Services.

Alternativ dazu könnte der Identity Provider zunächst lediglich ein sehr feingranulares Token ausstellen, aus dem wenig Rechte hervorgehen. Braucht ein Microfrontend mehr Rechte, könnte es dieses feingranulare Token gegen ein Token für Services in seiner Subdomäne eintauschen.

Die nächsten Abschnitte gehen etwas genauer auf diese vier Varianten ein. Zuvor bietet der nachfolgende Abschnitt einen Überblick über die Nutzung von OAuth 2 und OpenID Connect zur Ausstellung von Tokens.

OAuth 2 und OpenID Connect für SPA

Große Geschäftsanwendungen verwalten in den wenigsten Fällen Ihre Benutzerkonten selbst, sondern interagieren mit existierenden Identity Providern wie Active Directory. Üblicherweise stellt der Identity Provider Securitytokens aus, mit denen der Client über die Identität des Benutzers informiert sowie Zugriff auf das Backend bekommt.

Damit der Identity Provider austauschbar bleibt, empfiehlt sich der Einsatz von standardisierten Protokollen. In der heutigen Welt der REST-artigen Services haben sich die Protokolle OAuth 2 und OpenID Connect durchgesetzt. Ersteres stellt dem Client ein Access-Token aus. Damit greift der Client im Namen des Benutzers auf das Backend zu. Das auf OAuth 2 aufbauende OpenID Connect versorgt den Client mit Informationen zum Benutzer. Das erfolgt über ein sogenanntes Identity-Token (ID-Token) und/oder bei Bedarf über eine HTTP-Anfrage bei einem normierten User-Info-Endpoint (Abb. 2). Der Client leitet hierzu den Benutzer zum Authorization-Server weiter. Dort muss sich der Benutzer zu erkennen geben, zum Beispiel mit Benutzername und Passwort oder Windows-Authentifizierung. Viele Authorization Server unterstützen auch verschiedene Arten der 2-Faktor-Authentifizierung.

steyer_authentifizierung_2.tif_fmt1.jpg
Abb. 2: OAuth 2 und OpenId Connect und Vogelperspektive

Nach erfolgreicher Authentifizierung muss der Benutzer dem Client explizit die Erlaubnis erteilen, in seinem Namen auf das Backend – von den Standards als Resource-Server bezeichnet – zugreifen zu dürfen. OAuth 2 spricht hierbei von Consent, also der Einwilligung oder Zustimmung des Benutzers. Gerade Geschäftsanwendungen überspringen diesen Punkt häufig. Hier geht man davon aus, dass das Unternehmen diese Einwilligung zum Zugriff auf Unternehmensressourcen bereits im Vorfeld erteilt hat.

Anschließend leitet der Authorization Server den Benutzer wieder zum Client um. Im Rahmen dieser Umleitung erhält der Client einen sogenannten Authorization-Code in Form eines URL-Parameters. Hat der Client Zugriff auf die HTTP-Payload, kann der Authorization Server den Parameter auch dort verstauen. Diesen Code tauscht der Client nun über eine verschlüsselte HTTPS-Verbindung gegen die Tokens.

ABTAUCHEN IM DEEP DIVE

Im Fortgeschrittenen Camp tauchen Sie ab unter die Oberfläche einer modernen Angular-Anwendung.
→ Nächster Termin: 2. - 4. Dezember, Berlin

Vom Implicit Flow zu Code-Flow und PKCE

Früher war es bei Single Page Applications üblich, die Tokens direkt in Form von URL-Parametern an den Client zu übersenden. Diesen vereinfachten Ablauf bezeichnen OAuth 2 und OpenID Connect als Implicit Flow. Damit wollte man den eingeschränkten Möglichkeiten in Browseranwendungen Rechnung tragen.

Aktuelle Best-Practice-Dokumente [7] raten davon jedoch ab, und mit der nächsten OAuth-Version 2.1 wird der Implicit Flow auch als veraltet gelten. Grund hierfür sind zahlreiche Attacken, die auf den Implicit Flow möglich sind. Allen voran Attacken, die darauf beruhen, dass URL-Parameter häufig sowohl in der Browser-History als auch in serverseitigen Logs gespeichert werden.

Stattdessen empfiehlt sich zumindest für neue Anwendungen der Einsatz des Authorization-Code-Flows. Dieser entspricht der zuvor erfolgten Beschreibung (Abb. 2) sowie der ursprünglichen Idee hinter OAuth 2.

Als Ergänzung empfehlen die aktuellen Best-Practice-Dokumente auch den Einsatz eines Proof Keys for Code Exchange [8], kurz PKCE (ausgesprochen „pixie“). Salopp gesprochen handelt es sich dabei um einen weiteren Code, der den Diebstahl des Access-Codes verhindern soll. Der Client generiert diesen Code und sendet ihn typischerweise als SHA256-Hash beim Initiieren des Flows in Form eines URL-Parameters an den Authorization Server. Beim Einlösen des Access-Codes via HTTPS präsentiert der Client den Code im Klartext und beweist somit, dass er den Flow initiiert hat und somit der rechtmäßige Besitzer des Access-Codes ist.

Clientseitiges Token-Handling

Die Implementierung der einzelnen durch OAuth 2 und OpenID Connect beschriebenen Flows in Browseranwendungen ist an und für sich kein Problem. Bibliotheken wie angular-oauth2-oidc [9] kümmern sich seit Jahren um diese Aufgabe und unterstützen mittlerweile sowohl den Implicit Flow als auch Code-Flow und PKCE (Listing 1).

Listing 1: Nutzung der Bibliothek Angular-oauth2-oidc in einer Angular-Anwendung

 

// Eckdaten zur Kommunikation mit dem Auth-Server bekannt geben
this.oauthService.configure(authCodeFlowConfig);
 
// Login starten (Umleitung auf Auth-Server)
this.oauthService.loadDiscoveryDocumentAndLogin();

 

Die eigentliche Herausforderung für die Anwendung entsteht jedoch erst nach dem Erhalt der Tokens: Die Tokens müssen irgendwo verstaut werden. Egal ob man sich für eine Eigenschaft im Programmcode, für den Session Storage oder den langlebigeren Local Storage entscheidet – sobald einem Angreifer eine XSS-Attacke gelingt, kann er die Tokens auslesen und im Namen des Benutzers auftreten.

Glücklicherweise gibt es mittlerweile einige Ansätze zum Abwehren von XSS-Attacken, und Securityaudits sind auch immer häufiger an der Tagesordnung. Um das Risiko bei einem Diebstahl weiter einzudämmen, ist es üblich, Tokens eine sehr kurze Lebenszeitspanne zu gewähren. Access-Tokens, die zum Beispiel nach 5 bis 20 Minuten ablaufen, sind keine Seltenheit. Das führt allerdings zum nächsten Problem: Der Client muss sich regelmäßig ein neues Token beim Identity Provider besorgen – und das möglichst ohne erneute Benutzerinteraktion.

Für diesen Token-Refresh gibt es leider keine gute Möglichkeit im Browser. Ein häufig anzutreffender Workaround sind HTTP-only-Cookies, die es dem Identity Provider ermöglichen, sich an den aktuellen Benutzer zu erinnern. Das Anfordern der neuen Tokens erfolgt bei diesem sogenannten Silent-Refresh häufig über ein verstecktes iFrame, sodass der Benutzer davon nichts mitbekommt und die aktuelle SPA geladen bleiben kann.

Leider stellt die Nutzung von Cookies in iframes auch ein mögliches Angriffsszenario dar, weswegen immer mehr Browser uns hier einen Strich durch die Rechnung machen. Eine andere Option ist der Einsatz von Refresh-Tokens, die es dem Client erlauben, neue Tokens ohne Benutzerinteraktion anzufordern. Allerdings müssen auch Refresh-Tokens irgendwo verstaut werden und ein Diebstahl solcher Tokens erlaubt es dem Angreifer, über lange Zeit hinweg im Namen des Benutzers aufzutreten.

Deswegen verbieten die Spezifikationen von OAuth 2 und OpenID Connect auch den Einsatz von Refresh-Tokens im Browser. Ein aktuelles Best-Practice-Dokument der OAuth-2.0-Arbeitsgruppe lockert diese Einschränkungen jedoch in bestimmten Fällen. Um die damit verbundenen Probleme jedoch prinzipiell zu lösen, spricht sich dasselbe Dokument aber auch sehr stark für die Handhabung von Tokens im Backend aus.

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

Serverseitiges Token-Handling

Um es Angreifern zu erschweren, Tokens zu entwenden, bietet es sich an, Tokens lediglich in einer serverseitigen Benutzersitzung zu verwalten. Eine sehr geradlinige Möglichkeit dazu ist der Einsatz eines Authentifizierungs-Gateways (Abb. 3).

steyer_authentifizierung_3.tif_fmt1.jpg
Abb. 3: Authentifizierungs-Gateway kümmert sich um OAuth 2 und OIDC

Diese Spielart, die ich in Ausgabe 2.2022 genauer beschrieben habe, erlaubt es u. a., die vielen Details des Token-Handlings in eine generische Lösung auszulagern. Die Idee ist, sämtliche Zugriffe auf die SPA, aber auch auf die eingebundenen APIs durch das Authentifizierungs-Gateway zu tunneln.

Beim ersten Zugriff leitet das Authentifizierungs-Gateway den Benutzer zur Authentifizierung an den Identity Provider weiter. Die so erhaltenen Tokens verstaut das Gateway in einer Benutzersitzung. Um sich an den Benutzer zu erinnern, stellt das Gateway ein HTTP-only-Cookie aus. Solche Cookies lassen sich nicht via JavaScript lesen und somit auch nicht bei XSS-Attacken entwenden.

Bei allen weiteren Zugriffen auf das API ergänzt das Gateway die HTTP-Anfrage um das Access-Token. Außerdem kümmert es sich bei Bedarf um den Token-Refresh mittels Refresh-Tokens. Dem Client stellt das Gateway einen einfachen Endpunkt zur Verfügung, der Informationen zum Benutzer liefert. Weitere einfache Endpunkte erlauben es dem Client, den Benutzer abzumelden bzw. erneut anzumelden. Somit ist der Implementierungsaufwand für den Client minimal (Listing 2).

Listing 2: Angular-Code zur Kommunikation mit Authentifizierungs-Gateway

 

@Injectable({
  providedIn: 'root'
})
export class AuthService {
 
  constructor(private http: HttpClient) { }
 
  loadUserInfo(): Observable<unknown> {
    return this.http.get<unknown>('/userinfo');
  }
 
  login(): void {
    location.href = '/login';
  }
 
  logout(): void {
    location.href = '/logout';
  }
 
}

 

Aber auch die einzelnen Services haben mit dem Token-Handling an sich sehr wenig zu tun. Sie prüfen lediglich die eingehenden Access-Tokens.

Tokenaustausch: Eine Hand wäscht die andere

Natürlich möchte sich der Benutzer nicht bei jedem Microfrontend unserer Gesamtlösung separat anmelden. Somit ist es verlockend, ein einziges Token zu teilen. Das heißt aber nicht, dass dieses Token Zugriff auf sämtliche Ressourcen aller Microfrontends erlauben soll. Ein solcher Master Key wäre natürlich ein gefundenes Fressen für Angreifer und das wollen wir ihnen nicht gönnen.

Deswegen wäre es sinnvoller, zunächst ein Token mit wenig Rechten anzufordern. Dieses Token kann dann jedes Microfrontend gegen ein weiteres Token tauschen, das lediglich Zugriff auf seine Domäne erlaubt. Abbildung 4 zeigt ein Authentifizierungs-Gateway, das so einen Tokenaustausch durchführt. Mittlerweile existieren gleich mehrere Standards, die in der Welt von OAuth 2 einen Tokenaustausch ermöglichen. Beispiele dafür sind [10] und [11].

steyer_authentifizierung_4.tif_fmt1.jpg
Abb. 4: Authentifizierungs-Gateway tauscht Token

Beispielanwendung

Wie die vorangegangenen Diskussionen zeigen, ist der Einsatz von serverseitigem Token-Handling und jeweils eines eigenen Tokens pro Domäne bzw. Microfrontend sehr verlockend. Genau dieses Szenario veranschaulicht die hier gezeigte Beispielanwendung. Es handelt sich dabei um eine Shell mit zwei Microfrontends – das eine dreht sich um Flugbuchungen und das andere verwaltet Passagiere (Abb. 5).

 

steyer_authentifizierung_5.tif_fmt1.jpgAbb. 5: Beispielanwendung

Das Beispiel tunnelt sämtliche Zugriffe auf die Microfrontends und ihre APIs durch ein Gateway. Beim ersten Zugriff fordert das Gateway ein ID-Token, ein sehr eingeschränktes Access-Token sowie ein Refresh-Token an. Sobald das Microfrontend für die Flugbuchung auf sein API zugreift, tauscht das Gateway sein Access-Token gegen ein Access-Token, das Zugriff auf dieses API gewährt. Analog geht das Gateway beim Zugriff auf das Passagier-API durch das Passagier-Microfrontend vor.

Keycloak in Container starten

Als Identity Provider nutze ich in diesem Beispiel die populäre und freie Identity-Lösung Keycloak [12] aus der Feder von Red Hat. Als dieser Text geschrieben wurde, lag Keycloak in der Version 17 vor. Diese Version erlaubt den Tokenaustausch in Anlehnung an den Standard [11]. Die Umsetzung in Version 17 ist jedoch noch eine Technology Preview, die sich beim Start des Servers über ein Kommandozeilenargument bzw. über eine entsprechende Einstellung in der Konfigurationsdatei aktivieren lässt. Für Entwicklungszwecke kann Keycloak in einem Docker-Container gestartet werden. Zur besseren Lesbarkeit wurde der entsprechende Aufruf in Listing 3 auf mehrere Zeilen umgebrochen. Lassen Sie uns einen genaueren Blick auf die einzelnen Parameter werfen:

Listing 3: Keycloak im Docker-Container starten

 

docker run 
  -p 7777:8080
  --volume /c/Users/Manfred/[…]/data:/opt/keycloak/data
  -e KEYCLOAK_ADMIN=admin
  -e KEYCLOAK_ADMIN_PASSWORD=admin
  quay.io/keycloak/keycloak:17.0.1 start-dev
  -Dkeycloak.profile.feature.token_exchange=enabled
  -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
  • Der Parameter -p mappt den Container-internen Port 8080, auf dem standardmäßig Keycloak läuft, auf den Port 7777 des Hostrechners. Keycloak ist also nach dem Start über http://localhost:7777 erreichbar.

  • –volume mappt den Ordner, in dem Keycloak seine Datenbank ablegt, auf einen lokalen Ordner. Somit bleiben die getätigten Einstellungen erhalten. Standardmäßig nutzt Keycloak eine datei-basierte H2-Datenbank [13]. Für Entwicklungszwecke ist das auch ausreichend. Für den Produktionseinsatz lassen sich zahlreiche andere Datenbanken konfigurieren.

  • Die mit -e definierten Umgebungsvariablen legen Benutzername und Passwort des eingerichteten Administrators fest.

  • Der Parameter start-dev startet Keycloak im Entwicklungsmodus.

  • Der Parameter -Dkeycloak.profile.feature.token_exchange aktiviert die Preview-Implementierung des Tokenaustauschs. Um Details des Tokenaustausches konfigurieren zu können, aktiviert -Dkeycloak.profile.feature.admin_fine_grained_authz u. a. die entsprechenden Optionen in der Administrationskonsole.

Clients und Tokenaustausch in Keycloak konfigurieren

Nach dem Start von Keycloak lassen sich einzelne Clients in der Browser-basierten Administrations-Konsole konfigurieren. Für das hier gezeigte Beispiel benötigen wir die drei Clients gateway, flight-api und passenger-api. Die Einstellungen für das Gateway finden sich in Tabelle 1.

Einstellung Wert
Client ID gateway
Access Type confitential
Valid Redirect URIs http://localhost:8080/signin-oidc

http://localhost:8080/signout-callback-oidc

Base URL http://localhost:8080
Advanced Settings | Access Token Lifespan 10 Minutes
Advanced Settings | Proof Key for Code Exchange Code Challenge Method S256

Tabelle 1: Keycloak-Konfiguration des Gateways

 

Alle hier gezeigten Einstellungen beschränken sich auf jene, die von den Standardeinstellungen abweichen und für das besprochene Szenario von Bedeutung sind. Besonders wichtig sind die Valid Redirect URIs. Nur an die hier angegebenen URLs macht Keycloak einen Redirect. Das soll verhindern, dass sich Angreifer in den Flow einklinken, um z. B. in den Besitz des Access-Codes zu kommen. Neben den hier gezeigten Einstellungen erhält das Gateway im Registerblatt Credentials ein Client-Secret. Mit diesem gibt es sich später Keycloak gegenüber zu erkennen.

Die Einstellungen für die beiden APIs fallen ein wenig einfacher aus (Tabelle 2). Der Access-Type bearer-only gibt an, dass diese Clients lediglich Tokens empfangen und somit keinen Flow zum Abrufen von Tokens via OAuth 2 bzw. OpenID Connect initiieren. Aus diesem Grund entfällt auch die Angabe von Redirect-URIs sowie die Aktivierung von PKCE. Auch ein Client-Secret ist für diese Clients nicht notwendig.

Einstellung Wert
Client ID flight-api bzw. passenger-api
Access Type bearer-only
Advanced Settings | Access Token Lifespan 10 Minutes

Tabelle 2: Keycloak-Konfiguration des Flight-API und Passenger-API

Nun müssen wir es dem Gateway noch erlauben, erhaltene Access-Tokens gegen Access-Tokens für den Zugriff auf die beiden APIs einzutauschen. Dazu ist die Konfiguration des flight-api und des passenger-api zu erweitern. Die nötigen Einstellungen finden sich im Registerblatt Permissions, das in Keycloak 17 nur sichtbar ist, wenn Sie die oben diskutierte Preview-Implementierung aktivieren (Abb. 6).

steyer_authentifizierung_6.tif_fmt1.jpgAbb. 6: Permissions für APIs

Hier ist die Option Permissions Enabled zu aktivieren. Ein Klick auf den Link token-exchange führt zu einer Seite, die das Einrichten einer Policy für den Tokenaustausch erlaubt. Für das hier beschriebene Vorhaben ist eine Policy einzurichten, die einen Tokenaustausch erlaubt. Dabei handelt es sich um eine sogenannte Client-Policy. Um sie einzurichten, ist im Drop-down-Feld auf der rechten Seite der Eintrag Client auszuwählen (Abb. 7). Daraufhin erscheint ein Detaildialog (Abb. 8). Die Policy erhält einen beliebigen eindeutigen Namen. Außerdem ist im Drop-down-Feld der Client gateway auszuwählen.

steyer_authentifizierung_7.tif_fmt1.jpgAbb. 7: Client-Policy
steyer_authentifizierung_8.tif_fmt1.jpgAbb. 8: Policy für Tokenaustausch

Wechseln Sie nach dem Speichern der Policy auf die vorherige Seite und stellen Sie sicher, dass die neue Policy unter Apply Policy aufscheint (Abb. 7). Dazu müssen Sie ggf. die Policy aus dem Drop-down-Feld auswählen, nachdem Sie dort den Anfangsbuchstaben Ihres Namens erfasst haben. Diese Einstellung ist für beide APIs vorzunehmen.

Neben den Clients benötigen wir noch den einen oder anderen Benutzer, der sich auch in der Administrationskonsole einrichten lässt. Es empfiehlt sich, auch optionale Felder wie Vorname, Nachname oder E-Mail-Adresse auszufüllen. Wir werden diese Werte später im ausgestellten ID-Token wiederfinden. Ähnlich wie bei den Clients existiert für Benutzer ein eigenes Registerblatt Credentials. Hier lassen sich Passwörter hinterlegen (Kasten: „Demo: Keycloak in der Cloud“).

Demo: Keycloak in der Cloud

Alternativ können wir zum Testen der hier diskutierten Lösung auch einen Keycloak-Server in der Cloud betreiben. Sie finden alle nötigen Eckdaten in dem Abschnitt dieses Artikels, in dem es um die Konfiguration und den Start des Gateways geht.

Implementierung mit YARP und ASP.NET Core

Die Umsetzung des Gateways erfolgt mit YARP. Die Abkürzung YARP steht für Yet Another Reverse Proxy [14]. Das Besondere daran ist, dass Microsoft YARP als Middleware für ASP.NET Core entwickelt hat. Das bedeutet, dass man nicht nur einen vollwertigen Reverse Proxy mit den typischen Möglichkeiten wie Lastausgleich, Monitoring und Health-Checks bekommt, sondern dass sich auch sämtliche existierende Middlewarelösungen für ASP.NET Core sowie eigene Erweiterungen nutzen lassen. Zum Beispiel lässt sich Microsofts OpenID-Connect-Implementierung für ASP. NET Core mit ein paar Zeilen Code hinzufügen.

Das gesamte Gateway ist somit vereinfacht ausgedrückt eine Ansammlung von Standardmiddlewarekomponenten aus der Feder von Microsoft sowie ein paar Erweiterungen, die sich unter anderem um den Tokenaustausch sowie das Anhängen von Tokens an API-Aufrufe kümmern.

Wer nichts mit ASP.NET Core am Hut hat, kann die gesamte Lösung auch als Blackbox betrachten und sie in einem Container laufen lassen. Ein dockerfile hierfür liegt dem Beispiel unter [1] bei. Außerdem findet sich dort auch eine docker-compose.yml, die das Austauschen der Gateway-Konfiguration vereinfacht.

Konfiguration des Gateways

Die Konfiguration des Gateways befindet sich in der Datei appsettings.json (Listing 4).

Listing 4: YARP und Middlewarekomponenten konfigurieren

 

"Gateway": {
  "SessionTimeoutInMin": "60",
  "Url": "http://localhost:8080",
  "TokenExchangeStrategy": "default"
},
"Apis": [
  {
    "ApiPath": "/flight-api/",
    "ApiAudience": "flight-api"
  },
  {
    "ApiPath": "/passenger-api/",
    "ApiAudience": "passenger-api"
  }
],
"OpenIdConnect": {
  "Authority": "http://164.92.183.220:7777/realms/master",
  "ClientId": "gateway",
  "ClientSecret": "WmsokbzFvqWRWIijKLgLktMFVnQR1TUn",
  "Scopes": "openid profile email offline_access"
},

 

Da sich derzeit die verschiedenen Identity-Lösungen auf verschiedene Standards für den Tokenaustausch stützen und diese teilweise auch lose interpretieren, nutzt das Gateway hierfür einen austauschbaren Service. Dieser lässt sich über die Eigenschaft TokenExchangeStrategy konfigurieren. Die hier verwendete Einstellung default nutzt das unter [2] beschriebene Verfahren, das heute schon von Keycloak in seinen Grundzügen unterstützt wird. Dieses Verfahren scheint sich für das verfolgte Vorhaben am besten zu eignen. Aus diesem Grund ist davon auszugehen, dass es auch andere Produkte früher oder später unterstützen werden.

Weitere mögliche Werte sind AzureAd für die Interpretation von [3] durch Azure Active Directory und none für Fälle, in denen kein Tokenaustausch stattfinden soll bzw. die genutzte Identity-Lösung diese Möglichkeit gar nicht bietet.

Der Abschnitt Apis definiert für alle Pfade, die YARP auf ein API weiterleitet, die Client-ID des jeweiligen API als ApiAudience. Diese Information gibt das Gateway beim Tokenaustausch an. Das Ergebnis ist ein neues Access-Token, das Zugriff auf das jeweilige API erlaubt.

Die verwendete Keycloak-Version erlaubt noch nicht die Angabe von Scopes für dieses neue Access-Token. Das soll sich jedoch mit einer künftigen Version ändern. Ist das mal der Fall, können Sie dem Gateway pro API den gewünschten Scope über die Eigenschaft ApiScope mitteilen:

 

"ApiPath": "/flight-api/",
  "ApiAudience": "flight-api",
  "ApiScope": "read write delete"

 

Unter OpenIdConnect finden sich die Einstellungen für die OpenID-Connect-Middleware. Als Authority kommt der URL der verwendeten Keycloak-Instanz zum Einsatz. Das Beispiel verweist auf eine Keycloak-Instanz, die wir zum Testen über die Cloud anbieten. Sowohl die hier hinterlegte ClientId als auch das ClientSecret wurden in Keycloak für das Gateway konfiguriert. Der Scope drückt die Rechte aus, die der Client im Namen des Benutzers wahrnehmen möchte. Mit den Einträgen openid profile und email drückt der Client aus, dass er Benutzerdaten via OpenID Connect lesen möchte, und offline_access führt zur Ausstellung eines Refresh-Tokens.

Die appsettings.json enthält auch Konfigurationseinträge für YARP. Darunter finden sich Routen, die auf einzelne APIs weitergeleitet werden (Listing 5).

Listing 5: Routen und Cluster

 

"ReverseProxy": {
  "Routes": {
    "flightApiRoute": {
      "ClusterId": "flightApiCluster",
      "AuthorizationPolicy": "authPolicy",
      "Match": {
        "Path": "flight-api/{**remainder}"
      },
      "Transforms": [
        {
          "PathPattern": "/api/{**remainder}"
        }
      ]
    },
    [...]
  },
  "Clusters": {
    "flightApiCluster": {
      "Destinations": {
        "destination1": {
          "Address": "http://demo.angulararchitects.io"
        }
      }
    },
    [...]
  }
}

 

Der gezeigte Fall definiert eine Route für alle Pfade, die mit flight-api beginnen. Die Einstellung transforms ändert dieses Präfix auf den vom Flight API tatsächlich verwendeten Präfix api. Anschließend erfolgt die Weiterleitung an den weiter unten definierten Cluster, der wiederum auf die Adresse des Flight API verweist. Als Cluster bezeichnet YARP eine Menge von Adressen, hinter denen sich jeweils eine Instanz desselben Diensts befindet. Auf diese Weise lässt sich Load Balancing realisieren.

Gateways starten und Konfiguration austauschen

Standardmäßig nutzt das Gateway die Konfiguration in der Datei appsettings.json. Allerdings lässt sich eine davon abweichende Konfigurationsdatei beim Start sowohl über einen Kommandozeilenparameter als auch über eine Umgebungsvariable angeben. Ersteres kann bei lokalen Tests nützlich sein. Der folgende Aufruf nutzt zum Beispiel die beiliegende Keycloak-Demokonfiguration im Ordner conf:

dotnet run conf/appsettings.keycloak.json

Vor dem ersten Start müssen auch noch die Abhängigkeiten geladen werden:

dotnet restore

Für Testzwecke können Sie das Benutzerkonto jane, dessen Passwort ebenfalls jane lautet, verwenden. Weitere Demokonfigurationen, u. a. für Azure Active Directory, Auth0 oder Identity Server, finden sich auch in diesem Ordner. Alternativ dazu lässt sich die zu nutzende Konfigurationsdatei über die Umgebungsvariable GATEWAY_CONFIG angeben. Diese Möglichkeit ist u. a. beim Betrieb in einem Container interessant:

docker build -t auth-gateway .
 
docker run 
  -it --rm -p 8080:8080 
  -e GATEWAY_CONFIG=conf/appsettings.keycloak.json 
  --name auth-gateway auth-gateway 

Zur besseren Lesbarkeit wurde der Aufruf von docker run hier wieder umgebrochen. Etwas komfortabler gestaltet sich die Nutzung von Docker Compose. Die dafür beiliegende docker-compose.yml beinhaltet bereits alle nötigen Informationen, wie die zu nutzende Konfigurationsdatei und die Ports, die beim Aufruf von docker run von Hand angegeben werden müssen. Außerdem bildet sie den lokalen Ordner conf auf den entsprechenden Ordner im Container ab. Nach einer Änderung einer der Konfigurationsdateien ist ein Neustart des Gateways trotzdem notwendig, jedoch muss der Container nicht neu gebaut werden. Um das Gateway mit Docker Compose zu starten, nutzen Sie die folgende Anweisung:

docker compose up 

Ein Blick hinter die Kulissen des Gateways

Das YARP-basierte Gateway ist sehr leichtgewichtig aufgebaut. Im Wesentlichen registriert es lediglich Services bei einem DI-Container sowie Middlewarekomponenten, die ihren Teil zu allen Anfragen und Antworten beitragen (Listing 6).

Listing 6: YARP und Middlewarekomponenten konfigurieren

var builder = WebApplication.CreateBuilder(args);
 
//
// 1. Register Services for DI
//
 
builder.Services.AddReverseProxy()
  .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
 
builder.Services.AddAuthentication([...])
  .AddCookie([...])
  .AddSession([...])
  [...]
  .AddOpenIdConnect(options => { [...] });
 
 
var app = builder.Build();
 
//
// 2. Add Middleware Components
//
 
[...]
app.UseAuthentication();
app.UseAuthorization();
[...]
app.MapReverseProxy([…]);
[...]
 
//
// 3. Start Gateway
//
 
app.Run("http://+:8080");

Die von YARP benötigten Services registriert die Erweiterungsmethode AddReverseProxy beim DI-Container. Die zu nutzenden Einstellungen entnimmt das Beispiel der Sektion ReverseProxy in der Konfigurationsdatei.

Services für weitere im Lieferumfang von ASP.NET Core enthaltene Features fügen die Erweiterungsmethoden AddAuthenticationAddCookieAddSession und AddOpenIdConnect hinzu. Diese Methode nehmen zahlreiche hier nicht abgebildete Konfigurationsparameter. Darunter befinden sich zum Beispiel die Eckdaten für die Kommunikation mit dem Authorization Server.

Nach dem Aufruf der Methode Build kreiert das Beispiel eine Pipeline mit Middlewarekomponenten, die ASP.NET Core für jede ausgehende Anfrage und eingehende Antwort durchläuft. Die Erweiterungsmethode MapReverseProxy fügt am Ende der Pipeline die Middlewarekomponente für YARP hinzu. Die Parameter dieser Methode bieten auch Einsprungpunkte für Erweiterungen. Auf deren Basis stellt das Gateway sicher, dass zum richtigen Zeitpunkt ein Tokenaustausch stattfindet bzw. die entsprechenden Access-Tokens an API-Anfragen angehängt werden. Die Methode Run startet schlussendlich das Gateway.

Um den programmatischen Einsatz des Gateways zu vereinfachen, versteckt die hier besprochene Implementierung viele dieser Aufrufe hinter eigenen Erweiterungsmethoden (Listing 7).

Listing 7: Erweiterungsmethoden des Gateways

var builder = WebApplication.CreateBuilder(args);
 
[...]
 
//
// 1. Register Services for DI
//
 
builder.AddGateway(config, disco);
 
var app = builder.Build();
 
//
// 2. Add Middleware Components
//
 
app.UseGateway();
 
//
// 3. Start Gateway
//
 
if (string.IsNullOrEmpty(config.Url)) {
  app.Run();
}
else {
  app.Run(config.Url);
}

Einsatz zur Laufzeit

Das Logging des Gateways erlaubt es, die implementierte Funktionsweise nachzuvollziehen. Nach dem Start des Gateways bietet es Zugriff auf die Shell, die die einzelnen Microfrontends lädt. Entsprechend der gezeigten Konfiguration ist das Gateway über http://localhost:8080 erreichbar.

Die einzelnen angeforderten Access-Tokens protokolliert das Gateway im Debugmodus auf der Konsole. Es liegt auf der Hand, dass diese Funktion in der Produktion nicht verwendet werden soll. Bei den Tokens handelt es sich um JSON Web Tokens (JWT), die sich z. B. unter https://jwt.io dekodieren und inspizieren lassen. Dabei fällt auf, dass das erste angeforderte Access-Token lediglich Zugriff auf das Benutzerkonto gibt:

{
  "aud": "account",
  [...]
}

Beim Zugriff auf die APIs erfolgt der Tokenaustausch. Die so erhaltenen Tokens erlauben zusätzlich den Zugriff auf das jeweilige API:

{
  "aud": [
    "account",
    "flight-api"
  ],
  [...]
}

Fazit

Leider ist der Browser kein sicherer Ort für das Verstauen von Tokens. Auch für den Token-Refresh existieren clientseitig keine wirklich guten Optionen. Deswegen empfiehlt mittlerweile auch die OAuth-2-Arbeitsgruppe, das Handling der Tokens auf die Serverseite zu verlagern.

Dank eines Authentifizierungs-Gateways lässt sich diese Idee generisch implementieren, sodass sich weder der Client noch das Backend umfangreich damit belasten müssen.

Bei Microfrontends und großen Modulithen ist es allerdings zu wenig, sich nur ein einziges Token zu teilen. Vielmehr braucht man hier mehrere feingranulare. Deswegen empfiehlt es sich, zunächst nur ein Token mit sehr eingeschränkten Rechten anzufordern. Die einzelnen Microfrontends bzw. Frontend-Module können dieses Token ohne weitere Benutzerinteraktion gegen ein weiteres Token eintauschen, das den Zugriff auf ihr API bzw. ihre Domäne gewährt. Auch diese Aufgabe lässt sich in einem Authentifizierungs-Gateways automatisieren.

The post Authentifizierung für Microfrontends und Frontend-Modulithen appeared first on Angular Camp.

]]>
Wir sehen uns in der Zukunft! https://angular-camp.de/blog/wir-sehen-uns-in-der-zukunft/ Tue, 22 Mar 2022 09:16:15 +0000 https://angular-camp.de/?p=7082 Barrels sind nicht nur ein guter Ersatz für Angular-Module, sondern sie erlauben auch die Unterscheidung zwischen öffentlichen APIs und Implementierungsdetails. Bibliotheken in Nx-basierten Workspaces ermöglichen daneben auch das Einschränken von Zugriffen und somit das Erzwingen der festgelegten Frontend-Architektur.

The post Wir sehen uns in der Zukunft! appeared first on Angular Camp.

]]>
In der letzten Ausgabe haben wir die für künftige Angular-Versionen geplanten Standalone Components besprochen. Sie kommen gänzlich ohne die ohnehin kontrovers diskutierten Angular-Module aus und gestalten somit die gesamte Anwendung leichtgewichtiger (Kasten: „Standalone Components“). Nun stellt sich die Frage, wie man eine bestehende Angular-Lösung auf eine Zukunft ohne Angular-Module vorbereitet. Ich zeige dazu vier Optionen.

Standalone Components

Um eine Komponente als Standalone Component zu kennzeichnen, ist künftig die Eigenschaft standalone auf true zu setzen. Außerdem sind weitere benötigte Standalone Components im imports-Array zu hinterlegen:

[…]
import { Component } from './standalone-shim';
import { NavbarComponent, Sidebar-Component } from './shell';
 
@Component({
  standalone: true,
  selector: 'app-root',
  imports: [
    NavbarComponent, 
    SidebarComponent,
    HomeComponent,
    AboutComponent,
    HttpClientModule,
    RouterModule.forRoot([…])
  ],
  template: `[…]`
})
export class AppComponent {
}

Mit Imports lassen sich aber auch ganze Angular-Module referenzieren. Das erlaubt das Einbinden bestehender Building Blocks, die auf der Basis von Angular-Modulen geschrieben wurden.

Option 1: Vogel-Strauß-Strategie

Lassen Sie uns mit der einfachsten Option beginnen: die Vogel-Strauß-Strategie. Man stecke den Kopf in den Sand und ignoriere das gesamte Umfeld. Auch wenn das süffisant klingt, spricht eigentlich nichts dagegen. Niemand zwingt uns, Anwendungen auf Standalone Components umzustellen. Angular wird auch weiterhin Angular-Module unterstützen. Schließlich basiert das gesamte Ökosystem darauf. Somit können Sie getrost Standalone Components ignorieren oder diese neue Möglichkeit lediglich in neuen Anwendungen bzw. Anwendungsteilen nutzen.

Option 2: Angular-Module einfach wegwerfen

Auch diese Strategie wirkt auf den ersten Blick selbstgefällig: Sie entfernen einfach sämtliche Angular-Module aus Ihrem Quellcode. Das muss auch nicht in einem Rutsch erfolgen, denn Standalone Components spielen wunderbar mit Angular-Modulen zusammen. Angular-Module lassen sich in Standalone Components importieren und vice versa. Ein Beispiel für Ersteres finden Sie im Kasten „Standalone Components“, eines für Zweiteres in Listing 1.

Listing 1: Standalone Components und NgModules

import { NgModule } from '@angular/core';
import { SomeStandaloneComponent } from './some-standalone.component';
 
@NgModule({
  imports: [SomeStandaloneComponent],
  exports: [...],
  declarations: [...],
  providers: [...],
})
export class SomeModule { }

Möglich macht diese gegenseitige Kompatibilität das mentale Modell hinter Standalone Components. Demnach ist eine Standalone Component eine Kombination aus einer Komponente und einem Modul. Auch wenn die tatsächliche technische Umsetzung keine dedizierten Angular-Module einrichtet, hilft diese Vorstellung beim Brückenschlag zwischen den beiden Welten. Außerdem erklärt sie, warum Angular Module und Standalone Components sich wechselseitig importieren können.

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

Option 3: Angular-Module durch Barrels ersetzen

Bei Barrels handelt es sich um ECMAScript-Dateien, die zusammengehörige Building-Blocks (re-)exportieren:

// /shell/index.ts
export * from './navbar/navbar.component';
export * from './sidebar/sidebar.component';

Somit lassen sich Elemente, die in der Regel gemeinsam verwendet werden, gruppieren. Andere Teile der Anwendung können alle über ein Barrel exportierten Elemente mit einer einzigen Anweisung importieren:

import * as shell from '../shell';

Nennt sich das Barrel index.js bzw. index.ts, reicht es, lediglich den Ordner des Barrels zu importieren. Diese Vorgehensweise hat neben dem Gruppieren auch den Vorteil, dass sich Barrels zum Definieren öffentlicher APIs nutzen lassen. Wenn andere Programmteile nur auf das Barrel zugreifen, können wir alle anderen Building-Blocks als Implementierungsdetails betrachten. Da von diesen Implementierungsdetails keine anderen Anwendungsteile abhängig sind, lassen sie sich einfacher ändern. Hierbei handelt es sich um eine einfache, aber auch effektive Maßnahme für stabile Softwarearchitekturen.

In einem weiteren Schritt könnte jedes Barrel auch ein Path Mapping über die tsconfig.json erhalten. In diesem Fall kann die Anwendung über schöne Namen, die den Namen von npm-Paketen gleichen, auf das Barrel zugreifen:

import * as shell from '@demo/shell';

Allerdings kommen Barrels auch mit Herausforderungen. Beispielsweise sind sie häufig die Ursache für zyklische Abhängigkeiten. Abbildung 1 veranschaulicht das anhand der Datei b.ts, die zum einen vom Barrel (index.ts) referenziert wird und zum anderen auf das Barrel zugreift.

steyer_standalone_1.tif_fmt1.jpgAbb. 1: Zyklische Abhängigkeiten via Barrel

Dieses Problem lässt sich mit zwei einfachen Regeln, die es konsequent einzuhalten gilt, von vornherein vermeiden:

  • Ein Barrel darf nur Elemente aus seinem Hoheitsgebiet veröffentlichen. Das Hoheitsgebiet erstreckt sich über den Ordner des Barrels sowie dessen Unterordner.

  • Innerhalb eines Hoheitsgebietes referenzieren sich Dateien über relative Pfade ohne Nutzung des Barrels.

Diese Regeln klingen zwar auf dem ersten Blick etwas abstrakt, allerdings gestaltet sich deren Umsetzung einfacher, als man denken würde. Um den zuvor aufgezeigten Zyklus zu vermeiden, greift in Abbildung 2 beispielsweise b.ts direkt auf die Datei a.ts zu, die sich im gleichen Hoheitsgebiet befindet. Auf den Umweg über das eigene Barrel wird verzichtet.

steyer_standalone_2.tif_fmt1.jpgAbb. 2: Zyklische Abhängigkeiten vermeiden

Dieses Problem lässt sich mit Linting in den Griff kriegen. Eine Linting-Regel müsste unerlaubte Zugriffe erkennen und anprangern. Das populäre Werkzeug Nx kommt mit einer solchen Regel, mit der sich auch noch weitere nicht gewünschte Zugriffe verhindern lassen. Schauen wir uns diese Idee genauer an.

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

Option 4: Nx Workspace mit Bibliotheken und Linting-Regeln

Das Werkzeug Nx [1] basiert auf dem Angular CLI und bringt viele Annehmlichkeiten für die Entwicklung großer Unternehmenslösungen. Nx erlaubt das Untergliedern eines großen Projekts in verschiedene Anwendungen und Bibliotheken. Jede Bibliothek hat ein öffentliches API, das ein Barrel mit dem Namen index.ts festlegt. Nx spendiert allen Bibliotheken ein Path Mapping und bringt zudem eine Linting-Regel, die das Umgehen des Barrels verhindert und andere Einschränkungen zulässt.

Diese Linting-Regel erlaubt das Erzwingen einer festgelegten Frontend-Architektur. Das Nx-Team empfiehlt z. B., eine große Anwendung vertikal nach fachlichen Domänen und horizontal nach den technischen Kategorien der Bibliotheken zu untergliedern (Abb. 3).

steyer_standalone_3.tif_fmt1.jpgAbb. 3: Architekturmatrix

Featurebibliotheken beinhalten zum Beispiel Smart Components, die Anwendungsfälle realisieren, während UI-Bibliotheken wiederverwendbare Dumb Components beherbergen. Domain-Bibliotheken kapseln das clientseitige Domänenmodell sowie Services, die darauf operieren. Utility-Bibliotheken gruppieren allgemeine Hilfsfunktionen.

Mit den genannten Linting-Regeln lässt sich nun sicherstellen, dass jede Schicht nur auf darunterliegende Schichten zugreifen darf. Zudem lassen sich Zugriffe auf andere Domänen verhindern. Bibliotheken aus dem Bereich Booking dürfen somit nicht auf Bibliotheken in Boarding zugreifen. Möchte man bestimmte Konstrukte domänenübergreifend nutzen, sind sie z. B. im Bereich shared zu platzieren. Verletzen Programmteile eine dieser Regeln, gibt der Linter augenblicklich Feedback (Abb. 4).

steyer_standalone_4.tif_fmt1.jpgAbb. 4: Feedback zu einer Linting-Regel

Die hierfür von Nx genutzte Ordnerstruktur spiegelt die gezeigte Architekturmatrix wider: Die Unterordner in libs repräsentieren die Domänen. Die darin zu findenden Bibliotheken bekommen ein Präfix wie feature- oder domain-. Diese Präfixe spiegeln die technischen Kategorien und somit die Layer wider (Abb. 5).

steyer_standalone_5.tif_fmt1.jpgAbb 5: Aufbau eines Nx Workspace

Diese vierte Option hat sich schon länger im Zusammenspiel mit Angular-Modulen zur Strukturierung großer Lösungen bewährt. Jede Bibliothek bekommt dazu genau ein Angular-Modul zugewiesen. Sobald Angular-Module jedoch optional sind, können die Angular-Module weggelassen werden. In diesem Fall dienen nur noch die Bibliotheken der Strukturierung: Ihre Barrels gruppieren zusammengehörige Building Blocks wie Standalone Components, und dank der genannten Linting-Regeln können wir unsere Architekturen erzwingen.

Fazit

Niemand muss wegen Standalone Components in Panik ausbrechen. Es handelt sich dabei nur um eine Option, und die gewohnten Angular-Module werden auch künftig unterstützt. Das bedeutet auch, dass sich bestehender Code ohne Probleme wiederverwenden lässt.

Möchte man aber die Vorteile von Standalone Components nutzen, reicht es zuerst aus, die bestehenden Angular-Module zu entfernen und die einzelnen Building Blocks direkt zu importieren. Zum Gruppieren von zusammengehörigen Konzepten bietet sich der Einsatz von Barrels an. Sie erlauben auch eine Unterscheidung zwischen internen Implementierungsdetails und öffentlichen APIs.

Das auf dem CLI basierende Werkzeug Nx bringt weitere Annehmlichkeiten: Lösungen lassen sich mit Bibliotheken untergliedern, wobei jede Bibliothek nicht nur ein Barrel zum Festlegen des öffentlichen API erhält, sondern auch ein Path Mapping für einen komfortablen Zugriff. Außerdem erlaubt Nx auch das Einschränken von Zugriffen zwischen Bibliotheken und somit das Sicherstellen der festgelegten Frontend-Architektur.

Genau deswegen wird Nx bereits heute sehr gerne für große Angular-Lösungen genutzt. Das heute noch pro Bibliothek benötigte Angular-Modul kann beim Umstellen auf Standalone Components entfernt werden. Als Ersatz fungiert dann das Barrel, das die einzelnen Standalone Components direkt veröffentlicht.

 

Links & Literatur

[1] https://www.angulararchitects.io/aktuelles/tutorial-first-steps-with-nx-and-angular-architecture/

The post Wir sehen uns in der Zukunft! appeared first on Angular Camp.

]]>
Blitzschnelle Angular Builds https://angular-camp.de/blog/blitzschnelle-angular-builds/ Thu, 08 Apr 2021 12:28:58 +0000 https://angular-camp.de/?p=6950 Nx erkennt geänderte Programmteile und baut auch nur diese erneut. Alle anderen Systembestandteile werden aus einem Cache bezogen. Damit lässt sich der Build-Vorgang enorm beschleunigen.

The post Blitzschnelle Angular Builds appeared first on Angular Camp.

]]>
Moderne Frontends werden immer größer und das wirkt sich auch auf die Build-Zeiten aus. Mit inkrementellen Builds lassen sie sich in den Griff bekommen. Somit müssen nur jene Teile, die von Änderungen betroffen sind, neu gebaut werden. Der Rest kommt aus einem Cache. Diese Strategie, die bei Google schon seit Jahren erfolgreich eingesetzt wird, benötigt entsprechende Werkzeuge. Nx [1] ist ein solches. Es ist frei verfügbar und basiert auf der Angular CLI. In diesem Artikel wird gezeigt, wie sich Nx zum inkrementellen Kompilieren von Angular-Anwendungen einsetzen lässt. Das verwendete Beispiel findet sich in meinem GitHub-Account [2].

Fallstudie

Die hier verwendete Fallstudie basiert auf einem Nx-Workspace. Er ist in eine Anwendung flights und drei Bibliotheken untergliedert (Abb. 1).

steyer_nx_incremental_1.tif_fmt1.jpgAbb. 1: Fallstudie

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

 

Nx ist in der Lage, jede Bibliothek separat zu kompilieren. Bibliotheken, die sich nicht geändert haben, kann es einem Cache entnehmen. Das ist der Schlüssel für den inkrementellen Build. Kommt Domain-driven Design zum Einsatz, könnte der Nx-Workspace jede Domäne durch solch eine Gruppe mit Bibliotheken repräsentieren. In diesem Fall kann Nx mit Zugriffseinschränkungen eine lose Kopplung zwischen Domänen sicherstellen [3]. Zum Einrichten eines solchen Workspaces steht der folgende Befehl zur Verfügung:

npm init nx-workspace flights

Er lädt die neueste Version von Nx herunter und generiert damit einen Workspace. Im Zuge dessen sind ein paar Fragen zu beantworten (Abb. 2).

steyer_nx_incremental_2.tif_fmt1.jpgAbb. 2: Erzeugung eines neuen Nx-Workspace für Angular-Projekte

Diese Anweisung erzeugt auch innerhalb des Workspaces eine erste Angular-Anwendung. Außerdem initialisiert Nx für den Workspace ein lokales Git Repository. Dessen History nutzt es später, um herauszufinden, welche Dateien geändert wurden. Für einige Vergleiche nutzt Nx auch den Hauptzweig. Sein Name kann in der nx.json-Datei festgelegt werden:

"affected": {
  "defaultBase": "main"
},

Mit dem Angular CLI lassen sich nun die Bibliotheken erzeugen:

ng g lib domain --directory luggage --buildable
ng g lib feature-checkin --directory luggage --buildable

Die Schalter directory und buildable kommen von Nx. Ersterer gibt das Verzeichnis an, in dem die Bibliothek zu erstellen ist. Beim Einsatz von DDD spiegelt es die jeweilige Domäne wider. Letzterer gibt an, dass die Bibliothek gebaut werden kann. Das ist notwendig für inkrementelle Builds. Als Alternative zu –buildable kann auch –publishable verwendet werden (Kasten: „–publishable“).

–publishable

Als Alternative zu –buildable lässt sich auch der Schalter –publishable verwenden. Er generiert ein paar weitere Dateien, die es erlauben, die Bibliothek nach dem Bauen auch via npm zu veröffentlichen. Beim Einsatz von –publishable ist seit Nx 10 zusätzlich der Schalter –import-path zu nutzen. Mit ihm lässt sich der Name des npm-Pakets angeben. Das ist notwendig, da die von Nx intern verwendeten Namen nicht zwangsweise als npm-Paketnamen verwendet werden dürfen.

Das Erzeugen von Bibliotheken und Domänen lässt sich mit dem Nx-Plug-in @angular-architects/ddd [5] automatisieren. Es erzeugt auch die nötigen Verweise zwischen den Bibliotheken und konfiguriert die oben erwähnten Zugriffseinschränkungen.

Inkrementelle Builds

Um in den Genuss von inkrementellen Builds zu kommen, ist anstatt des Angular CLI das Nx CLI zu verwenden. Es lässt sich via npm installieren:

npm i -g nx

Der Aufruf zum Kompilieren gleicht jenem des Angular CLI:

nx build luggage --with-deps

Neu ist jedoch der Schalter with-deps. Er veranlasst Nx dazu, jede Bibliothek separat zu kompilieren und das Ergebnis zu cachen. Wird diese Anweisung ein weiteres Mal ausgeführt, erhält man blitzschnell das Ergebnis aus dem Cache. Liegen hingegen Änderungen an einigen Bibliotheken vor, werden zumindest die nicht geänderten Bibliotheken aus dem Cache bezogen. Um das zu veranschaulichen, bietet es sich an, den aktuellen Stand zu committen:

git add *
git commit -m 'init'

Wird nun zum Beispiel die Bibliothek luggage-feature-checkin geändert, kann Nx das durch einen Blick in die Git History herausfinden. Diese Erkenntnis lässt sich auch mit

nx affected:dep-graph

visualisieren (Abb. 3).

steyer_nx_incremental_3.tif_fmt1.jpgAbb. 3: Abhängigkeitsgraph mit betroffenen Bibliotheken

Wie dieser Graph zeigt, ermittelt Nx nicht nur die geänderten Systembestandteile, sondern auch die, die von diesen Änderungen betroffen sind. Das sind alle, die von den geänderten abhängig sind. All diese gilt es nun, neu zu bauen. Ein nochmaliger Aufruf von

nx build luggage --with-deps

kümmert sich darum (Abb. 4).

steyer_nx_incremental_4.tif_fmt1.jpgAbb. 4: Inkrementeller Build

Wie der aus Platzgründen etwas gekürzte Screenshot zeigt, baut Nx tatsächlich nur die beiden betroffenen Systembestandteile.

ABTAUCHEN IM DEEP DIVE

Im Fortgeschrittenen Camp tauchen Sie ab unter die Oberfläche einer modernen Angular-Anwendung.
→ Nächster Termin: 2. - 4. Dezember, Berlin

Output-Cache

Der genutzte Cache lässt sich in nx.json verwalten. Genaugenommen wird hier ein sogenannter Task-Runner konfiguriert, der an einen Cache delegiert (Listing 1).

Listing 1

"tasksRunnerOptions": {
  "default": {
    "runner": "@nrwl/workspace/tasks-runners/default",
    "options": {
      "cacheableOperations": ["build", "lint", "test", "e2e"]
    }
  }
},

Die Eigenschaft cacheableOperations listet alle Anweisungen, deren Ergebnisse gecacht werden sollen. Die Eigenschaft runner verweist auf den zu nutzenden Task-Runner. Der hier gezeigte und standardmäßig eingerichtete Runner nutzt einen lokalen dateisystembasierten Cache. Er verwaltet seine Einträge im Projekt unter node_modules/.cache/nx (Abb. 5).

steyer_nx_incremental_5.tif_fmt1.jpgAbb. 5: Lokaler Cache

Nun ist es natürlich wünschenswert, den Cache mit anderen Teammitgliedern zu teilen. Hierzu stellt das Team hinter Nx die kommerzielle Nx-Cloud zur Verfügung. Sie existiert als Cloud-Lösung, lässt sich mittlerweile aber auch lokal installieren. Alternativ dazu kann man den Ordner mit dem Cache auch über einen Symlink auf ein Netzlaufwerk verweisen lassen. Da Nx Open Source ist, lassen sich auch eigene Cacheimplementierungen schreiben. Ein Beispiel dafür ist Apployees-Nx [4], (Kasten: „Apployees-Nx“). Dieses Projekt erlaubt den Einsatz von Datenbanken wie MongoDB oder Redis zum Verwalten des Nx-Cache. Erste Tests brachten ein vielversprechendes Ergebnis mit sich. Allerdings muss man an dieser Stelle auch erwähnen, dass das API der Task-Runner noch nicht final und somit möglicherweise Änderungen unterworfen ist.

Apployees-Nx

Beim Einsatz von Apployees-Nx und Redis ist zu beachten, dass die anzugebende Verbindungszeichenfolge einen Benutzernamen enthalten muss:

rediss://<dummy-user>:<password>@<host>:<port>/<db-name>

Da Redis allerdings keine Benutzernamen verwendet, kann hier ein beliebiger Dummywert angegeben werden.

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

Fazit

Durch das separate Kompilieren von Bibliotheken und den Einsatz eines Cache ist Nx in der Lage, inkrementelle Builds durchzuführen. Das beschleunigt den gesamten Build-Vorgang. Damit das möglich ist, müssen wir jedoch unsere Anwendungen in Bibliotheken untergliedern. Das wirkt sich auch auf die Struktur der gesamten Anwendung positiv aus: Jede Bibliothek hat klare Grenzen und kann Implementierungsdetails vor anderen Bibliotheken verbergen. Nur das veröffentlichte API muss abwärtskompatibel bleiben, um Breaking Changes zu vermeiden. Dieser Ansatz passt auch wunderbar zu den Ideen von DDD [3]. Pro Domäne wird eine Gruppe mit Bibliotheken erzeugt. Zwischen den einzelnen Gruppen, aber auch zwischen den Bibliotheken einer Gruppe kann Nx mit Zugriffseinschränkungen eine lose Kopplung erzwingen. Neben einem lokalen Cache unterstützt Nx auch Netzwerkcaches. Somit wird sichergestellt, dass jeder Programmstand im gesamten Team nur ein einziges Mal kompiliert werden muss.

The post Blitzschnelle Angular Builds appeared first on Angular Camp.

]]>
Interview mit Manfred Steyer https://angular-camp.de/blog/interview-mit-manfred-steyer/ Wed, 10 Feb 2021 10:05:15 +0000 https://angular-camp.de/?p=6928 Manfred Steyer beantwortet Fragen zu den neusten Entwicklungen in der Angular Welt. Welche Highlights gab es in 2020 und was kommt 2021 auf Angular-Begeisterte zu?

The post Interview mit Manfred Steyer appeared first on Angular Camp.

]]>
„Hallo Manfred und danke, dass du dir die Zeit für dieses Gespräch nimmst. Lass uns doch erst einmal einen Blick zurückwerfen: Was war denn für dich als Softwarearchitekt und Angular-Experte die wichtigste technologische Neuerung 2020?

Manfred Steyer: Das war auf jeden Fall Module Federation, weil es ganz neue Use Cases erlaubt, die meine Kunden schon seit langem brauchen. Bisher musste man für die Umsetzung von Micro-Frontend-Architekturen, bei denen unabhängige sowie separat deployte Anwendungen zur Laufzeit geladen werden, ordentlich in die Trickkiste greifen. Mit Module Federation haben wir endlich eine geradlinige Lösung dafür.

Im Herbst 2020 ist ja auch Angular 11 erschienen. Wie würdest du das Release beschreiben?

Manfred Steyer: Ich würde sagen, Version 11 ist eine weitere Abrundung. Die Entwicklung bei Angular ist ja wirklich sehr evolutionär – genau das, was man auch im Enterprise-Umfeld, wo Angular stark ist, braucht: Keine großen Umbrüche, dafür ein kleines Bündel an neuen Features alle 6 Monate.

Ganz ohne Neuerungen kam Angular 11 aber ja auch nicht aus. Was ist denn für dich spannend daran?

Steyer: Die wichtigste Neuerung für mich und viele meiner Kunden ist, dass das Angular CLI nun webpack 5 unterstützt – zumindest ist das ein experimentelles Feature. webpack 5 ist wichtig, weil es mit Module Federation eine sehr solide und innovative Lösung für die Umsetzung von Micro Frontends bietet. Hierzu haben wir in der Vergangenheit viele Tricks und Workarounds einsetzen müssen. Die brauchen wir zum Glück nicht mehr.

Und was denkst du, wie es da 2021 weitergehen wird?

Manfred Steyer: Die webpack-5-Integration wird hoffentlich mit dem CLI 12, das im Frühling 2021 kommt, produktionsreif sein. Aktuell handelt es sich dabei nur um ein experimentelles Opt-In. Das Ganze ist also derzeit eher etwas fürs Prototyping.

Du hast die Weiterentwicklung von Angular 11 ja gerade eben als „evolutionär“ bezeichnet. Wenn man sich das gesamte JavaScript-Ökosystem anschaut, würdest du sagen, dass es da inzwischen auch eher ruhiger wird oder stehen wir vor der nächsten Disruption?

Manfred Steyer: Ich erlebe die Web-Welt derzeit als eher evolutionär. In der Zeit vor 2015 habe ich sie dagegen als sehr (fr-)agil wahrgenommen. Mittlerweile haben sich gewisse defacto-Standards herauskristallisiert und das bringt Stabilität.

Das soll aber nicht heißen, dass es keine neuen und innovativen Entwicklungen gibt. Neben Module Federation hat zum Beispiel das Team hinter Nx einige coole Sache eingeführt, wie inkrementelle Builds und Tests sowie einen Build-Cache. Die Idee ist zwar nicht neu – beispielsweise würde Bazel dasselbe leisten – aber es ist gefühlt das erste Mal, dass sowas als quasi Turn-Key-Solution für einen Main-Stream-Stack wie Angular oder React zur Verfügung steht.

Ich denke aber auch an die React Server Components, die sehr wohl das Potential haben, frischen Wind und eventuell sogar einen Paradigmenwechsel zu etablieren.

Wenn du dir etwas für die JavaScript-Welt wünschen dürftest, was wäre das?

Manfred Steyer: Irgendwie wäre es cool, wenn mehr Browser die Technologien, die man mit Progressive Web Apps assoziiert, implementieren würden. Chrome ist da ja Vorreiter, aber Safari steht auf der Bremse.

Im Übrigen freue ich mich auf den Tag, wo ich Angular-Anwendungen ohne Angular-Module (die ursprünglich gar nicht geplant waren und alles etwas komplizierter machen) entwickeln kann. Dasselbe gilt für Angular-Anwendungen, die ohne Zone.js auskommen.

Du bist natürlich als Angular-Experte bekannt; uns interessiert aber auch immer der Blick über den Tellerrand. Gibt es ein Tech-Thema jenseits von Angular, mit dem du dich 2021 gern beschäftigen möchtest?

Manfred Steyer: Ich habe mir angewöhnt, jedes Jahr zur Horizonterweiterung ein Spaß-Projekt mit Technologien zu machen, die sonst gar nicht in meinem Fokus liegen. In den letzten Jahren waren das z. B. ein Pascal-Compiler, der nach Web Assembly kompiliert oder eine eigene 3D-Engine auf Basis von Canvas2D.

Für 2021 habe ich schon ein paar Ideen, wie Deep Learning mit TensorFlow oder Stream-Processing mit Spark. Eigentlich möchte ich auch mal mein eigenes neuronales Netzwerk von der Pike auf, also ohne irgendwelche Bibliotheken, implementieren.

Vielen Dank für das Gespräch!

Die Fragen stellte Ann-Cathrin Klose.

The post Interview mit Manfred Steyer appeared first on Angular Camp.

]]>
„Eine Interessante Neuerung an Angular 10 ist, dass ECMAScript-5-Browser nicht mehr standardmäßig unterstützt werden“ https://angular-camp.de/blog/eine-interessante-neuerung-an-angular-10-ist-dass-ecmascript-5-browser-nicht-mehr-standardmaessig-unterstuetzt-werden/ Mon, 20 Jul 2020 10:17:58 +0000 https://angular-camp.de/?p=6802 Jedes Jahr erscheinen zwei große neue Versionen von Angular. Das JavaScript-Framework steht also nicht still, sondern verändert sich immer wieder. Darüber haben wir mit Angular Camp Trainer Manfred Steyer gesprochen: Was ist neu in Angular 10, wie hat der neue Compiler das Framework verändert und wo kann Angular noch besser werden?

The post „Eine Interessante Neuerung an Angular 10 ist, dass ECMAScript-5-Browser nicht mehr standardmäßig unterstützt werden“ appeared first on Angular Camp.

]]>
Hallo Manfred und danke, dass du dir die Zeit für dieses Interview genommen hast. Lass uns ein wenig über Angular 10, Ivy und die Zukunft von Angular reden. Fangen wir doch mit der neuen Version an: Angular 10 ist ein relativ kleines Major Release geworden. Woran liegt das?

Manfred Steyer: Das liegt in erster Linie daran, dass Angular 10 eigentlich eine Wartungsrelease ist. Nach der Einführung von Ivy wollte man mal intern aufräumen. Außerdem war dieses Mal auch weniger Zeit, weil das Angular-Team zwei Versionen jährlich liefern möchte und Version 9 verspätet war.

Ein bisschen was hat sich aber ja trotzdem getan. Was ist denn deiner Meinung nach die wichtigste Neuerung an Angular 10?

Steyer: Eine – auch aus symbolischen Gründen – interessante Neuerung ist, dass ECMAScript-5-Browser nicht mehr standardmäßig unterstützt werden. Das bedeutet, dass das CLI mit den generierten Standardeinstellungen nur mehr ECMAScript-2015+ Bundles erzeugt. Die sind kleiner und führen somit zu einer besseren Start-Performance. Auch der Build-Vorgang beschleunigt sich dadurch, weil die CLI nicht mehr zusätzlich ECMAScript-5-Bundles generieren muss.

Wer allerdings ECMAScript-5-Browser wie den Internet Explorer 11 unterstützen muss, muss das lediglich in der Konfigurationsdatei .browserslistrc angeben.

Diese Änderung ist insofern interessant, weil sie zeigt, worauf das Angular Team wert legt. Es geht ihnen darum, Best Practices über sinnvolle Standardeinstellungen voranzutreiben und auch das Thema Performance steht ganz oben auf der Liste der Architekturziele. Diese beiden Aspekte kann man bei verschiedenen Entscheidungen wahrnehmen und ich finde es gut, dass hier konsistent gehandelt wird. Außerdem zeigt diese Änderung, dass es langsam Zeit wird, sich vom Internet Explorer 11 zu lösen. Dank des neuen Edge-Browsers wird das immer einfacher.

Die fertige Fassung von Ivy ist jetzt bereits in der 2. Major-Version von Angular enthalten. Welche Praxiserfahrungen gibt es inzwischen mit dem neuen Compiler?

Steyer: In erster Linie merkt man nur, dass die Bundles kleiner werden. Gerade sehr kleine und sehr große Projekte profitieren davon am meisten. Daneben kann man die entryComponents nun weglassen und Komponenten per Lazy Loading laden (früher konnte man nur ganze Module Lazy laden).

Ansonsten merkt man wenig von Ivy, weil es sich noch nicht bis zum öffentlichen API von Angular durchschlägt. Das war ja das große Ziel von Angular 9: Die Einführung von Ivy sollte zu keinen Breaking Changes führen. Da Aufräumen im Fokus von Angular 10 stand, hat sich in Sachen öffentlichem API auch nichts getan. Ich habe da große Hoffnungen für Version 11 und danach, weil das Potential von Ivy doch groß ist und mit einigen Ideen im Hinterkopf konzipiert wurde.

Wie geht es denn jetzt weiter, wo Ivy da ist. Was ist das nächste große Feature, das für Angular geplant ist?

Steyer: Es liegt derzeit noch keine Roadmap vor. Ich gehe aber davon aus, dass die Weiterentwicklung, wie bisher, auch evolutionär sein wird. Typischerweise nutzt jede neue Angular- und CLI-Version auch die neuesten stabilen Versionen der Abhängigkeiten, wie TypeScript und webpack. Ich hoffe, dass es dann webpack 5 sein wird, weil es mit Module Federation eine sehr interessante Neuerung für Microfrontends und Plugin-Systeme mit sich bringt.

Außerdem wird es früher oder später eine Alternative zu zone.js geben müssen. Ivy ist darauf schon vorbereitet. Daneben hoffe ich, dass sich auch andere Möglichkeiten von Ivy bis zum öffentliche API durchschlagen werden.

Update: Seit dem 06.08 gibt es die neue Roadmap: https://angular.io/guide/roadmap

Wenn du einen Wunsch frei hättest, was würdest du dir für die künftige Entwicklung von Angular wünschen?

Steyer: Ich würde gerne über das öffentliche API jene Dinge, die Ivy bereits heute unter der Motorhaube kann, nutzen können. Dazu zählen Standalone-Komponenten, also Komponenten, die ohne Angular-Module auskommen, aber auch dynamische Komponenten und Komponenten höherer Ordnung. Auch ein (sinnvolles) Arbeiten ohne zone.js wäre interessant und könnte sich positiv auf die Performance auswirken. Daneben wäre es cool, wenn Angular Elements noch kleinere Web-Component-Bundles erzeugen würde. Ivy würde das prinzipiell erlauben, wie erste Experimente zeigen.

Vielen Dank für das Gespräch!

Die Fragen stellte Ann-Cathrin Klose.

The post „Eine Interessante Neuerung an Angular 10 ist, dass ECMAScript-5-Browser nicht mehr standardmäßig unterstützt werden“ appeared first on Angular Camp.

]]>
Angular 9 ist da: Ivy, Lazy Loading und mehr https://angular-camp.de/blog/angular-9-ist-da-ivy-lazy-loading-und-mehr/ Wed, 26 Feb 2020 15:12:56 +0000 https://angular-camp.de/?p=6626 Angular 9 bringt Ivy in einer abwärtskompatiblen Variante und somit kleinere Bundles. Außerdem wurde die I18N-Lösung umfangreich überarbeitet und einige Ecken wurden abgerundet. Damit stehen nicht nur dem Entwickler, sondern auch den zukünftigen Versionen von Angular neue Möglichkeiten zur Verfügung.

The post Angular 9 ist da: Ivy, Lazy Loading und mehr appeared first on Angular Camp.

]]>
Mit Angular 9 soll es so weit sein: Ivy wird endlich Standard. Das ist möglich, weil das Angular-Team sehr viel Energie investiert hat, um Ivy abwärtskompatibel zum Vorgänger ViewEngine zu machen. Ein Blick auf den Change Log verrät, dass genau das der Fokus von Version 9 war. Abseits davon gibt es aber trotzdem ein paar sehr nette Neuerungen. In diesem Artikel stelle ich anhand von Beispielen diejenigen vor, die uns die tägliche Arbeit vereinfachen werden. Dazu verwende ich ein paar Beispiele, die sich in meinem GitHub Repository finden.

Update

Das Update auf Angular 9 ist, wie schon von den Vorgängerversionen gewohnt, sehr geradlinig. Der CLI-Befehl ng update @angular/core @angular/cli reicht aus, um die Bibliotheken auf den neuesten Stand zu heben. Eventuelle Breaking Changes werden dabei soweit als möglich automatisiert berücksichtigt. Möglich machen das Migrationsskripte, die auf dem CLI-internen Codegenerator Schematics basieren und den bestehenden Quellcode geringfügig modifizieren.
Für die Fälle, in denen wir dennoch manuell eingreifen müssen, geben die verwendeten Schematics eine Meldung aus. Außerdem findet man weitere Informationen zur Migration wie gewohnt unter Angular.io.

Ivy by default

Die wichtigste Neuerung bei Angular 9 ist wohl, dass der neue Ivy-Compiler, an dem seit vielen Monaten gearbeitet wird, standardmäßig aktiviert ist. Das bedeutet, dass unsere Bundles nach der Umstellung auf Version 9 um bis zu 40 Prozent kleiner werden. Wie sehr unsere Anwendung dieses Potenzial ausschöpfen kann, hängt von ihrem Aufbau ab.

Um Breaking Changes zu vermeiden, wurde ein besonderes Augenmerk auf die Abwärtskompatibilität gelegt. Das hat auch etwas damit zu tun, dass bei Google über 1 500 Angular-Anwendungen im Einsatz sind. Diese Anwendungen sollen selbstverständlich auch noch nach der Umstellung auf Version 9 funktionieren. Gleichzeitig halfen sie, die Qualität von Ivy sicherzustellen. Zusätzlich kam eine Vielzahl an weit verbreitenden Angular-Bibliotheken zur Qualitätssicherung zum Einsatz.

Da das Angular-Team jedoch den gesamten Unterbau austauschen musste, ist Ivy alles andere als ein einfaches Unterfangen. Deswegen kann es vorkommen, dass Angular Ivy in Randfällen den einen oder anderen Fehler zu Tage fördert. In diesen Fällen können wir Ivy mit der Eigenschaft enableIvy in der tsconfig.json deaktivieren:

1
2
3
"angularCompilerOptions": {
  "enableIvy": false
}

Das Angular-Team ist an Fällen interessiert, in denen das notwendig ist, und freut sich über entsprechende Bug Reports.

Newsletter

Jetzt anmelden & regelmäßig wertvolle Insights in Angular sowie aktuelle Weiterbildungen erhalten!

Lazy Loading von Komponenten

Auf dem ersten Blick bringt Ivy kleinere Bundles. Doch die Architektur von Ivy hat noch viel mehr zu bieten, sodass wir in den nächsten Releases neue, darauf aufbauende Features erwarten können.

Ein neues Feature ist jedoch heute schon verfügbar: Lazy Loading von Komponenten. Während Lazy Loading zwar schon von Anfang an integriert war, mussten bis dato immer ganze Angular-Module geladen werden. Der Grund war, dass ViewEngine die Metadaten für den Einsatz der einzelnen Komponenten auf Modulebene untergebracht hat. Ivy verstaut diese Metadaten nun jedoch beim Kompilieren direkt in den Komponenten, somit können diese auch separat bezogen werden.

Um den Einsatz dieser neuen Möglichkeit zu verdeutlichen, nutze ich hier ein einfaches Dashboard, das die anzuzeigenden Kacheln per Lazy Loading bezieht (Abb. 1).

bezieht (Abb. 1).

Abb. 1: Dashboard mit Lazy Loading

Abb. 1: Dashboard mit Lazy Loading

Hierfür benötigen wir zunächst Platzhalter, in den die Komponente geladen werden kann. Das kann jeder beliebige Tag sein, solange er mit einer Templatevariable markiert wird:

1
<ng-container #vc></ng-container>

Templatevariablen beginnen, wie man hier sieht, mit einer Raute. Die zugehörige Komponente kann dieses Element als ViewChild laden (Listing 1).

Listing 1
1
2
3
4
5
6
7
8
9
10
11
12
13
export class DashboardPageComponent implements OnInit, OnChanges {
  @ViewChild('vc', {read: ViewContainerRef, static: true})
  viewContainer: ViewContainerRef;
  […]
  constructor(
    private injector: Injector,
    private cfr: ComponentFactoryResolver) { }
[…]
}

Außerdem benötigen wir für das dynamische Erzeugen der lazy-Komponente den aktuellen Injector sowie einen ComponentFactoryResolver. Beides lässt sich in den Konstruktor injizieren.

Danach ist alles sehr geradlinig: Über einen dynamischen Import lässt sich die lazy-Komponente laden und der ComponentFactoryResolver ermittelt die Factory, die wir zum Instanziieren der Komponente benötigen (Listing 2).

Listing 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import('../dashboard-tile/dashboard-tile.component').then(m => {
  const comp = m.DashboardTileComponent;
  // Only b/c of compatibility; will not be needed in future!
  const factory =
    this.cfr.resolveComponentFactory(comp);
  const compRef = this.viewContainer.createComponent(
    factory, null, this.injector);
  const compInstance = compRef.instance;
  compInstance.a = Math.round(Math.random() * 100);
  compInstance.b = Math.round(Math.random() * 100);
  compInstance.c = Math.round(Math.random() * 100);
  compInstance.ngOnChanges();
});

Die Methode createComponent des ViewContainer nimmt die Factory entgegen und erzeugt eine Instanz der Komponente. Damit sie beim Dependency-Injection-Mechanismus von Angular angeschlossen wird, gilt es auch, den aktuellen Injector zu übergeben. Anschließend ruft das Beispiel die Komponenteninstanz ab, setzt Eigenschaften und ruft die ngOnChanges-Methode auf. Das Ergebnis ist eine DashboardTileComponent, die mitten im ViewContainer erscheint.

Eine weitere kleine, aber feine Neuerung ist, dass die Anwendung solche dynamischen Komponenten nicht mehr in den entryComponents registrieren muss. Dieses für viele ohnehin schwer verständliche Konstrukt existiert nur mehr für den Fall eines Fallbacks auf ViewEngine.

Besseres I18N mit @angular/localize

Die seit Version 2 in Angular integrierte Lokalisierungslösung (I18N) war in erster Linie auf Performance getrimmt. Sie erzeugt pro Locale (Kombination aus Sprache und Land) einen Build und passt beim Kompilieren die Übersetzungstexte ein, sodass sich zur Laufzeit kein Overhead ergibt. Diese Strategie hatte jedoch auch ein paar Nachteile:

  • Das Erstellen all dieser Builds war zeitintensiv
  • Es bestand keine Möglichkeit, die Übersetzungstexte zur Laufzeit festzulegen
  • Es war nicht möglich, Übersetzungstexte programmatisch zu nutzen
  • Die Sprache konnte nicht zur Laufzeit geändert werden. Stattdessen musste die Anwendung den Benutzer auf die gewünschte Sprachversion weiterleiten

Mit Angular 9 erscheint eine neue Lösung, die die meisten dieser Nachteile kompensiert und dabei trotzdem keine Abstriche in Sachen Performance macht. Sie nennt sich @angular/localize und lässt sich über das gleichnamige npm-Paket beziehen. Das neue @angular/localize erzeugt zunächst einen einzigen Build. Pro Locale legt sie danach eine Kopie an und bringt dort die Übersetzungstexte ein (Abb. 2).

Abb. 2: Funktionsweise von @angular/localize (mit freundlicher Genehmigung von Minko Gechev aus dem Angular-Team übernommen)

Abb. 2: Funktionsweise von @angular/localize (mit freundlicher Genehmigung von Minko Gechev aus dem Angular-Team übernommen)

Außerdem ergänzt @angular/localize jede Kopie um Metadaten für die Formatierung von Zahlen und Datumswerten entsprechend der Gepflogenheiten der jeweiligen Sprache. Wir müssen also nicht mehr die benötigten Metadaten beim Programmstart importieren und registrieren. Wie im Angular-Umfeld üblich lässt sich die Bibliothek via ng add beziehen:

1
ng add @angular/localize

Danach können wir die zu übersetzenden Texte in den Templates mit dem Attribut i18n versehen. Um dem Übersetzungsstudio Kontextinformationen zu bieten, erhält dieses Attribut einen Wert mit der Bedeutung und einer Beschreibung:

1
<h1 i18n="meaning|description@@home.hello">Hello World!</h1>

Beide Informationen sind optional und werden mit einer Pipe voneinander getrennt. Ebenso optional ist die ID, die nach zwei @-Symbolen erscheinen kann. Fehlt diese ID, erzeugt Angular selbst eine. Das ist jedoch problematisch, denn wenn sich das Markup ändert, vergibt Angular einen neuen Wert dafür.

Nachdem alle Texte mit i18n markiert wurden, extrahiert der Befehl ng xi18n sie in eine XML-Datei. Diese ist pro Sprache zu kopieren und um entsprechende Übersetzungen zu ergänzen (Listing 3).

Listing 3
1
2
3
4
5
6
7
8
9
10
<trans-unit id="home.hello" datatype="html">
  <source>Hello World!</source>
  <target>Hallo Welt!</target>
  <context-group purpose="location">
    <context context-type="sourcefile">src/app/app.component.html</context>
    <context context-type="linenumber">1</context>
  </context-group>
  <note priority="1" from="description">description</note>
  <note priority="1" from="meaning">meaning</note>
</trans-unit>

Standardmäßig liegen diese Dateien im XLF-(XML-Localization-Interchange-File-)Format vor. Alternativ dazu lässt sich das CLI anweisen, stattdessen XLF2 oder XMB (XML Message Bundles) zu nutzen. Diese Formate werden häufig von Übersetzungsstudios unterstützt.

Die übersetzten Dateien sind danach in der angular.json zu registrieren (Listing 4).

Listing 4
1
2
3
4
5
6
7
"i18n": {
  "locales": {
    "de": "messages.de.xlf",
    "fr": "messages.fr.xlf"
  },
  "sourceLocale": "en-US"
},

Mit ein paar weiteren Eigenschaften lässt sich auch das gewählte Format festlegen, sofern vom Standardformat XLF abgewichen wurde. Außerdem ist hier das Locale, in dem die Templates vor der Übersetzung vorliegen, als sourceLocale einzutragen.

Die Anweisung ng build –localize erzeugt nun eine Version pro Locale (Abb. 3). Wie erwähnt, lässt sich diese Aufgabe verhältnismäßig schnell erledigen, weil das CLI im Gegensatz zu früher nur ein einziges Mal kompiliert und dann nur noch die Texte tauscht. Startet man nun einen Webserver im Verzeichnis dist/projektname, lässt sich eine der Sprachversionen durch Anhängen des jeweiligen Locales an den URL wählen.

Abb. 3: Sprachversionen im dist-Ordner

Abb. 3: Sprachversionen im dist-Ordner

Das neue @angular/localize erlaubt jedoch auch die Nutzung der Übersetzungstexte zur Laufzeit. Dazu sind sogenannte Tagged Template Strings zu nutzen:

1
title = $localize`:@@home.hello:Hello World!`;

Als Tag kommt hier der globale Bezeichner $localize zum Einsatz. Außerdem wird der String mit der ID des jeweiligen Texts eingeleitet. Diese befindet sich zwischen zwei Doppelpunkten. Danach kommt der Standardwert. Derzeit extrahiert ng xi18n solche Texte zwar noch nicht automatisch, aber das hält uns nicht davon ab, bestehende Einträge auf diese Weise zu nutzen oder neue manuell in die XML-Dateien einzutragen.

Eine weitere sehnlich erwartete Möglichkeit ist das Festlegen der Übersetzungstexte zur Laufzeit. Um diese Runtime Translations zu unterstützen, sind sie lediglich in Form von Key/Value-Pairs festzulegen:

1
2
3
4
5
import { loadTranslations } from '@angular/localize';
loadTranslations({
  'home.hello': 'Küss die Hand!'
});

Allerdings muss das vor dem Laden von Angular erfolgen, weswegen diese Codestrecke in ein eigenes Bundle auszulagern ist. Aus diesem Grund weist das beiliegende Beispiel diesen Aufruf in der polyfills.ts auf. Außerdem muss sich hier die Anwendung selbst ums Laden der richtigen Metadaten für die Formatierung von Zahlen und Datumswerten kümmern.

ABTAUCHEN IM DEEP DIVE

Im Fortgeschrittenen Camp tauchen Sie ab unter die Oberfläche einer modernen Angular-Anwendung.
→ Nächster Termin: 2. - 4. Dezember, Berlin

Any und platform

Die mit Version 6 eingeführten Treeshakable Providers machen die Arbeit mit Services um einiges einfacher. Version 9 setzt da noch einen drauf, indem es zwei weitere Werte für providedIn festlegt: any und plattform:

1
2
3
4
5
@Injectable({ providedIn: 'any' })
export class LoggerConfig {
  loggerName = 'Default';
  enableDebug = true;
}

Die Einstellung any bewirkt, dass jeder Scope auf Modulebene eine eigene Service-Instanz erhält. Das bedeutet, dass es für alle lazy-Module eine eigene Instanz gibt sowie eine weitere für die Gesamtheit der restlichen Nicht-lazy-Module. Scopes auf Komponentenebene betrifft das nicht.

Hierdurch lässt sich die Notwendigkeit für forRoot und vor allem forChild-Methoden reduzieren. Letztere haben jedoch nach wie vor den Vorteil, dass sie auch Konfigurationsparameter entgegennehmen können. Beim Einsatz von any muss die Anwendung diese auf andere Weise festlegen, z. B. im Konstruktor des jeweiligen Modules (Listing 5).

Listing 5
1
2
3
4
5
6
7
@NgModule({ […] })
export class FlightBookingModule {
  constructor(private loggerConfig: LoggerConfig) {
    loggerConfig.loggerName = 'FlightBooking';
    loggerConfig.enableDebug = false;
  }
}

Die ebenfalls ergänzte Einstellung platform registriert einen Service im Platform-Injektor. Dieser befindet sich eine Ebene über root und beherbergt Angular-interne Services.

Dev-Server für Server-side Rendering

Wenngleich auch Server-side Rendering (SSR) sicher kein Thema ist, das jedes Projekt benötigt, ist es dennoch für das Angular-Team strategisch wichtig. Es erlaubt nämlich, mit Angular in den Bereich öffentlicher, SEO-kritischer Seiten vorzudringen. Bis jetzt war das Entwickeln solcher Lösungen jedoch lästig, zumal der Entwicklungswebserver nur die browserbasierte und nicht die serverbasierte Version des Projekts nach einer Änderung neu erstellte. Um auch die Auswirkung auf den serverseitigen Betrieb zu testen, musste man einen neuen Build erzeugen.

Damit ist nun Schluss, denn für SSR steht ein Builder zur Verfügung, der beide Versionen neu erstellt und daraufhin das Browserfenster aktualisiert. Wir sehen somit sofort alle Auswirkungen unserer Änderungen.

Um in den Genuss dieser Lösung zu kommen, ist nach dem Hinzufügen des entsprechenden @nguniversal-Pakets lediglich das npm-Skript dev:ssr zu starten:

1
2
ng add @nguniversal/express-engine@next
npm run dev:ssr

Da hier zwei Builds parallel stattfinden müssen, ist die Performance nicht ganz so gut wie beim klassischen Development-Server. Nichtsdestotrotz verbessert es das Entwicklererlebnis erheblich gegenüber dem Status quo.

BRINGEN SIE LICHT INS ANGULAR-DUNKEL

Die ersten Schritte in Angular geht man am besten im Basic Camp.
→ Nächster Termin: 9. - 11. Dezember, online

Fazit und Ausblick

Mit Angular 9 bekommen wir das lang ersehnte Ivy. Dank seines durchdachten Aufbaus macht es Angular selbst besser treeshakable und führt somit in vielen Fällen zu deutlich kleineren Bundles. Außerdem ermöglicht es Lazy Loading von Komponenten.

Um Ivy zu unterstützen, musste auch die integrierte I18N-Lösung überarbeitet werden. Das hat das Angular-Team zum Anlass genommen, die Implementierung zu verbessern: Der Build-Vorgang wurde drastisch beschleunigt, und wir können Übersetzungstexte programmatisch bereitstellen, aber auch konsumieren. Daneben erleichtert die Einstellung any für providedIn das Konfigurieren von Bibliotheken, indem es dafür sorgt, dass jedes lazy-Modul eine eigene Instanz erhält. Es kann eine Alternative zu forRoot und + darstellen und ist Mosaiksteinchen bei Bestrebungen, das Angular-Modulsystem optional zu gestalten.

Auch wenn sich das alles sehr aufregend anhört, wird es erst nach Angular 9 so richtig spannend. Denn Ivy bietet Potenzial für viele Neuerungen, denen sich das Angular-Team nun widmen kann.

The post Angular 9 ist da: Ivy, Lazy Loading und mehr appeared first on Angular Camp.

]]>