Klassischer Architekturansatz für Webanwendungen

JavaScript? Gern, aber bitte in Maßen

JavaScript? Gern, aber bitte in Maßen

Klassischer Architekturansatz für Webanwendungen

JavaScript? Gern, aber bitte in Maßen


Eine moderne Webanwendung wird natürlich in JavaScript implementiert und erzeugt ihr HTML clientseitig im Browser selbst. Sie kommuniziert mit dem Server nur, um über ein HTTP/REST API Daten im JSON-Format abzuholen – das, so scheint es, ist die gängige Weisheit. Aber haben die bewährten Ansätze wie serverseitiges HTML und Progressive Enhancement tatsächlich ausgedient? Im Gegenteil, damit lassen sich Anwendungen realisieren, die oft sehr viel besser sind als die mit dem Framework der Woche realisierte Single Page App.

Sehen wir uns zunächst an, welche Probleme es bei SPAs gibt. Bei der traditionellen Trennung zwischen Server und Client liegen auf der Clientseite lediglich Aussehen und Verhalten. Zustand, Geschäftslogik, Routing sowie Präsentationslogik und Templating sind ausschließlich auf dem Server zu finden (Abb. 1).

tilkov_javascript_1.tif_fmt1.jpgAbb. 1: Traditionelle Trennung zwischen Server und Client

Mit dem Ziel, eine App zu bauen, die schneller reagiert und vielleicht auch offlinefähig ist, wird nun oftmals Verantwortung vom Server auf den Client verschoben. In vielen Unternehmen ist ein zusätzlicher Anreiz, Abteilungen stärker zu trennen. Backend-Experten beispielsweise stellen nur ein API bereit und müssen sich um HTML/CSS/JS nicht kümmern. Zudem kursiert der Irrglaube, dass JSON kleiner sei als HTML. Jon Moore hat in einem Artikel erklärt, wieso das nicht stimmt [1]. Häufig werden die View-Logik sowie das Templating ins Frontend verschoben. Das passiert beispielsweise mit Technologien wie React oder Vue.js (Abb. 2).

tilkov_javascript_2.tif_fmt1.jpgAbb. 2: View-Logik und Templating werden ins Frontend verschoben

Ein weiterer Schritt ist oftmals, das Routing ebenfalls in den Client zu verlagern. In React zum Beispiel mit React Router, in Vue.js mit Vue Router. In Frameworks wie Angular oder Ember ist das Routing ebenfalls im Client angesiedelt (Abb. 3).

tilkov_javascript_3.tif_fmt1.jpgAbb. 3: Das Routing wandert in den Client

Auf den ersten Blick wirkt diese Verschiebung der Verantwortlichkeiten von Server zu Client nur wie das Versetzen einer Grenze. Tatsächlich ist es aber so, dass das Verschieben dieser Linie weitere Folgen mit sich bringt.

Vorher haben wir HTML im Client interpretiert – eine Funktionalität, die der Browser mit sich bringt. Nun müssen wir einen JSON-Client sowie ein API zur Verfügung stellen. Das ist auch eine Indirektion: In der Präsentationslogik greifen wir nun nicht mehr direkt auf die Geschäftslogik und den Zustand zu, sondern müssen eine Transformation in JSON und zurück durchführen. Gerade wenn Server und Client von separaten Teams entwickelt werden, erhöht sich hier der Koordinations- und Kommunikationsaufwand.

Wenn wir Geschäftslogik und Zustand vollständig auf dem Server belassen, bleibt die Menge der Kommunikation gleich bzw. erhöht sich in vielen Fällen im Vergleich zum Server-Rendered-Modell. Bewegen wir einen Teil der Geschäftslogik auf den Client, müssen wir nun stets entscheiden, welche Geschäftslogik auf dem Server, welche auf dem Client und welche an beiden Orten vorhanden sein muss. Dabei müssen wir beachten, dass der Client keine vertrauenswürdige Umgebung ist. Entsprechend müssen wir also viele Dinge auf dem Server prüfen. GraphQL ist eine Technologie, die eine solche Nutzung des Backends als reine „Datenbank“ zu ermöglichen versucht.

Auch der Zustand kann nicht mehr vollständig auf dem Server (Datenbank/Session/…) bleiben, wir müssen den Zustand im Client halten. Das realisieren wir mit Lösungen wie LocalStorage/PouchDB, aber auch mit Technologien wie Redux, Flux, Reflux und Vuex. Hier müssen wir ebenfalls entscheiden, welche Daten auf dem Client, welche auf dem Server und welche an beiden Orten abgelegt werden. Dabei müssen wir über (Multi-Leader-)Synchronisierung nachdenken: Da Daten auf mehreren Clients gleichzeitig bearbeitet werden, die teilweise auch längere Zeit offline sein können, kommt es zwangsläufig zu Schreibkonflikten, die es zu lösen gilt.

Zudem bedeutet diese Verschiebung, dass wir mehr JavaScript-Code auf dem Client ausführen müssen. Die Zeit, die der Browser braucht, den JavaScript-Code zu parsen und anschließend auszuführen, unterschätzen viele. Das führt auch dazu, dass JavaScript-lastige Seiten eine längere Startzeit haben. Um das zu umgehen, bieten die meisten Frameworks die Möglichkeit der Hydration: Die Anwendung wird auf dem Server mit Hilfe von Node gerendert und als HTML ausgeliefert. Wenn das JS fertig geladen ist, übernimmt es das Markup und wird ab diesem Zeitpunkt clientseitig gerendert.

Ein weiterer Grund dafür ist SEO: Da JS weiterhin nicht zuverlässig von Suchmaschinen ausgewertet wird, ist es sinnvoll, Inhalte auch als HTML anzubinden. Für die Hydration fügen wir neben weiterer Komplexität auf dem Client also auch Infrastruktur hinzu.

Ausführung unter widrigen Umständen

Browser sind zudem eine widrigere Ausführungsumgebung als unser Server, was wir immer beachten müssen, wenn wir mehr Code in den Browser verschieben. Ein Punkt ist beispielweise die geringere Beobachtbarkeit. Auf dem Server können wir Logs erfassen sowie Exceptions abfangen und loggen. Das geht auf dem Client ebenfalls, ist aber wesentlich komplexer und fehleranfälliger...