Im Juli 2013 veröffentlichte der Software-Unternehmer Jonas Bonér zusammen mit einigen Mitstreitern das Reactive Manifesto. Nur wenige Monate später war der Begriff schon in aller Programmierer Munde. Dabei ist das scheinbar neue Konzept schon weitaus älter.
Den Begriff Reactive Programming sollen David Harel und Amir Pnueli bereits 1985 geprägt haben [1]. In ihrer wissenschaftlichen Arbeit ging es um den Entwurf von Systemen, die ständig auf Eingaben oder Änderungen reagieren müssen. Im Laufe der folgenden Jahre änderte sich die Bedeutung leicht. Heute gilt eine Anwendung allgemein als reactive, wenn sie möglichst schnell auf eingegebene oder geänderte Daten reagiert.
Besonders wünschenswert ist das bei interaktiven Programmen oder Webanwendungen, bei der Analyse von großen Datenmengen oder in der Robotik – also immer dann, wenn ein Programm in wenigen Millisekunden eine Antwort liefern muss, unter hohen (Netzwerk-)Lasten leidet oder große Datenmengen verarbeiten soll.
Da diese Anforderungen immer mehr Anwendungen betreffen, verwundert es wenig, dass sich immer mehr Projekte und wissenschaftliche Arbeiten mit Reactive Programming befassen. Besonders weit verbreitet ist eine ebenso simple wie interessante Grundidee: Man stellt kurzerhand die Daten in den Mittelpunkt.
Immer im Fluss
Wenn ein Entwickler etwas berechnen möchte, muss er in den meisten Programmiersprachen das Ergebnis in einer neuen Variablen auffangen. Ein einfaches Beispiel wäre:
a = b + 2
Das Ergebnis der Berechnung ist in diesem Fall eine Zahl. Sie bleibt in der Regel auch dann in »a« gespeichert, wenn sich der Inhalt von »b« im späteren Programmverlauf ändert.
Reactive Programming fordert hingegen, dass die Variablen »a« und »b« voneinander abhängen: Sobald sich »b« ändert, aktualisiert sich auch »a« automatisch. In der Variablen »a« liegt damit stets der Wert von »b + 2« , egal welchen Wert »b« gerade besitzt. Ein Programmierer braucht nicht mehr zu überlegen, an welchen Stellen im Programm er die Berechnung erneut durchführen muss oder wo sich zuvor »b« geändert haben könnte. Dies reduziert nicht nur Fehlerquellen, im Idealfall reagiert die Anwendung auch flotter. Der Begriff Reactive lässt sich daher sowohl mit reaktionsfreudig als auch mit rückwirkend übersetzen.
Während sich Entwickler bei der herkömmlichen Programmierung auf den Kontrollfluss (also die Befehlsabfolge) konzentrieren, steht beim Reactive Programming meist der Datenfluss im Vordergrund.
Davon unabhängig bietet sich noch die parallele Verarbeitung an: Je mehr Prozessoren eine Anwendung einspannt, desto schneller kann sie ihre Berechnungen durchführen – so zumindest das Kalkül. Arbeiten die Komponenten einer Anwendung nebenläufig, lässt sie sich auch wesentlich einfacher in verteilten Systemen beziehungsweise in einer Cloud einsetzen.
Ebenfalls en vogue ist eine asynchrone Verarbeitung der Daten: Nachdem die Benutzeroberfläche die geänderten Daten an die Datenbank geschickt hat, wartet sie nicht erst auf eine Antwort, sondern steht sofort wieder für weitere Eingaben zur Verfügung.
Schnellsprecher
Auch wenn es beim Blick auf den Datenfluss nicht so aussieht: Reactive Programming funktioniert auch in bestehenden Sprachen. In der objektorientierten Programmierung kann man zum Beispiel mit dem Entwurfsmuster Observer Datenänderungen weiterreichen [2]. Diesen Weg geht etwa Microsoft mit seiner Bibliothek Rx, die Reactive Programming in der Dotnet-Welt nachrüsten soll (Abbildung 1, [3]). Eine Portierung auf Java trägt den Namen Rx Java und stammt von der Online-Videothek Netflix [4].
Auch für die meisten anderen Sprachen existieren entsprechende Bibliotheken oder Erweiterungen. Für Ruby gibt es beispielsweise Frappuccino [5], während Sodium das Umsetzen von Reactive Programming unter Java, Haskell und C++ ermöglicht [6]. Sofern – wie bei Rx – ein objektorientiertes Paradigma zugrunde liegt, spricht man auch von Object Oriented Reactive Programming (OORP). Besonders verbreitet ist Reactive Programming aber bei funktionalen Sprachen, wo es Functional Reactive Programming (FRP) heißt.
Mittlerweile sind daneben Programmiersprachen entstanden, die ihre Erfinder von Anfang an auf Reactive Programming zugeschnitten haben. Hierzu zählt die recht junge funktionale Programmiersprache Elm, die das bewährte Javascript ersetzen möchte (siehe Abbildung 2 und den Kasten “Vernetzte Ulme”). Wer sich mit diesen bereits existierenden Lösungen beschäftigt, trifft auf einen Mix aus unterschiedlichen Begriffen, Konzepten und Umsetzungen.
Vernetzte Ulme
Die funktionale Programmiersprache Elm (Ulme) gehört zu den wenigen Sprachen, die ihre Erfinder von vornherein für Reactive Programming ausgelegt haben. Elm ist auf die Programmierung von Webanwendungen ausgerichtet, der zugehörige Compiler erzeugt HTML, CSS und Javascript [11]. Erfunden hat sie Evan Czaplicki im Rahmen seiner Abschlussarbeit an der Universität Harvard im Jahre 2011.
Elm bietet keine Variablen im herkömmlichen Sinn. Neben Konstanten wie
a = 2 + 4
gibt es noch so genannte Signale. Diese verhalten sich wie Variablen, die jedoch plötzlich ihren Wert ändern können. Ein Paradebeispiel dafür ist das Signal »Mouse.position« , das immer die aktuellen Koordinaten des Mauszeigers enthält. Bindet der Programmierer »Maus.position« in eine Berechnung ein, dann aktualisiert Elm nach einer Mausbewegung automatisch das Ergebnis.
Dieses Verhalten lässt sich gut veranschaulichen, indem man die Mausposition auf dem Bildschirm ausgibt. Der dazu notwendige Quellcode umfasst gerade mal zwei Zeilen:
import Mouse main = lift asText Mouse.position
»import« holt die Bibliothek mit Maus-Funktionen und Signalen hinzu. Anschließend steckt die Funktion »lift« die aktuellen Mauskoordinaten aus »Mouse.position« in die Funktion »asText« . Diese wiederum übersetzt den Zahlenwert in einen Text. Dieser Text wandert in die spezielle Konstante »main« , deren Inhalt später auf dem Bildschirm erscheint. Die Funktion »lift« ist notwendig, um eine normale Funktion auf das Signal anzuwenden. Elm bezeichnet diesen Vorgang als Lifting.
Das Ergebnis ist wieder ein Signal. »lift« selbst geht dabei davon aus, dass die Funktion (hier »asText« ) nur einen Parameter erwartet. Sollte die Funktion zwei Parameter benötigen, greift der Programmierer zu »lift2« , bei drei Parametern zu »lift3« und so weiter.
Ausprobieren kann man den Code direkt im Browser: Der Entwickler von Elm stellt auf seiner Homepage eine kleine Entwicklungsumgebung bereit [12]. Auf der linken Seite tippt man den Code ein, rechts erscheint nach einem Klick auf »Compile« (am unteren linken Fensterrand) umgehend das Ergebnis – oder eine Fehlermeldung.
Abgesehen vom Lifting erinnert Elm ein klein wenig an Haskell oder Coffeescript. Dank der mitgelieferten Bibliotheken lassen sich schnell Clientanwendungen im Browser umsetzen, die Signale prädestinieren Elm geradezu für den Bau interaktiver Benutzeroberflächen.
Im Gegensatz zu vielen anderen funktionalen Programmiersprachen ist der Wechsel von imperativen Sprachen recht einfach. Einen schnellen und leicht verständlichen Einstieg bietet die Onlinedokumentation [13]. Was mit Elm alles möglich ist, zeigt die Beispielsammlung [14]. Das Diagramm aus Abbildung 3 etwa lässt sich mit der Maus in Echtzeit verändern, ein komplettes Pong-Spiel benötigt gerade mal 100 Zeilen Code [15]. Allerdings gibt es derzeit noch keine ernsthafte Praxisanwendung, die in Elm implementiert wäre.
Genau das möchten Jonas Bonér und seine Co-Autoren mit ihrem “Reactive Manifesto” ändern ([7], [8]). Es definiert einheitliche Begriffe, die nicht nur die Zusammenarbeit unter den Entwickler-Communities verbessern, sondern es auch den Anwendern und Herstellern erleichtern sollen, miteinander zu reden und einander besser zu verstehen.
Dabei handelt Jonas Bonér nicht ganz ohne Eigennutz: Wie viele seiner Mitstreiter arbeitet er für die von ihm mitgegründete Firma Typesafe, die an einer Plattform für reaktive Programme bastelt (Abbildung 4, [9]). Dazu gehören die Programmiersprache Scala, die Laufzeitumgebung Akka, ein Framework namens Play und mit Activator ein Reactive Development Environment.
Gemäß Manifest gilt eine Anwendung als reaktiv, wenn sie umgehend auf einen Stimulus reagieren kann. Dazu muss sie wiederum ereignisgesteuert (Event-driven), skalierbar (scalable), robust (resilient) und jederzeit ansprechbar (responsive) sein. Diese vier Eigenschaften gelten zwingend für alle Bestandteile des Softwaresystems. So müssen etwa bei einer Webanwendung neben dem Client im Browser auch die Serverkomponenten schnell reagieren, andernfalls geriete das komplette System ins Stocken.
Staffellauf
Diese vier recht schwammigen Eigenschaften lassen die Autoren des Manifests nicht einfach so im Raum stehen, sondern konkretisieren sie. So dürfen die Bestandteile einer reaktiven Anwendung nur über Ereignisse miteinander kommunizieren (Message-Passing), die sie zudem asynchron senden und empfangen. Diese Zwangsmaßnahme stellt sicher, dass die Komponenten nebenläufig arbeiten und sich die Anwendung somit leichter skalieren lässt.
Gift sind dabei blockierenden Operationen. Erhält beispielsweise ein Thread exklusiven Zugriff auf eine Ressource, müssen andere Threads im schlimmsten Fall warten und stoppen so die weitere Ausführung der Anwendung. Um solche Situationen zu vermeiden, verbietet das Manifest blockierende Operationen, jegliche Synchronisation sowie Ressourcen in Beschlag zu nehmen.
Auf diese Weise soll das System ganz nebenbei unter hoher Last eine geringere Latenz und einen höheren Durchsatz erreichen. Um eine eventuell notwendige Synchronisation soll sich im Idealfall die Laufzeitumgebung kümmern. Entwickler beschäftigen sich nur noch damit, wie die einzelnen Komponenten einer Anwendung miteinander agieren.
Um zu verhindern, dass bei großer Last irgendwo Flaschenhälse entstehen, ist es zudem wichtig, dass das komplette System asynchron und nicht-blockierend arbeitet. Es muss also nicht nur das GUI mit asynchron gesendeten Ereignissen umgehen können, sondern auch die Middleware bis hin zur unterliegenden Datenbank. Die Autoren des Manifests fordern deshalb eine Reaktionsfreudigkeit von oben bis unten (“reactive from top to bottom”).
Zu guter Letzt lassen sich ereignisgesteuerte Systeme bequemer erweitern: Der Entwickler muss nur neue Ereignisse einführen und lässt die entsprechenden Komponenten darauf passend reagieren.
Anbau
Gemäß Manifest muss eine reaktive Anwendung auf zusätzliche Lasten flexibel reagieren und skalierbar sein. Reicht die vorhandene Rechenleistung nicht mehr aus, muss sie es schaffen, weitere Rechenknoten einzuspannen und sie bei wieder gesunkenem Bedarf freizugeben (“scale in or out”). Sollte dabei ein neuer Rechenknoten mit mehreren Prozessorkernen oder neuen Funktionen hinzukommen, muss die Anwendung in der Lage sein, dieses Angebot zu nutzen (“scale up or down”). Kurz: Die Anwendung soll sich ihrer Umgebung anpassen, idealerweise sogar automatisch. Diese Elastizität (“elasticity”) ist vor allem beim Betrieb in einer Cloud von Vorteil.
Transparenz
Die eigene Anwendung elastisch zu gestalten scheint aus Sicht der Manifest-Autoren simpel: Da die Bestandteile der Anwendung sowieso nur über Ereignisse miteinander kommunizieren dürfen, wäre es auch egal, auf welchem Rechenknoten eine Komponente läuft (“location transparency”). Das gilt insbesondere dann, wenn die Komponenten nur locker gekoppelt sind und möglichst unabhängig vor sich hin werkeln.
Die Kommunikation soll dabei nicht über die bekannten RPC-Mechanismen laufen. Stattdessen möchte das Manifest, dass der Entwickler das Internet umarmt (“embrace the network”), indem er es direkt im Programmiermodell durch ein asynchrones Message-Passing darstellt. Wie das genau gehen soll, verraten die Autoren aber nicht. Wer sich schon mal mit verteilten Systemen beschäftigt hat, kommt dabei wohl ins Grübeln.
Eine reaktive Applikation möge außerdem auf Störungen und Fehler reagieren, so eine weitere Forderung. Gemäß Manifest soll sich die Anwendung selbst samt den Daten wiederherstellen beziehungsweise reparieren. Hierzu müssen sich die Defekte beziehungsweise Fehler isolieren und die Bestandteile der Anwendung überwachen lassen.
Unkaputtbar
Das Manifest schlägt dazu das so genannte Bulkhead Pattern vor [10]. Wie bei einem Frachtschiff mit seinen Schotten im Rumpf teilt der Entwickler auch sein System in einzelne Abteile, wobei der Ausfall eines Abteils die anderen nicht mit den Tod reißt. Bei der Aufteilung hilft laut Manifest wieder das ereignisgesteuerte Modell: Fällt eine der unabhängigen Komponenten aus, lässt sie sich leicht neu starten beziehungsweise rasch durch einen Klon ersetzen.
Flink
Abschließend muss eine reaktive Anwendung einem Benutzer immer sofort antworten, egal wie hoch die Rechenlast gerade ist. Das macht die Arbeit mit der Anwendung nicht nur effizienter, Benutzer erhalten so auch das Gefühl, eine Aufgabe schneller zu lösen. Laut Manifest soll die Anwendung diese flotten Reaktionen durch Observal Models, Event Streams und Stateful Clients erreichen.
Mit Observal Models meint das Manifest schlicht den Einsatz des Observer-Entwurfsmusters: Spezielle Komponenten benachrichtigen andere, sobald sich irgendein Zustand ändert. Aktualisiert die Datenbank beispielsweise eine Adresse, erhalten alle Clients eine Benachrichtigung über diese Änderung.
Die Nachrichten laufen dabei über spezielle Kanäle, die Event Streams. Der Zugriff auf diese Streams erfolgt asynchron. Die Anwendung hat sicherzustellen, dass die Nachrichten sofort eintreffen. Das Manifest schlägt hierzu gleich mehrere Verfahren vor. Beispielsweise könnte man das System ständig überwachen und dann auf der Basis dieser Informationen eine Kapazitätsplanung durchführen.
Manifest und Praxis
Auch wenn ihn das Manifest nicht mehr explizit erwähnt, steht der Datenfluss auch dort im Mittelpunkt: Die Daten sind schlicht die asynchron verschickten Ereignisse. Allerdings geht das Manifest nicht auf die Rückkopplung ein. Ändert sich ein Wert, so braucht die Anwendung nicht alle abhängigen Werte neu zu berechnen. Sie informiert die betroffenen Komponenten lediglich über die Änderung des Werts.
Reactive Programming erfindet keine umwerfend neuen Programmierkonzepte. Stattdessen fasst der Begriff einige bekannte Konzepte zusammen. Diese erscheinen jedoch besonders im Hinblick auf Webanwendungen, mobile Geräte und Cloud Computing attraktiv. In den mittlerweile entstandenen Begriffswirrwarr versucht das Reactive Manifesto etwas Ordnung zu bringen. Damit scheinen die Autoren auf dem richtigen Weg: Zum Redaktionsschluss hatten bereits rund 5000 Personen das Manifest (Abbildung 5) unterzeichnet.
Die verschiedenen Projekte wie Frappuccino oder Sodium haben die Vorgaben des Manifestes allerdings noch nicht offiziell umgesetzt. Somit bleibt abzuwarten, ob sich dessen Programmatik mittelfristig tatsächlich durchsetzt. (mhu)
Infos
- D. Harel und A. Pnueli, “On the Development of Reactive Systems” in Logics and Models of Concurrent Systems: NATO ASI Series Volume 13, 1985, S. 477-498; http://link.springer.com/chapter/10.1007/978-3-642-82453-1_17
- Entwurfsmuster Observer: http://de.wikipedia.org/wiki/Beobachter_%28Entwurfsmuster%29
- Rx (Reactive Extensions): http://rx.codeplex.com
- Rx Java: https://github.com/Netflix/RxJava/wiki
- Frappuccino: https://github.com/steveklabnik/frappuccino
- Sodium: https://github.com/kentuckyfriedtakahe/sodium
- Reactive Manifesto: http://www.reactivemanifesto.org
- Jonas Bonér, “Why Do We Need a Reactive Manifesto?”: https://typesafe.com/blog/why_do_we_need_a_reactive_manifesto%3F
- Typesafe: http://typesafe.com
- Bulkhead Pattern: http://skife.org/architecture/fault-tolerance/2009/12/31/bulkheads.html
- Elm: http://elm-lang.org
- Try Elm (Online-Entwicklungsumgebung): http://elm-lang.org/try
- Elm-Dokumentation: http://elm-lang.org/Learn.elm
- Elm-Programmierbeispiele: http://elm-lang.org/Examples.elm#intermediate
- Pong in Elm: http://elm-lang.org/edit/examples/Intermediate/Pong.elm










