State im Frontend

Diesen Beitrag gibt es auch als Podcast Folge:

In dem heutigen Artikel geht es um den State im Frontend. Dabei wollen wir einmal das Setting

annehmen, dass wir eine einfache Webanwendung vorliegen haben, bei der das Frontend eine Angular Anwendung ist, welches per REST mit einem Spring Boot Backend kommuniziert.

Mit dem State ist der Zustand der Applikation gemeint, also der Datenzustand zur Laufzeit der Anwendung. Offensichtlich ist, dass die Backend Anwendung mit ihrer Datenbank einen State hat, nämlich genau den Zustand der Datenbank zur Laufzeit.


Aber auch das Frontend hat einen State. Es ist überraschend wie unklar dieser Fakt in vielen Projekten selbst erfahrenen Entwickl

ern zu sein scheint. Nachfolgend seien zwei Beispiele von Features genannt welche zwingend einen State im Frontend erfordern.


Beispiel: Das Formular

Nehmen wir als ein weit verbreitetes Feature ein einfaches Formular einer Webanwendung an – also eine Gruppe von Eingabefeldern, wie beispielsweise den Namen, die Email und eine Nachricht in einem Kontaktformular. Gibt der User nun diese Eingaben in die Eingabefelder ein, so hat das Frontend einen State zum Zeitpunkt der Eingabe. Dieser State liegt nur indirekt im Browser und unserem Arbeitsspeicher vor. Richtig relevant wird dies jedoch dann, wenn zu unserem Formular das Feature hinzukommen soll, einmal eingegebene Daten während der Session zu behalten. Das heißt man kann den Kontext der Eingabe verlassen, vielleicht etwas innerhalb der Anwendung nachschauen und wieder zur Eingabe zurückkehren, ohne dass die Eingaben verloren wären. Bei diesem Feature stellt sich die Frage, wo der Zustand der Eingabe zwischengespeichert werden soll.


Beispiel 2: Die Tabelle

Ein weiteres Beispiel ist eine Tabelle. Tabellen haben üblicherweise neben der reinen Anzeige drei Standartfunktionalitäten: Die Sortierung, Filtrierung und die Pagination. Die ersten beiden sollten selbsterklärend sein - mit Pagination ist gemeint, dass es möglich ist die Daten der Tabelle auf einer Seite anzuzeigen, die Seitengröße zu wählen und zwischen den Seiten zu navigieren. Welche Filter der User gerade ausgewählt hat, welche Sortierung er gewählt hat und auf welcher Seite er sich gerade befindet ist ebenfalls der State des Frontends. Es ist genau wie im vorherigen Beispiel die Herausforderung, dass navigiert werden können muss, ohne dass der User noch einmal seine Filter, Sortierung und Seiten umständlich auswählen muss, nur weil er innerhalb der Anwendung navigiert hat.


Lösungsansätze

Beide Beispiele zeigen, dass für einfachste Funktionalitäten in einer modernen Webanwendung, bei der der Zustand der Session erhalten bleiben soll, die Frage des States im Frontend beantwortet werden muss. Bevor hier Lösungsansätze erklärt werden, sei gesagt, dass es in der Realität leider oft so ist, dass gar kein Konzept zum Statemanagement im Frontend vorliegt. Es wird oft ein großer Fokus auf Themen wie Datensicherheit und -konsistenz gelegt – jedoch nur im Backend. Wenn dann der Kunde die oben genannten Features wünscht, muss die bisherige Lösung stark erweitert und vielleicht sogar umgebaut werden. Da aber im Bewusstsein des Kunden, sowie von Product Ownern und Managern solche Features selbstverständlich erscheinen ist die Zeit dafür nicht da. Letztlich führt diese Zeitknappheit in Kombination von ungünstigen Release-Prozessen dann zu einem Aufbau von technischen Schulden. Solche Themen können sich zu starken Zeit-, Motivations- und Geldfressern entwickeln.

Deshalb seien hier vier grundsätzliche Herangehensweisen erklärt, mit denen sich im Vorhinein solche Probleme vermeiden lassen:


Alles ins Backend

Bei diesem Ansatz wird versucht den Zustand, den der User sieht in der Datenbank des Backends festzuhalten. Dies kann automatisch nach einer Eingabe geschehen, oder durch einen Speichern-Button. Beim Speichern wird dann das Formular, bzw. der Zustand der Tabelle serialisiert und an das Backend zur Speicherung geschickt.

Folgen

Session-Management

Dadurch ergibt sich, dass das Backend einen Mechanismus braucht, um die Daten zugehörig zum User und der Session des Users zu speichern, sowie auch zu verwalten wann diese Daten ablaufen. Außerdem muss ein Mechanismus programmiert werden, wann diese Daten geladen und vorausgefüllt werden und dies mit eventuellen vorausgefüllten Standartdaten abgeglichen werden. Da dies asynchron geschieht und es mehrere „Quellen der Wahrheit“ gibt erhöht dies die Komplexität.


Enge Kopplung

Frontend-State ins Backend zu schreiben bedeutet, dass das Backend und Frontend eng gekoppelt sind – schließlich muss das Backend nun das genaue Aussehen der View kennen. Was ist, wenn sich die View verändert? Dann muss nicht nur Frontend-Code, sondern auch Backend-Code, sowie eine Datenmigration stattfinden. Für eine eventuellen Anschluss eines anderen Consumers sind diese Daten nutzlos – wird dies also zusätzlich gewünscht sollte dieser Teil der API geschützt werden.


Frontend State-Management

State im Frontend zu managen erscheint also sinnvoll, vor allem da dieser implizit ohnehin vorliegt. Jede Eingabe, jeder geklickte Radio-Button oder Sucheingabe ist de facto State im Frontend, der implizit vorliegt. Dieser sollte also gemanaged werden.


Angular Component hält Daten

Startet man mit der Entwicklung einer Component in Angular so hält diese den Zustand der Component. Daran ist grundsätzlich nichts auszusetzen, es sei denn dieser Zustand wird anderswo benötigt.

Nehmen wir beispielsweise an, wir haben eine einfache Suchkomponente mit einem Suchtext als Eingabe, so könnte man diesen in der Component verwalten. Was ist jedoch, wenn bei einem bestimmten Suchtext eine ganz andere Component benachrichtigt werden soll um beispielsweise einen Text hervorzuheben? Bleibt man bei der Component, dann müsste dieser Text über einen EventEmitter in der Komponentenhierarchie durchgereicht werden. Dadurch ensteht viel Boilerplate-Code und bei der Wartung extreme Verwirrung, da unklarer ist wo genau die durchgereichten Daten überhaupt verwendet werden.

Außerdem führt die Verwendung von Komponenten-State in Angular dazu, dass der Datenfluss immer unklarer wird. Umso mehr Userinteraktionen und Events, oder sogar asynchrone Events in ein komplexes (und großes) Datenobjekt schreiben umso schwieriger wird überhaupt zu verstehen was passiert. Sowohl das Durchreichen durch viel zu viele Kompnenten-Ebenen, als auch er unklare Datenfluss sind Dinge, welche sehr häufig in Angular-Projekten anzutreffen sind.


Angular Service hält Daten

Ein Aspekt dieser Problematik kann gelöst werden, indem ein Stateful-Singleton-Service verwendet wird. Der Service existiert pro Anwendung nur einmal und fungiert als Datenspeicher (für die Session). Unterschiedliche Komponents können dann den Service über die DI von Angular einbinden und die Daten beschreiben.

Das Problem des unklaren Datenflusses bleibt jedoch bestehen und es kommt in der Praxis häufig vor, dass der Zustand des Services ebenfalls unklar ist. So kann es sein, dass bei der Programmierung übersehen wird einmal geschriebene Daten gegebenfalls zu löschen. Letztlich programmiert man damit so etwas wie einen Caching-Mechanismus, welcher erfahrungsgemäß diverse Konsistenzprobleme mit sich bringt.


Statemanagement-Frameworks

Eine professionellere Lösung ist die Verwendung eines State-Management Frameworks, wie beispielsweise Redux, oder im Angular Umfeld Derivate davon, wie ngXs oder ngRx. Da diese Frameworks relativ umfangreich sind, würde es den Rahmen sprengen diese vollständig zu beleuchten.

Sie basieren jedoch auf ähnlichen Prinzipien. Für die gesamte Anwendung wird ein Javascript-Objekts vorgehalten, welches als State dient. Die Manipulation dieses Objekts (also des States) geschieht ausschließlich durch die Verarbeitung sogenannter Actions. Dabei sendet die Komponente, beispielsweise unsere Suchkomponente SearchAction an das Statemanagement-Framework. Das Framework verarbeitet dann diese Action und packt die Sucheingabe, welche an der Action hängen, in den State – also das Javascript Objekt. Die Komponente welche sich für diese Änderung interessiert, kann sich dann als Observer genau dieses einen Feldes anmelden und wird benachrichtigt sobald sich diese ändert.

Der Zustand wird also immer nur in einem Schritt verändert, nämlich durch die Verarbeitung des aktuellen Zustands, sowie der Action mit den zugehörigen Daten. Dieser Ansatz ist also funktional – der neue Zustand ist eine Funktion welche den alten Zustand und die Action als Argumente entgegen nimmt.

Diese Formel der Berechnung eines neuen Zustands das Statemanagement so effizient. Es hat die Vorteile von funktionalen Ansätzen, bei der Datenfluss und die Konsistenz einfach gegeben ist. Außerdem lassen sich die Funktionen, welche State und Action entgegen nehmen, sehr einfach automatisiert testet - es sind pure Functions. Und zu guter Letzt bietet das Framework diverse Tools zum einfacheren Debuggen und Warten der Anwendung.

Damit haben wir anhand von Beispielen verschiedene Probleme und Ansätze den State im Frontend zu managen betrachtet und besprochen.


Zusammenfassung:

  • In modernen Web-Anwendungen muss dem State im Frontend Aufmerksamkeit geschenkt werden. In Backend und Frontend stellen sich dabei ähnliche Probleme.

  • Es ist wichtig diesbezüglich möglichst früh ein Konzept für das Statemanagement im Frontend für eine Anwendung zu entwerfen

  • Die vier vorgestellten Ansätze waren:

  • State im Backend, mit den Folgen:

  • Aufwändiges Session-Management

  • Enge Kopplung

  • Es existiert trotzdem implizit State im Frontend

  • State im Frontend

  • in der Component, mit den Folgen:

  • Umständliches Durchreichen von Daten mittels Events

  • Unklarer Datenfluss bei zu vielen Events und zu komplexem State

  • Singleton Service

  • Caching Probleme

  • Unklarer Datenfluss

  • Statemanagement mit einem Framework wie Redux

  • Single Source of Truth

  • tomare Veränderungen mit Pure Functions

  • Einfache Testbarkeit von Funktionen, welche den State verändern

  • Tooling Support


18 Ansichten0 Kommentare

Aktuelle Beiträge

Alle ansehen