Überblick über moderne (JavaScript-)Frontend-Architektur

Herbstcampus 2017

Oliver Zeigermann / @DJCordhose

Folien: http://bit.ly/architektur-herbstcampus

@igrigorik : https://twitter.com/igrigorik/status/891901115045785600

Grundproblem bei einer komplexen Web-Anwendung

Beste UI/UX beißt sich mit Anforderungen an Wartbarkeit

Was macht Frontend-Architektur schwierig?

Das "Problem" ist der Benutzer!

  • alles soll wirken wie aus einem Guss
    • einheitliches Layout (UI)
    • einheitliches Bedienkonzept (UX)
  • reaktionsschnelles UI, auch unter Last
  • Anzeige soll jederzeit über alle Komponenten konsistent sein
  • alles von Anfang an da, ohne Verzögerung

Wo ist das Problem? Das ist doch einfach oder?

Prominente Gegenbeispiele

  • Facebook, LinkedIn, XING: Alle kämpfen mit uneinheitlichem Anzeigestand
  • Gmail: Ladebalken am Anfang, langsamere erste Page Impression
  • Amazon: uneinheitliches Layout und Bedienkonzept
  • Viele seiten von Behörden/Ämtern/Großkonzernen etc.: zähes UI, validieren der Eingaben erst nach Submit

Sind die alle unfähig?

Oder liegt das eher an Konflikten mit anderen typischen Anforderungen?

Natürlich soll das alles auch

  • schnell und kostengünstig entwickelt werden
  • über Jahrzehnte wartbar sein
  • als Komponenten oder Teilprojekte entwickelbar sein
  • eine einheitliche Architektur haben
  • zum Skillset der vorhandenen Entwickler passen

Klassiche Webanwendung

jeder Boot tut gut, jeder Roundtrip setzt Zustand zurück

Copyright 2016, embarc

Klassichen Webanwendungen

  • Browser sendet HTTP-Request und empfängt HTML
  • Content wird auf der Server-Seite gerendert
  • Jede Interaktion wiederholt diesen Zyklus

Klassische Webanwendung: Oft ein nahe liegender Ansatz

  • Klassische Webanwendung sind für viele Enterprise-Entwicklungen der natürliche Ausgangspunkt
  • Server-Seitige Entwicklung mit Java/C# und HTTP und HTML sind gut verstanden
  • kleinere UI-Änderungen können auf Client-Seite mit JavaScript-Schnippseln (jQuery) realisiert werden
  • häufig ist dies völlig ausreichend

Grenzen

  • UI/UX ist grunsätzlich eingeschränkt
  • selbst in vermeintlich klassischen Anwendungen verstecken sich SPAs oft als Teile
  • z.B. JSF Komponenten mit einem komplexen UI
  • Vermischung von client- und serverseitigem Rendering macht Anwendungsarchitektur und Programmiermodell abenteuerlich

Perfide, da zuerst die Aufwände zur Umsetzung ansteigen und der Code immer unübersichtlicher wird.

Was geht nicht mehr als Klassische Web Anwendung

Ein Beispiel

Gar nicht mal so komplex...

Nächster Schritt

Single-Page Application

Überblick: Single-Page Application

Copyright 2016, embarc

Single-Page Applications (SPAs) verschieben eure Anwendung in den Browser

  • eine einzige Seite pro Anwendung/Modul
  • läuft ohne signifikanten Server-Anteil
  • kann auch als statische Web-Seite laufen
  • ermöglicht Offline-Betrieb
  • Bedienbarkeit wie Desktop
  • Browser bietet mächtige Speicherungsmöglichkeiten

Komponenten basierte Ansätze mit Templates

  • Templates nun auf der Clientseite
  • Anwendung bekommt Struktur
  • Logik hängt an Komponenten, ist keine lose Schnippsel-Sammlung wie bei jQuery

Findet sich wieder in React, Angular, Vue und Web Components

Smart and Dumb Components

Klares Architektur Pattern

Heißt auch Mediator Pattern bei Web Components / Polymer

Smart Components

  • Verwalten Teilzustand der Anwendung
  • Reichen Teile des Zustands als unveränderliche Daten an Unterkomponente weiter
  • Enthalten UI Logik, die sie als Callbacks an ihre Unterkomponenten weiter geben können
  • Meist spezifisch für eine Domäne
  • Nicht außerhalb der Domäne wiederverwendbar

Dumb Components

  • Managen höchstens transienten State
  • enthalten keine Logik
  • Unterkomponente sind meist selbst nur Dumb Components (es gibt Ausnahmen)
  • haben kein Wissen oder Abhängigkeit zu Oberkomponenten
  • wiederverwendbar

Copyright 2017, Nils Hartmann

Code Sample Angular

Smart Component


@Component({
  template: `<sub greeting={{greeting}} (onSend)="sent($event)">`
})
export class AppComponent {
  // component state
  private greeting: string = 'Hiho';

  constructor(private greetingService: GreetingService) {
  }

  // "Business Logic" delegated to service
  sent(greeting) {
    this.greeting = this.greetingService.greetBack(greeting);
  }
}
            

Dumb Component


@Component({
  selector: 'sub',
  template: `

{{greeting}}, World

`, }) export class SubComponent { @Input() greeting: string; // rxjs observer @Output() onSend = new EventEmitter(); // no business logic, just event emitting send() { this.onSend.emit(this.greeting); } }

Grenzen

Besonders bei wachsenden und langlebigen Anwendungen

  • Tendenz zu "Gottkomponenten": Zustand und Logik wandern langsam nach oben in eine einzige Komponente
  • Vermischung von Framework und UI-Logik (erschwert Austausch das Frameworks)
  • Verteilter, veränderlicher Zustand erschwert Wartbarkeit
    • Zustand oft nicht klar zuzuordnen
    • In welchem Zustand ist die Anwendung?
  • Architektur immer noch unklar
    • Wo ist Nebenläufigkeit erlaubt?
    • Wie läuft die Initialisierungsphase
    • Wie testet man die Business Logik?

Nächster Schritt

Redux als Architektur-Muster

Redux

Zustand wird zentral gehalten, UI-Logik geht aus den Komponenten

Selbst kein Framework, sondern nur ein Muster (wie z.B. MVC)

Redux ist unabhängig vom Framework

Implementierungen existieren für alle wichtigen UI-Frameworks

Resultierende Architektur anhand des Redux Musters

Redux extrahiert Verwantwortlichkeiten aus UI-Framework

Code Sample: Reducer

Nur eine Funktion, daher unabhängig vom UI Framework


type Greeting = {
	greeting: string;
	name: string;
}
            

type SaveGreetingAction = {
	type: 'ADD_GREETING',
	greeting: Greeting
}
            

function greetingsReducer(state: Greeting[] = [],
                          action: SaveGreetingAction) {
    switch (action.type) {
        case 'ADD_GREETING':
            // immutable operation, creating new state
            return [...state, action.greeting];
        default:
            return state;
    }
}
            

Code Sample: Action Creator

Ebenso unabhängig vom UI Framework


async function loadGreetings(dispatch) {
    try {
        const response = await fetch(BACKEND_URL);
        const json = await response.json();
        dispatch({
            type: SET_GREETINGS,
            greetings: json
        });
    } catch (err) {
        console.error('LOADING GREETINGS FAILED:', err)
    }
}
            

Wieder nur eine Funktion, der einzige Ort an dem asynchrone Operationen erlaubt sind

Ist das nur eine spinnige Idee, oder nutzt das echt jemand?

Zusammenfassung Redux

  • Ein Architektur-Muster für UIs
  • Kontroll-/Datenfluss in eine Richtung
  • Zustand wandert aus Smart Components in zentralen State
  • UI-Logik wandert aus Smart Components in Action-Creators und Reducer
  • Nebenläufigkeit nur in Action-Creators
  • State ist zentral und immutable
  • Business Logik ausschließlich in puren Funktionen - Reducer (beste Testbarkeit)
  • Nur Reducer dürfen State verändern
  • Initialisierung durch initiale Aktion

Grenzen: SPA Ansatz selbst

  • SEO
  • First-Page-Impressions
  • Preview, z.B.
    • bei der Vorschau von Suchergebnissen
    • oder dem Teilen von Links durch Social Media

SPA: First-Page-Impression

Selbst für eine einfache Web Application kann das Laden und das Aufbauen der Anwendung einige Zeit kosten und was vom Sever kommt ist eine leere Seite

Nächster Schritt: Universal Web Apps

Universal Rendering: First-Page-Impression

Universal Rendering

  • First-Page-Impression wird auf dem Server gerendert
  • Links werden als normale HTML-Links in die Seite gerendert
  • Beliebig viele andere Seiten werden ebenfalls statisch gerendert
  • Läuft dann (zumindest zum Teil) auch ohne JavaScript
  • Code fast 100% geteilt zwischen Client und Server
  • serverseitiges Rendering mit https://nodejs.org
  • Neben Markup liefert der Browser ebenso Zustand
  • Wird von JS-Frameworks React, Angular und Vue unterstützt

Herausforderungen / Schwächen

  • Aller Zustand muss beim serverseitigen Rendering komplett vorliegen (oder man rendert statische Platzhalter)
  • Falls First Page Impression ungültig: Flackern bei Neurendering im Client
    • Unterschiedliche Locales auf Server und Client
    • Zeitliche veränderliche Daten (Timestamp, Börsenkurs)
  • Auch auf dem Server muss JavaScript laufen (zumindest für das Rendering Layer)
  • Unklar, ob/wie das mit Web Components - besonders Shadow Dom - funktionieren könnte (headless chrome?, https://twitter.com/DJCordhose/status/900328374601740288)
  • Hilft nur bei "Time to first Meaningful Paint", nicht bei "Time to Interactive"

Zusammenfassung

Was ist mit meiner guten alten Web-Anwendung passiert?

Ist das nicht alles zu kompliziert?

  • Klassische, serverseitig gerenderte Anwendungen, sind häufig wirklich einfacher und besser modularisierbar
  • Hohe Anforderungen an UX und UI sind allerdings nicht mit sinnvollem Aufwand mit klassischen Web-Anwendungen umsetzbar
  • die Komplexitäten einer Single Page App entspringen aus diesen Anforderungen an UX und UI
  • Das Grundproblem ist der Widerspruch zwischen ganzheitlicher UX und Aufteilung in Komponenten / Module / Teams

Bonus Level

Legenden und Mythen

Wir sammeln

Was für potentielle(!) Mythen im Bereich Frontend-Architektur kennt ihr?

- React ist besser als Angular

Legenden und Mythen #1: Single Page Applications (SPA)

  • In SPAs kann ich keine Links teilen und auch nicht sinnvoll verlinken: Link wird im Browser aufgelöst und geroutet
  • Back-Button geht nicht: siehe oben
  • SPA hat immer eine schlechte First Page Impression: Universal App oder Code-Splitting hilft , Web Components brauchen nicht einmal Framework => weniger Code , Service Workers und HTTP/2 Server Push
  • SPA ist unstrukturierter Monolith: Trennung, Isolation in Module und Teams ist mit gezeigten Ansätzen möglich
  • HTML/HTTP sind langlebig, Investition in SPA-Frameworks nicht lohnend, veraltet innerhalb einer Woche: Business relevante Teile außerhalb des SPA-Webframeworks

Legenden und Mythen #2: JavaScript

Ist JavaScript-Code nicht unwartbar?

  • keine Lesbarkeit: moderner JavaScript-Code ist auf Python-Niveau, JSDoc ist Standard
  • keine Analysierbarkeit: wird durch zusätzliche mächtige Typensysteme TypeScript oder Flow erlaubt
  • kein Refactoring: folgt aus Analysierbarkeit
  • keine Testbarkeit: gezeigte Ansätze erlauben allerbeste Testbarkeit aller Teile
  • keine Wiederverwendung: Dumb Componentes jederzeit wiederverwendbar, bei Smart Components nicht sinnvoll
  • keine Möglichkeit zur Strukturieren: Das moderne JavaScript Modul-System ist mächtig und erlaubt Strukturierung wie in Java

Legenden und Mythen #3: Integration mit Microservices geht nicht mit Client Side Redering

  • Integration im Browser in eine Anwendung
    • Anwendung ist in logische Module aufgeteilt, die als ganze Anwendung zusammen laufen
    • Pro Seite eine SPA, kann Komponenten aus andern Modulen integrieten
    • "Echt" getrennte Anwendung über iFrame und EventBus
  • Integration mehrerer Anwendungen über Links, die eine neue Anwendung öffnen und die alte ersetzen
    • jedes Modul ist eine eigene SPA Anwendung
    • erlaubt Vertikalen wie eine klassische Web-App
    • Allerdings: Modul-Wechsel setzt Zustand zurück wenn nicht explizit übertragen

The Doctor is In

ArchitekturMythosUI / UX
20 Muss man für SPAs und Universal Apps JavaScript programmieren? Warum soll man überhaupt JavaScript machen? Geht das nicht alles auch mit CSS?
40 Sollte ich nicht jede SPA als Universal App schreiben? Widersprechen SPAs nicht der Grundidee es Webs? Wie realisiert man komplexe UI-Komponenten (ala TreeGrid)?
60 Risiko: Frage eurer Wahl Risiko: Frage eurer Wahl Risiko: Frage eurer Wahl
80 Wieso funktioniert für SPAs kein Schichtenmodell? Wie schneide ich Module in einer SPA? Geht das mit DDD Aggregate Root? Wann passt eine klassische WebApp besser als eine SPA?
100 Was kann man retten, wenn man das UI-Framework wechselt? Was ist denn eigentlich mit Progressive Web Apps (PWA)? Muss ich immer wählen zwischen Frontend-Monolithen oder UX Katastrophe?

Oliver Zeigermann / @DJCordhose, http://bit.ly/architektur-herbstcampus