Kolumne: EnterpriseTales

Von reaktiven UIs und expliziten Datenflüssen

Hendrik Müller

©Shutterstock / shutter_o

Moderne Webapplikationen sollen reaktiv sein. Zumindest propagieren das viele etablierte Web-Frameworks. Aber was bedeutet Reaktivität in diesem Kontext eigentlich genau? Warum sollte man sie einsetzen und inwiefern erleichtert sie dem Frontend-Entwickler das Leben?

Der Begriff „reactive“ oder Reaktivität hat sich mittlerweile fest im Umfeld der Softwareentwicklung etabliert. Doch in meinem Alltag habe ich nicht das Gefühl, dass jeder damit das Gleiche meint. In Summe fallen mir besonders drei unterschiedliche Auslegungen von Reaktivität auf. Zur ersten Gruppe, zu der ich mich auch zähle, gehören überwiegend Frontend-Entwickler. Sie verwenden den Begriff der Reaktivität vor allem im Zusammenhang mit funktional geprägten Programmierstilen und Frameworks. Auch die zweite Gruppe besteht aus Entwicklern, diese sind allerdings eher Backend-fokussiert. Sie verbinden mit Reaktivität das Verhalten ganzer Systeme im Sinne des Reaktiven Manifests [1] und seiner Prinzipien: antwortbereit, widerstandsfähig, elastisch und nachrichtenorientiert.

Die dritte und mit Abstand größte Gruppe assoziiert mit „reactive“ einfach nur ergonomische, intuitive Bedienbarkeit des UI. Insbesondere gehören hierzu direktes Feedback auf Eingaben und eine in sich konsistente Darstellung der Anwendung. Diese Eigenschaft wird nachfolgend als Responsiveness bzw. responsive bezeichnet.

Bezogen auf Frontend-Entwicklung sind sowohl die erste als auch die dritte Interpretation von Reaktivität relevant. Viele verbreitete Frontend Frameworks schreiben sich reaktive Programmierung auf die Fahne, und eine gute Responsiveness ist einer der Hauptgründe für den Einsatz solcher Frameworks.

Reactive, responsive oder beides?

Hat also Responsiveness etwas damit zu tun, ob die eingesetzte Technologie reaktiv ist? Eigentlich erstmal nicht. Es lassen sich auch mit jQuery oder purem JavaScript superergonomische Webseiten bauen, die allen Ansprüchen an User Experience (UX) gerecht werden. Aber der Einsatz von Frameworks wie React oder Angular vereinfacht es in der Regel, dieses Ziel zu erreichen. Vor allem wenn die Anwendung größer wird, helfen diese Frameworks bei der Strukturierung. Man kann also sagen: Reaktivität und Responsiveness bedingen sich zwar nicht, widersprechen sich aber auch nicht. Sie stehen orthogonal zueinander.

Reaktive UI-Programmierung

Die etablierten Frontend Frameworks brüsten sich damit, reaktiv zu sein. Hier ein paar Beispiele:

  • Svelte [2] ist „truly reactive“

  • React [3] trägt Reaktivität im Namen

  • Angular [4] hat eine „change detection“

  • Vue [5] hat ein „unobtrusive reactivity system“

Und was bedeutet das jetzt? Eigentlich nichts anderes, als dass das UI immer den aktuellen State der Applikation widerspiegelt. Denn das UI reagiert immer sofort auf Datenänderungen und bleibt dabei jederzeit in sich konsistent, zeigt also an allen relevanten Stellen die geänderten Daten an.

Aber das sollte ja eigentlich keine Besonderheit sein. Der Benutzer erwartet stets, dass das UI den Zustand der Applikation repräsentiert. Das als Entwickler zu realisieren, ist jedoch nicht so einfach. Wenn ein UI rein imperativ umgesetzt wird, muss sich der Entwickler quasi per Hand darum kümmern, dass auf alle Wertänderungen, die anzeigerelevant sind, richtig reagiert wird. Gegebenenfalls müssen auch weitere Teile in der Applikation aktualisiert werden, die sich auch auf das UI auswirken. So ist es schwierig, den Überblick zu behalten, welche UI-Komponente direkt oder indirekt von welchen Daten abhängt. Generell ist manuelle Synchronisation von State sehr fehleranfällig. Es gibt überall lokal zwischengespeicherte Werte in den UI-Komponenten, und die Abhängigkeiten, woher diese Daten ursprünglich stammen, sind nicht mehr nachvollziehbar. So kann es leicht passieren, dass das, was im UI angezeigt wird, nicht mehr den tatsächlichen Zustand der Applikation widerspiegelt.

Eine mögliche Alternative zur imperativen Programmierung ist Reactive Programming. Wie der Name schon vermuten lässt, kommen wir dem reaktiven UI hier ein bisschen näher. Die Definition von Reactive Programming auf Wikipedia: „In computing, reactive programming is a declarative programming paradigm concerned with data streams and the propagation of change.“ [6] Der Teil von Reactive Programming, auf den sich eigentlich alle reaktiven UI Frameworks direkt beziehen, ist „the propagation of change“. Hinter diesem Ausdruck steckt nichts weiter, als dass Datenabhängigkeiten explizit gemacht werden. Wenn sich ein Wert innerhalb eines Programms ändert, werden auch alle anderen von ihm abgeleiteten Werte aktualisiert. Das beliebteste Beispiel hierfür ist Excel: Wenn man den Wert einer Zelle ändert, aktualisiert sich auch die abgeleitete Summe sofort. Bezogen auf das UI hat das zur Konsequenz, dass alle angezeigten Daten immer konsistent mit dem Zustand der Applikation sind.

In Reactive Programming wird der Datenfluss durch die Anwendung explizit modelliert. Hierbei wird das Konzept von Streams verwendet. In den meisten UI Frameworks gibt es jedoch keine Streams, sondern der Datenfluss wird durch die Hierarchie der UI-Komponenten beschrieben. Es gibt aber auch UI Frameworks, die direkt auf Streams als Kernkonzept bauen. Der vermutlich bekannteste Vertreter dieses Ansatzes ist Cycle.js [7].

Datenfluss durch den Komponentenbaum

Eigentlich sind alle reaktiven Frontend Frameworks komponentenbasiert. Doch wie spiegelt sich jetzt der Datenfluss durch die Komponenten wider? Das gesamte UI der Anwendung ist als Baum von Komponenten strukturiert. In Abbildung 1 ist schematisch ein solcher Komponentenbaum dargestellt. Die Pfeile zeigen dabei den Datenfluss. Von oben werden Daten nach unten an die Kindknoten weitergereicht. Idealerweise sollten die Kindknoten ihre Eltern nicht kennen und daher nicht direkt mit ihnen kommunizieren. Stattdessen übermitteln in einer sauberen Architektur die Kindknoten Änderungen an Eltern über Events. Durch das Einhalten dieser Prinzipien innerhalb des UI wird der Datenfluss explizit, was ein wichtiges Kriterium für reaktive Programmierung ist.

Abb. 1: Komponentenbaum – Data down, Events up

Abb. 1: Komponentenbaum – Data down, Events up

In den meisten Frameworks gibt es zusätzlich zum direkten Weiterreichen der Daten an die Kindknoten einen zusätzlichen Mechanismus, um Daten weiterzugeben, nämlich per Dependency Injection. Daten werden dann einem gesamten Teilbaum zur Verfügung gestellt. Das wird in React und Svelte als Context API bezeichnet. Abbildung 2 verdeutlicht Dependency Injection im Komponentenbaum. Component B spannt einen Kontext auf, und nicht nur alle direkten, sondern auch die indirekten Kindknoten – also insgesamt Component D, E und F – können auf die Daten des Kontexts zugreifen. Wichtig ist hierbei, dass auch die per Kontext bereitgestellten Daten bei Änderungen für ein Update sorgen und so ebenfalls ein Datenfluss explizit modelliert wird.

Abb. 2: Dependency Injection im Komponentenbaum

Abb. 2: Dependency Injection im Komponentenbaum

Wenn man sich eine einzelne Komponente genauer ansieht, wie schematisch in Abbildung 3 dargestellt, wird noch ein weiteres Detail deutlich: Komponenten können auch einen lokalen State haben. Es gibt zwei Möglichkeiten, wie Daten in eine Komponente gelangen können: entweder von oben durch den Elternknoten bzw. Kontext oder von unten per Event. Man kann zwei Arten von Events unterscheiden: die von den eigenen Kindkomponenten geworfenen Events und die Events, die direkt aus dem DOM stammen. Letztere sind in der Regel Benutzerinteraktionen. beispielsweise die Eingabe in ein Input-Feld oder ein Klick mit der Maus. Wenn Informationen aus einem Event weiterhin für die Komponente relevant sind, aber nicht für ihre Elternkomponente, muss sie sich diese Daten selbst merken. Genau für solche Anwendungsfälle benötigt man einen lokalen State.

Abb. 3: Komponente im Detail

Abb. 3: Komponente im Detail

Der Komponentenbaum und die Kommunikation unter den Komponenten ist der Schlüssel zur Reaktivität im UI. Um Propagation of Change als Kernkonzept konsequent umzusetzen, müssen die Datenflüsse explizit werden. Genau das passiert im Komponentenbaum. Es gibt nur zwei Möglichkeiten, den Zustand einer UI-Komponente zu beeinflussen: entweder die hinuntergereichten Daten ändern sich oder es wird ein Event geworfen. Die nach unten gereichten Daten können entweder aus der direkten Elternkomponente stammen oder ihren Ursprung höher im Baum haben, beispielsweise per Context API. Sowohl das Hinunterreichen als auch das Konsumieren der Daten ist explizit. Gleiches gilt für die Kommunikation nach oben über Events. Sowohl Werfen als auch Fangen der Events machen den Datenfluss explizit sichtbar. Ergänzend sei noch gesagt, dass ein Komponentenbaum natürlich nicht der einzige Weg ist, um reaktive UIs zu bauen, momentan ist es jedoch der etablierteste.

Fazit

Was die meisten Leute unter einem reaktiven UI verstehen, sind Responsiveness und eine generell gute UX. Frontend-Entwickler verstehen jedoch zumeist etwas ganz anderes darunter, nämlich eine Spielart von reaktiver Programmierung, um das UI zu implementieren. Man könnte einen Zusammenhang zwischen Responsiveness und reaktiven Frameworks sehen, aber eigentlich haben sie erst einmal nicht unmittelbar etwas miteinander zu tun. Im Kern reaktiver Programmierung geht es um Propagation of Change, also darum, dass der Datenfluss durch die Anwendung explizit modelliert ist. Änderungen am State können so niemals zu einem inkonsistenten Zustand mit dem UI führen. Das hört sich im ersten Augenblick selbstverständlich an, ist aber mit imperativer Programmierung gar nicht so leicht umzusetzen. Der reaktive Werkzeugkasten macht es einem da deutlich leichter.

Klassischerweise werden Datenflüsse direkt als Streams modelliert, doch die meisten reaktiven Frameworks weichen davon ab. Sie bauen einen Komponentenbaum auf und machen den Fluss der Daten auf diese Weise explizit. So sorgt der Komponentenbaum dafür, dass alle Teile des UI konsistente Daten haben. Im Prinzip sind sich React, Svelte, Vue oder Angular, was diesen Grundgedanken angeht, sehr ähnlich, auch wenn sie sich sonst in vielerlei Hinsicht unterscheiden. Hauptsache, die View ist in sich konsistent und spiegelt immer den aktuellen Zustand der Anwendung wider. Und das ist eben mit dem reaktiven Ansatz viel einfacher zu erreichen als ohne.

Links & Literatur

Geschrieben von
Hendrik Müller
Hendrik Müller
Hendrik Müller ist Enterprise Developer bei der OPEN KNOWLEDGE GmbH in Oldenburg. Mit dem Schwerpunkt auf Webtechnologien begleiten ihn momentan Angular und Web Components durch den Tag. Abseits davon beschäftigt er sich leidenschaftlich mit dem Design von Programmiersprachen.
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: