Stand: 2018-06-05

Reactor vs. RxJava

Ist Reactor oder RxJava die bessere Wahl?

Reactor und RxJava gehören als Frameworks für Functional Reactive Programming zur Familie der von den Reactive Extensions (Rx) abgeleiteten Frameworks. Beide sind sich so ähnlich, dass es gar nicht so einfach ist, sich für eins davon zu entscheiden. Dieser Artikel erläutert die Gemeinsamkeiten und Unterschiede und macht die Entscheidung hoffentlich leichter.

Java-Version

RxJava basiert nach wie vor auf der angestaubten Java-Version 6 (erschienen 2006, also kurz nach der letzten Eiszeit). Immerhin hat das den Vorteil, dass es für ältere Android-Versionen geeignet ist. Dafür können nicht die Vorteile von Java 8 genutzt werden.

Reactor basiert dagegen auf Java 8, was folgende Vorteile bietet:

Reactive Streams

Der “Reactive Streams”-Standard beschreibt minimale Schnittstellen zur Kommunikation mit einem Framework wie Reactor oder RxJava. Mit Java 9 wurden diese von mehreren Herstellern entwickelten Schnittstellen als Flow API offiziell zum Java-Standard erhoben und werden dementsprechend dauerhaft Bedeutung haben.

Reactor wurde in der aktuellen Version 3 von Grund auf mit diesen Schnittstellen entwickelt, was am deutlichsten daran erkennbar ist, dass die zentralen Klassen Flux und Mono die Schnittstelle org.reactivestreams.Publisher implementieren.

Die Verbindung von RxJava zu Reactive Streams ist nicht so nahtlos. Es wird eine zusätzliche Bibliothek benötigt und außerdem sind explizite Konvertierungen erforderlich.

Performance

Allzu viele Performance-Vergleiche habe ich nicht gefunden, aber RxJava und Reactor scheinen recht dicht bei einander zu liegen, wie dieser Benchmark zeigt: mal ist RxJava schneller, mal Reactor. Beide Frameworks sind auf jeden Fall so schnell, dass sie in den wenigsten Fällen zum Flaschenhals werden dürften.

API

Die RxJava-API hat eine deutlich größere Oberfläche, was in erster Linie daher kommt, dass es die zentralen Klassen in je zwei Varianten gibt: einmal mit Backpressure (Überlastungsschutz) und einmal ohne.

In RxJava gibt es diese grundlegenen Typen:

Reactor ist da deutlich übersichtlicher:

Im Prinzip müsste es für die Performance (außer bei Überlastung!) vorteilhaft sein, auf Backpressure zu verzichten, aber wie man in dem oben verlinkten Benchmark sieht, ist das nicht durchgängig der Fall. In manchen Messungen ist RxJava mit Backpressure schneller als ohne. Und selbst da, wo RxJava ohne Backpressure schneller ist als mit, ist Reactor trotz Backpressure manchmal schneller. Da Backpressure gerade dann sinnvoll ist, wenn man an die Grenze kommt, halte ich es für eine gute Idee der Reactor-Entwickler, nur Typen mit Backpressure anzubieten und die API dadurch übersichtlicher zu halten. Zudem sind die RxJava-Typen ohne Backpressure sowieso nicht mit Reactive Streams kompatibel.

Zur schlankeren API von Reactor trägt natürlich auch bei, dass nicht wegen der alten Java-Version eigene Varianten funktionaler Schnittstellen existieren müssen.

Dokumentation

Die API-Dokumentation ist bei beiden Frameworks umfangreich und gut. Bei der Referenzdokumentation gibt es allerdings größere Unterschiede. Die Reactor-Referenz ist hervorragend gegliedert, umfangreich und erklärt gut. Angenehm ist auch, dass es eine große, zusammenhängende Dokumentation ist. Dagegen ist die RxJava-Referenz auf eine Übersichtsseite, ein Wiki und die ReactiveX-Website verteilt. Letztere ist aber auch eher eine bessere API-Dokumentation und nicht mit der einem Buch ähnlichen Referenz von Reactor vergleichbar. Dafür gibt es für RxJava ein paar Bücher, die es für Reactor nicht gibt.

Reactor Referenz
Die Referenz von Reactor ist so umfangreich wie ein Buch.

Testen

Testhilfsmittel gibt es sowohl für Reactor als auch für RxJava, wobei mir die Werkzeuge von Reactor ausgefeilter erscheinen.

Zur Veranschaulichung von RxJava nehme ich ein Beispiel aus dem Blog von Iván Carballo:

@Test
public void shouldLoadTwoUsers() throw Exception {
  TestSubscriber<User> testSubscriber = new TestSubscriber<>();
  databaseHelper.loadUser().subscribe(testSubscriber);
  testSubscriber.assertNoErrors();
  testSubscriber.assertReceivedOnNext(Arrays.asList(user1, user2))
}

Das Gleiche in Reactor würde so aussehen:

@Test
public void shouldLoadTwoUsers() throw Exception {
  StepVerifier.create(databaseHelper.loadUser())
    .expectNext(Arrays.asList(user1, user2))
    .verifyComplete();
}

Besonders hilfreich ist bei Reactor, dass man sehr einfach mit virtueller Zeit testen kann. Dabei stellt man die Uhr im Test sozusagen schneller vor. Man braucht dazu nur folgende Methode statt create aufzurufen:

StepVerifier.withVirtualTime(() -> /* create flux */) 

RxJava scheint nichts Vergleichbares zu bieten. Stattdessen muss man sich mit mehr oder weniger eleganten Workarounds behelfen.

Einsatzgebiete

Grundsätzlich können beide Frameworks für alle möglichen Zwecke eingesetzt werden, es gibt jedoch zwei Fälle, die klar für das eine oder andere Framework sprechen. Wenn Spring verwendet wird, sollte man Reactor verwenden, weil Reactor dort auf allen Ebenen von der Datenbank bis zum HTTP-Service unterstützt wird. Wenn man für ältere Android-Versionen entwickelt, kommt Reactor gar nicht in Frage, sodass nur RxJava bleibt.

Beständigkeit

Mit Netflix und Pivotal stehen zwei solide Unternehmen hinter RxJava beziehungsweise Reactor. RxJava ist zwar vermutlich weiter verbreitet, weil es schon länger existiert, aber seit Spring 5 ist Reactor tief in das Framework integriert, sodass man sich um die Zukunft beider Frameworks wohl keine Sorgen machen muss.

Zusammenfassung

Das einzige wirkliche Argument für RxJava ist aus meiner Sicht, wenn man gezwungen ist, Java vor Version 8 zu benutzen. Sieht man von der etwa gleich guten Performance ab, ist Reactor in allen Punkten überlegen. Selbst das Argument, dass irgendeine Bibliothek, die man benutzen möchte, RxJava-Typen in der API anbietet, ist kein starkes Argument für dieses Framework, weil man dank Reactive Streams ziemlich leicht RxJava-Typen mit Reactor verwenden kann.

Sogar der Projektleiter von RxJava, David Karnok, hat auf Twitter geschrieben:

Use Reactor 3 if you are allowed to use Java 8+, use RxJava 2 if you are stuck on Java 6+ or need your functions to throw checked exceptions