5 Dinge, die man über reaktive Programmierung wissen sollte

By Clement Escoffier 30. Juni 2017August 6. 2020

Reaktiv, was für ein überladenes Wort. Viele Dinge entpuppen sich in diesen Tagen als magisch reaktiv. In diesem Beitrag werden wir über reaktive Programmierung sprechen, d. h. ein Entwicklungsmodell, das um asynchrone Datenströme herum strukturiert ist.

Ich weiß, dass Sie ungeduldig sind, Ihre erste reaktive Anwendung zu schreiben, aber bevor Sie das tun, sollten Sie einige Dinge wissen. Die reaktive Programmierung verändert die Art und Weise, wie Sie Ihren Code entwerfen und schreiben. Bevor Sie auf den Zug aufspringen, ist es gut zu wissen, wohin Sie sich bewegen.

In diesem Beitrag werden wir 5 Dinge über reaktive Programmierung erklären, um zu sehen, was sich dadurch für Sie ändert.

Bei der reaktiven Programmierung werden Datenströme das Rückgrat Ihrer Anwendung sein. Ereignisse, Nachrichten, Aufrufe und sogar Fehler werden über einen Datenstrom übertragen. Bei der reaktiven Programmierung beobachten Sie diese Datenströme und reagieren, wenn ein Wert ausgegeben wird.

So werden Sie in Ihrem Code Datenströme von allem und jedem erstellen: Klick-Ereignisse, HTTP-Anforderungen, aufgenommene Nachrichten, Verfügbarkeitsmeldungen, Änderungen an einer Variablen, Cache-Ereignisse, Messungen von einem Sensor, buchstäblich alles, was sich ändern oder passieren kann. Dies hat einen interessanten Nebeneffekt auf Ihre Anwendung: Sie wird von Natur aus asynchron.

Reactive eXtension (http://reactivex.io, auch bekannt als RX) ist eine Implementierung der reaktiven Programmierprinzipien, um „asynchrone und ereignisbasierte Programme durch die Verwendung beobachtbarer Sequenzen zu komponieren“. Mit RX erstellt Ihr Code Datenströme namens Observables und abonniert diese. Während es bei der reaktiven Programmierung um die Konzepte geht, bietet Ihnen RX eine erstaunliche Toolbox. Durch die Kombination von Observer- und Iterator-Mustern und funktionalen Idiomen verleiht RX Ihnen Superkräfte. Sie verfügen über ein ganzes Arsenal an Funktionen zum Kombinieren, Zusammenführen, Filtern, Transformieren und Erstellen von Datenströmen. Das nächste Bild illustriert die Verwendung von RX in Java (unter Verwendung von https://github.com/ReactiveX/RxJava).

Während RX nicht die einzige Implementierung der reaktiven Programmierprinzipien ist (wir können zum Beispiel BaconJS – http://baconjs.github.io anführen), ist es heute die am häufigsten verwendete. Im Rest dieses Beitrags werden wir Rx Java verwenden.

2. Observables können kalt oder heiß sein – und es spielt eine Rolle.

An dieser Stelle versuchen Sie zu sehen, mit welchen verschiedenen Streams (oder Observables) Sie in Ihrem Programm zu tun haben werden. Es gibt jedoch zwei Klassen von Datenströmen: heiße und kalte. Den Unterschied zu verstehen ist der Schlüssel zur erfolgreichen reaktiven Programmierung.

Kalte Observables sind faul. Sie tun nichts, bis jemand beginnt, sie zu beobachten (in RX zu abonnieren). Sie beginnen erst zu laufen, wenn sie konsumiert werden. Cold Streams werden verwendet, um asynchrone Aktionen darzustellen, z.B. dass sie erst ausgeführt werden, wenn jemand am Ergebnis interessiert ist. Ein anderes Beispiel wäre ein Dateidownload. Es wird nicht damit begonnen, die Bytes abzurufen, wenn niemand etwas mit den Daten machen will. Die von einem Cold Stream erzeugten Daten werden nicht mit anderen Abonnenten geteilt, und wenn man ein Abonnement abschließt, erhält man alle Daten.

Hot Streams sind vor dem Abonnement aktiv, wie z. B. ein Börsenticker oder Daten, die von einem Sensor oder einem Benutzer gesendet werden. Die Daten sind unabhängig von einem einzelnen Abonnenten. Wenn ein Beobachter ein Hot Observable abonniert, erhält er alle Werte im Stream, die nach dem Abonnieren ausgegeben werden. Die Werte werden von allen Abonnenten gemeinsam genutzt. Ein Thermometer zum Beispiel misst und veröffentlicht die aktuelle Temperatur, auch wenn niemand es abonniert hat. Wenn sich ein Abonnent für den Stream registriert, erhält er automatisch die nächste Messung.

Warum ist es so wichtig zu wissen, ob Ihre Streams heiß oder kalt sind? Weil sich dadurch die Art und Weise ändert, wie Ihr Code die übermittelten Elemente konsumiert. Wenn Sie kein Hot Observable abonniert haben, erhalten Sie die Daten nicht, und diese Daten gehen verloren.

Entwickeln Sie mit den wertvollsten Produkten von Red Hat

Mit Ihrer Mitgliedschaft erhalten Sie Zugang zu Red Hat-Produkten und technischen Schulungen zur Entwicklung von Cloud-Anwendungen für Unternehmen.

JOIN RED HAT DEVELOPER

Missbrauchte Asynchronität beißt

Es gibt ein wichtiges Wort in der Definition der reaktiven Programmierung: asynchron. Sie werden benachrichtigt, wenn Daten im Stream asynchron – also unabhängig vom Hauptprogrammfluss – ausgegeben werden. Wenn Sie Ihr Programm um Datenströme herum strukturieren, schreiben Sie asynchronen Code: Sie schreiben Code, der aufgerufen wird, wenn der Strom ein neues Element ausgibt. Threads, blockierender Code und Seiteneffekte sind in diesem Zusammenhang sehr wichtige Themen. Beginnen wir mit den Seiteneffekten.

Funktionen ohne Seiteneffekte interagieren mit dem Rest des Programms ausschließlich über ihre Argumente und Rückgabewerte. Nebeneffekte können sehr nützlich sein und sind in vielen Fällen unvermeidbar. Aber sie haben auch ihre Tücken. Bei der reaktiven Programmierung sollten Sie unnötige Seiteneffekte vermeiden und eine klare Absicht haben, wenn sie sie doch verwenden. Setzen Sie also auf Unveränderlichkeit und Funktionen ohne Seiteneffekte. Während einige Fälle gerechtfertigt sind, führt der Missbrauch von Seiteneffekten zu Gewitterstürmen: Thread-Sicherheit.

Das ist der zweite wichtige Punkt: Threads. Es ist schön, Streams zu beobachten und benachrichtigt zu werden, wenn etwas Interessantes passiert, aber Sie dürfen nie vergessen, wer Sie aufruft, oder genauer gesagt, auf welchem Thread Ihre Funktionen ausgeführt werden. Es wird dringend empfohlen, nicht zu viele Threads in Ihrem Programm zu verwenden. Asynchrone Programme, die sich auf mehrere Threads stützen, werden zu einem schwierigen Synchronisationspuzzle, das oft als Deadlock-Jagd endet.

Das ist der dritte Punkt: niemals blockieren. Da Sie den Thread, der Sie aufruft, nicht besitzen, müssen Sie sicher sein, dass Sie ihn niemals blockieren. Wenn Sie das tun, können Sie die anderen zu sendenden Elemente vermeiden, sie werden gepuffert, bis … der Puffer voll ist (in diesem Fall kann Gegendruck entstehen, aber das ist nicht das Thema dieses Beitrags). Durch die Kombination von RX und asynchroner IO haben Sie alles, was Sie brauchen, um nicht-blockierenden Code zu schreiben, und wenn Sie mehr wollen, schauen Sie sich Eclipse Vert.x an, ein reaktives Toolkit, das Reaktivität und Asynchronität fördert. Der folgende Code zeigt zum Beispiel den Vert.x Web Client und seine RX-API, um ein JSON-Dokument vom Server abzurufen und den Namenseintrag anzuzeigen:

client.get("/api/people/4").rxSend().map(HttpResponse::bodyAsJsonObject).map(json -> json.getString("name")).subscribe(System.out::println, Throwable::printStackTrace);

Beachten Sie die subscribe-Methode in diesem letzten Ausschnitt. Sie nimmt eine zweite Methode auf, die aufgerufen wird, wenn eine der Verarbeitungsstufen eine Ausnahme auslöst. Fangen Sie die Ausnahmen immer ab. Wenn Sie das nicht tun, werden Sie Stunden damit verbringen, herauszufinden, was schief gelaufen ist.

4. Halten Sie die Dinge einfach

Wie Sie wissen, kommt mit großer Macht große Verantwortung.“ RX bietet eine Menge sehr cooler Funktionen, und es ist leicht, sich auf die dunkle Seite zu begeben. Durch die Verkettung von flapmap, retry, debounce und zip fühlen Sie sich wie ein Ninja… ABER vergessen Sie nie, dass guter Code auch für andere lesbar sein muss.

Lassen Sie uns einen Code nehmen…

manager.getCampaignById(id) .flatMap(campaign -> manager.getCartsForCampaign(campaign) .flatMap(list -> { Single<List<Product>> products = manager.getProducts(campaign); Single<List<UserCommand>> carts = manager.getCarts(campaign); return products.zipWith(carts, (p, c) -> new CampaignModel(campaign, p, c)); }) .flatMap(model -> template .rxRender(rc, "templates/fruits/campaign.thl.html") .map(Buffer::toString)) ) .subscribe( content -> rc.response().end(content), err -> { log.error("Unable to render campaign view", err); getAllCampaigns(rc); });

Ein Beispiel wie dieses ist schwer zu verstehen, oder? Es verkettet mehrere asynchrone Operationen (Flatmap), die sich mit einer anderen Gruppe von Operationen (Zip) verbinden. Reaktiver Programmiercode erfordert zunächst einen Mindshift. Sie werden über asynchrone Ereignisse benachrichtigt. Dann kann die API schwer zu verstehen sein (sehen Sie sich nur die Liste der Operatoren an). Missbrauchen Sie es nicht, schreiben Sie Kommentare, erklären Sie es oder zeichnen Sie Diagramme (ich bin sicher, Sie sind ein Asciiart-Künstler). RX ist mächtig, es zu missbrauchen oder nicht zu erklären wird deine Kollegen mürrisch machen.

5. Reaktive Programmierung != Reaktives System

Wahrscheinlich der verwirrendste Teil. Die Verwendung reaktiver Programmierung baut kein reaktives System auf. Reaktive Systeme, wie sie im reaktiven Manifest definiert sind, sind ein architektonischer Stil, um reaktionsfähige verteilte Systeme aufzubauen. Reaktive Systeme könnten als verteilte Systeme in Reinkultur betrachtet werden. Ein reaktives System zeichnet sich durch vier Eigenschaften aus:

  • Reaktionsfähig: ein reaktives System muss Anfragen in einer angemessenen Zeit bearbeiten (die Definition von „angemessen“ überlasse ich Ihnen).
  • Belastbar: ein reaktives System muss auch bei Ausfällen (Absturz, Zeitüberschreitung, 500 Fehler… ) reaktionsfähig bleiben, also muss es für Ausfälle ausgelegt sein und diese angemessen behandeln.
  • Elastisch: ein reaktives System muss unter verschiedenen Belastungen reaktionsfähig bleiben. Folglich muss es auf- und abwärts skalieren und in der Lage sein, die Last mit minimalen Ressourcen zu bewältigen.
  • Nachrichtengesteuert: Die Komponenten eines reaktiven Systems interagieren unter Verwendung asynchroner Nachrichtenübermittlung.

Trotz der Einfachheit dieser Grundprinzipien reaktiver Systeme ist es schwierig, ein solches System aufzubauen. In der Regel muss jeder Knoten ein asynchrones, nicht blockierendes Entwicklungsmodell, ein aufgabenbasiertes Gleichzeitigkeitsmodell und nicht blockierende E/A verwenden. Wenn Sie nicht zuerst über diese Punkte nachdenken, wird es schnell zu einem Spaghetti-Brei.

Reaktive Programmierung und Reactive eXtension bieten ein Entwicklungsmodell, um das asynchrone Biest zu zähmen. Wenn Sie es klug einsetzen, wird Ihr Code lesbar und verständlich bleiben. Die Verwendung reaktiver Programmierung macht Ihr System jedoch nicht zu einem reaktiven System. Reaktive Systeme sind die nächste Stufe.

Abschluss

Wir sind endlich am Ende dieses Beitrags angelangt. Wenn Sie weiter gehen wollen und sich für reaktive Systeme interessieren, empfehle ich Ihnen einen Blick auf Eclipse Vert.x – ein Toolkit zum Aufbau reaktiver und verteilter Systeme (http://vertx.io) und auf das Minibuch Reactive Microservices in Java, erhältlich bei https://developers.redhat.com/books/building-reactive-microservices-java. Die Kombination von Vert.x und Reactive eXtension entfesselt Ihre reaktiven Superkräfte. Sie können nicht nur reaktiv programmieren, sondern auch reaktive Systeme bauen und haben Zugang zu einem spannenden und wachsenden Ökosystem.

Happy Coding!

Laden Sie sich das Eclipse Vert.x Cheat Sheet herunter, dieses Cheat Sheet liefert Schritt für Schritt Details, damit Sie Ihre Apps so erstellen können, wie Sie es möchten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.