Definition

Reaktive Programmierung

Was ist reaktive Programmierung?

Bei der reaktiven Programmierung handelt es sich um ein Programmierparadigma oder -modell, bei dem das Konzept im Mittelpunkt steht, auf Änderungen von Daten und Ereignissen zu reagieren, anstatt auf ein Ereignis zu warten.

Ein Ereignis in der Datenverarbeitung bezieht sich auf Benutzer- oder Systemaktionen, die eine bestimmte Reaktion innerhalb eines Programms auslösen. Diese Ereignisse lassen sich am besten als Datenströme oder Streams darstellen, die durch mehrere Verarbeitungselemente fließen, unterwegs angehalten werden oder sich in eine parallele Verarbeitungsaktivität aufspalten können. In den meisten Fällen ist diese Verarbeitung zeitkritisch, was bedeutet, dass die Anwendungen einen anderen Programmierstil erfordern, und so entstand die reaktive Programmierung.

Die reaktive Programmierung basiert auf einer asynchronen Programmierlogik, um Aktualisierungen von ansonsten statischen Inhalten in Echtzeit zu verarbeiten. Das bedeutet, dass ein Programm eine lang laufende Aufgabe starten kann, während es gleichzeitig auf andere Ereignisse reagiert.

Mit der reaktiven Programmierung können Entwickler asynchrone Datenströme und Ereignisse auf intuitivere Weise handhaben, indem sie ansonsten komplexe ereignisgesteuerte Szenarien vereinfachen. Im Wesentlichen handelt es sich dabei um eine Möglichkeit, Software zu erstellen, die auf Ereignisse reagiert, anstatt Eingaben von Benutzern zu verlangen. Mithilfe dieses Programmiermodells können Entwickler reaktionsschnelle und effiziente ereignisgesteuerte Anwendungen erstellen.

Frühe Anwendungen der reaktiven Programmierung für Geschäftsanwendungen beschränkten sich weitgehend auf Dinge wie die Überwachung des Zustands von Netzwerken, Servern und Software sowie die Signalisierung von Datenbankzuständen, zum Beispiel von Lagerbeständen. Mit dem Aufkommen des Internets der Dinge (IoT), intelligenter Gebäude und Städte sowie des Public Cloud Computing ändert sich dieser Schwerpunkt.

Das IoT hat das reaktive Modell in der Gebäudeverwaltung, der industriellen Prozesssteuerung und sogar in der Hausautomatisierung wichtig gemacht. Die Cloud hat sowohl eine Art der Komponentisierung von Software eingeführt – funktionales Computing und Microservices – als auch eine Bewegung, viele reaktive Anwendungen in die Cloud zu verlagern, um deren Skalierbarkeit und Zuverlässigkeit zu nutzen.

Wie funktioniert reaktive Programmierung?

Bei der reaktiven Programmierung dreht sich alles um Streams, das heißt zeitlich geordnete Sequenzen zusammengehöriger Ereignismeldungen. Datenströme, die in der reaktiven Programmierung verwendet werden, werden kontinuierlich oder nahezu kontinuierlich erstellt. Diese Datenströme werden von einer Quelle – einem Bewegungssensor, einem Temperaturmesser oder einer Produktbestandsdatenbank – als Reaktion auf einen Auslöser gesendet.

Bei diesem Auslöser (Trigger) kann es sich um einen der folgenden Punkte handeln:

  • Ein Ereignis (Event), wie zum Beispiel softwaregenerierte Alarme, Tastenanschläge oder Signale von einem IoT-System.
  • Ein Aufruf (Call), das heißt eine Funktion, die eine Routine als Teil eines Arbeitsablaufs aufruft.
  • Eine Nachricht (Message), das heißt eine Informationseinheit, die das System mit Informationen über den Status eines Vorgangs, eines Fehlers, einer Störung oder eines anderen Zustands an den Benutzer oder Systembetreiber zurücksendet.

Jede asynchrone Operation kann auch als Auslöser dienen.

Die reaktive Programmierung und die reaktiven Systeme, mit denen sie zu tun hat, bestehen ebenfalls aus einer Kombination von Observer- und Handler-Funktionen. Die Observer-Funktion erkennt wichtige Bedingungen oder Änderungen und erzeugt dann Nachrichten, um zu signalisieren, dass sie eingetreten sind. Der Event-Handler behandelt diese Nachrichten in geeigneter Weise.

Ein bestimmter Stream beginnt im Allgemeinen mit einem Observer. Dabei kann es sich entweder um ein Codesegment in einer Anwendung handeln, das auf eine anwendungsbezogene Bedingung achtet, oder um ein Gerät wie einen IoT-Sensor, der ein Ereignis erzeugt. Ein Stream wird manchmal als Pfeil – von links nach rechts – dargestellt, der mit dem Beobachterprozess beginnt und einen oder mehrere Handler durchläuft, bis er vollständig verarbeitet ist, in einem Fehlerstatus endet oder sich in abgeleitete Streams verzweigt. Bei der reaktiven Programmierung geht es um die Erstellung dieser Observer und Handler sowie um das Threading des Streams nach Bedarf.

Gerätegenerierte Streams sind leicht zu verstehen. Aber Datenströme, die von durch Software eingefügten Observern erzeugt werden, sind ein wenig komplizierter. Normalerweise arbeiten diese Elemente entweder mit der Verarbeitung durch eine Anwendung zusammen oder sie werden periodisch ausgeführt, um ein Datenbankelement zu überwachen. Wenn dieses Softwareelement eine Bedingung erkennt, erzeugt es ein Ereignis im Datenstrom.

Ein Ereignisstrom wird entweder von den Handlern selbst gesteuert, wo die Arbeit an einen bestimmten nächsten Prozess weitergeleitet wird, oder von einem Nachrichtenbus wie einem Enterprise Service Bus (ESB) oder einer Nachrichtenwarteschlange, die die Nachricht an bestimmte Bus-Listener weiterleitet. Der Nachrichtenverarbeitungsprozess bestimmt, ob eine Nachricht an mehrere Handler oder an einen einzigen Handler gesendet wird. Er ist normalerweise auch für den Lastausgleich zwischen mehreren parallelen Handlern oder die Bereitstellung von Ersatz-Handlern im Falle eines Ausfalls verantwortlich.

Jeder Handler muss entweder die Nachricht weiterleiten, feststellen, dass der Stream-Prozess beendet ist und die Nachricht verarbeiten, oder einen Fehler erzeugen. Der Handler kann entscheiden, ob er eine Nachricht an mehrere Streams weiterleitet oder einen neuen Stream erzeugt. Diese Fork-Bedingungen werden häufig verwendet, um Aufgaben bei der Nachrichtenverarbeitung zu trennen; so kann eine Nachricht beispielsweise eine lokale Antwort zum Öffnen eines Tores sowie eine Nachricht an ein Transaktionsverarbeitungssystem erzeugen.

Bei der reaktiven Programmierung wird davon ausgegangen, dass es keine Kontrolle über die Anzahl oder den Zeitpunkt der Ereignisse gibt, so dass die Software resilient und hoch skalierbar sein muss, um variable Lasten zu bewältigen. Anstatt einfachen sequenziellen Code zu schreiben, müssen die Entwickler alles als Callback-Funktionen schreiben.

In The Reactive Principle, dem Nachfolger von The Reactive Manifesto, definieren Jonas Bonér et al. die acht Prinzipien, die eine Anwendung verkörpern muss, um als reaktiv zu gelten:

  1. Reaktionsschnell bleiben. Reagieren Sie immer zeitnah und rechtzeitig.
  2. Unsicherheit akzeptieren. Bauen Sie Zuverlässigkeit trotz unzuverlässiger Grundlagen auf.
  3. Fehler akzeptieren. Rechnen Sie damit, dass etwas schief geht, und bauen Sie Widerstandsfähigkeit auf.
  4. Autonomie sichern. Entwickeln Sie Komponenten, die unabhängig voneinander agieren und zusammenarbeiten.
  5. Maßgeschneiderte Konsistenz. Individualisieren Sie die Konsistenz pro Komponente, um ein Gleichgewicht zwischen Verfügbarkeit und Leistung herzustellen.
  6. Zeit entkoppeln. Verarbeiten Sie asynchron, um Koordination und Wartezeiten zu vermeiden.
  7. Raum entkoppeln. Schaffen Sie Flexibilität, indem Sie das Netzwerk einbeziehen.
  8. Dynamik handhaben. Passen Sie sich kontinuierlich an wechselnde Anforderungen und Ressourcen an.
Die acht Prinzipien reaktiver Programmierung
Abbildung 1: Die Reactive Principles beziehen sich auf acht verschiedene Prinzipien, die eine Anwendung berücksichtigen muss, wenn sie reaktiv sein soll.

Vorteile und Herausforderungen der reaktiven Programmierung

Die Hauptvorteile reaktiver Programmiertechniken liegen in ihrer Fähigkeit, Folgendes zu leisten:

  • Bessere Kontrolle über die mit der Verarbeitung von Ereignissen verbundenen Reaktionszeiten.
  • Ermöglichung eines konsistenten Softwaredesigns für Echtzeitsysteme, um Entwicklungs- und Wartungskosten sowie den Aufwand zu reduzieren.
  • Unterstützung des Lastausgleichs und der Ausfallsicherheit, um die Qualität der Erfahrung zu verbessern.
  • Explizite Darstellung des Konzepts eines Datenstroms oder Ereignisflusses, wodurch die Verwaltung von Rechenelementen und Verarbeitungsressourcen insgesamt verbessert wird, indem sie anschaulicher gemacht werden.
  • Verarbeitung von asynchronem und nicht asynchronem Code. Dies bietet Entwicklern die Flexibilität, Code zu schreiben, der mehrere Aufgaben gleichzeitig abwickeln kann, ohne dass ein Haupt-Thread die Ausführung unterbrechen muss.

Diese Vorteile bringen jedoch auch Herausforderungen mit sich:

  • Das Hinzufügen von Observer-Prozessen zu aktueller Software kann schwierig oder unmöglich sein, je nach Verfügbarkeit von Quellcode und Programmierkenntnissen der Mitarbeiter.
  • Reaktives Design bedeutet für die Entwickler einen grundlegenden Bewusstseinswandel, und erfordert einen längeren Lernprozess, während dem mehr Validierung und Überwachung von Design und Kodierung erforderlich ist.
  • Reaktive Systeme können durch eine übermäßige Anzahl von Prozessen, die mit dem Datenstrom verbunden sind, leicht zu Verzögerungen führen.
  • Das Debugging einer Anwendung, die reaktive Programmierung verwendet, ist aufgrund der ereignisgesteuerten und asynchronen Natur des Codes komplexer.

Dennoch können moderne Webanwendungen und mobile Anwendungen sehr interaktiv sein und viele Datenereignisse nutzen. Die reaktive Programmierung ist eine Möglichkeit, diese Anwendungen in Echtzeit und auf skalierbare Weise reagieren zu lassen.

Einführung reaktiver Programmierung

Für die reaktive Programmierung gibt es verschiedene Ansätze. So kann die reaktive Programmierung beispielsweise in die imperative, akteursbasierte, regelbasierte oder objektorientierte Programmierung integriert werden. Die Entwickler müssen auch das Framework für die reaktive Programmierung auswählen. Zur reaktiven Programmierung in Java gehören beispielsweise Frameworks wie die folgenden:

  • RxJava. Dies ist eine funktionale reaktive Programmierbibliothek, die für Android verwendet wird.
  • Akka. Hierbei handelt es sich um ein Toolkit und eine Laufzeitumgebung für die Erstellung von Anwendungen auf der Java Virtual Machine.
  • Vert.x. Dies ist ein weiteres Toolkit für die Erstellung reaktiver Anwendungen auf der Java Virtual Machine und ist als ereignisgesteuerte und nicht blockierende Architektur strukturiert.
  • Spring Framework 5.0. Das Spring Framework ist ein reaktives Programmier-Framework, das Reactive Streams zur Kommunikation zwischen Bibliotheken und asynchronen Elementen verwendet.
Wie eine JVM funktioniert
Abbildung 2: Wie eine Java Virtual Machine (JVM) funktioniert.

Gute reaktive Programme beginnen mit einem klaren Diagramm des Ereignisstroms, das alle spezifischen Handler-Prozesse und ihre Rolle bei der Verarbeitung, Beendigung oder Fehlergenerierung enthält. Anschließend wird die Hosting-Plattform – Edge, Cloud oder Rechenzentrum – ausgewählt und im Stream-Diagramm für jeden Prozess festgelegt, so dass ein Hin- und Hergehen über die Grenzen der Hosting-Plattform hinweg vermieden wird. Dann kann die Entwicklung beginnen.

Bei der Entwicklung von Anwendungen mit reaktiver Programmierung sollten folgende Best Practices beachtet werden:

  • Wenn ein Ereignisstrom eine Reaktion in der realen Welt auslösen muss, wie zum Beispiel das Öffnen eines Tores, halten Sie die Kontrollschleife kurz, indem Sie den reagierenden Prozess näher an den Anfang des Stroms verschieben und ihn in der Nähe der Ereignisquelle hosten.
  • Vermeiden Sie die Verwendung von Programmiersprachen und -techniken, die zustandsbehaftete Komponenten erstellen, die Daten mit der Software speichern, um sicherzustellen, dass die Komponenten leicht skaliert und ersetzt werden können.
  • Überprüfen Sie den Standort und die Implementierung von Datenbanken, die von einem der Handler-Prozesse benötigt werden, um sicherzustellen, dass der Datenbankzugriff keine zusätzlichen Latenzzeiten oder Cloud-Grenzen verursacht, was zu zusätzlichen Kosten führt.
  • Verweisen Sie bei jedem Entwicklungsschritt auf das Ereignisstromdiagramm, um sicherzustellen, dass es gepflegt, aktuell und genau ist.

Was sind Reactive Streams?

Reactive Streams ist eine Spezifikation aus dem Jahr 2013. Sie definiert die Schnittstellen und Klassen, die zur Erstellung von Anwendungen durch reaktive Programmierung verwendet werden. Das Ziel der Klassen ist es, Schnittstellen, Protokolle und Frameworks für die reaktive Programmierung zu spezifizieren und gleichzeitig einen Standard für die asynchrone Stream-Verarbeitung mit nicht-blockierendem Gegendruck zu definieren.

Asynchrone Stream-Verarbeitung mit nicht-blockierendem Gegendruck bezieht sich auf die asynchrone Verarbeitung von Datenströmen, was bedeutet, dass empfangende Systeme den Datenfluss verarbeiten können, ohne überfordert zu werden.

Bibliotheken und Frameworks wie RxJava, Akka und Spring Framework 5.0 wurden beispielsweise unter Verwendung der Reactive-Streams-Spezifikation erstellt.

Anwendungsfälle der reaktiven Programmierung

Zu den wichtigsten Anwendungsfällen für die reaktive Programmierung gehören:

  • IoT-Anwendungen, bei denen Sensoren Ereignisse erzeugen, die dann Prozessschritte in der realen Welt steuern, Geschäftstransaktionen erzeugen oder beides. Dies ist die am schnellsten wachsende Anwendung reaktiver Programmiertechniken, wenn auch nicht das traditionelle Ziel.
  • Anwendungen, die Statusinformationen von Netzwerken oder Datenverarbeitungselementen durch eingefügte Softwareagenten sammeln, um Aktivitäten oder Datenelemente zu überwachen. Dies ist die erste klassische Anwendung der reaktiven Programmierung, die jedoch mit dem IoT konvergiert.
  • Jede Anwendung, die eine hochgradig interaktive Handhabung der Benutzeroberfläche erfordert, insbesondere wenn jeder Tastendruck verarbeitet und interpretiert werden muss. Dies ist die andere klassische Anwendung der reaktiven Programmierung, zu der jetzt auch Spiele, Webanwendungen und einige Social-Media-Anwendungen gehören.
  • Signale zwischen Anwendungen, insbesondere zwischen so genannten Vordergrund- und Hintergrundanwendungen, die statistische Analysen und Datenbankbereinigungen durchführen. Dieser Anwendungsfall beinhaltet normalerweise einen Daemon-Prozess, der Änderungen überwacht und einen Ereignisstrom aktiviert, wenn eine Änderung festgestellt wird.
  • Koordinierung zwischen der funktionalen AWS Lambda Cloud-Verarbeitung und der Backend-Verarbeitung im Rechenzentrum, wobei ein Ereignis die Ausführung eines Backend-Prozesses auslöst. Dies erleichtert einige Formen der Hybrid-Cloud-Entwicklung.
  • Data Streaming in Echtzeit und Big-Data-Analyse: Reaktive Programmierung ist ideal für die Verarbeitung großer Mengen von Echtzeit-Datenströmen.
Diese Definition wurde zuletzt im Juli 2024 aktualisiert

Erfahren Sie mehr über Softwareentwicklung