Suche
Kolumne

EnterpriseTales: Java und die reaktive Programmierung – ein neues Programmierparadigma

Arne Limburg

In meiner letzten Kolumne zu zehn Jahren JPA habe ich darüber geschrieben, wie die Spring-Hibernate-Community um das Jahr 2006 für einen Paradigmenwechsel in Enterprise Java gesorgt hat. Naturgemäß lag in der genannten Kolumne der Fokus auf der Veränderung, die JPA gebracht hat. Bis zu dem Zeitpunkt entstanden Java-Enterprise-Standards in der Regel am Reißbrett und waren hinterher für viele Anwendungsfälle untauglich.

Ein Paradebeispiel dafür ist der EJB-Standard in den Versionen 1 und 2. Der Paradigmenwechsel bestand darin, dass von da an Patterns standardisiert wurden, die sich bereits in der Praxis bewährt hatten. Zu diesem Paradigmenwechsel hat nicht nur Hibernate, sondern auch Spring in einem erheblichen Maße beigetragen. Das Spring-Framework nahm seinerzeit die Vorreiterrolle für Dependency Injection ein und etablierte so das Inversion-of-Control-Pattern im Praxiseinsatz. Damit sich Patterns etablieren können, reicht es nicht, das Pattern zu erfinden, es muss auch jemanden geben, der es in die Breite trägt. In diesem Fall gelang Rod Johnson und Jürgen Höller mit ihrem Buch „J2EE-Development without EJB“ und der Implementierung des darauf basierenden Spring Frameworks gleich beides. Das Ergebnis ist, dass Dependency Injection in Java EE standardisiert wurde und aus der Enterprise-Java-Welt heutzutage nicht mehr wegzudenken ist.

Spring war also schon damals ein Innovationstreiber der Enterprise-Java-Welt. Vor diesem Hintergrund ist es bemerkenswert, dass das Spring-Framework mit seiner Version 5 ein neues Webframework liefert, das auf reaktiver Programmierung basiert. Reaktive Programmierung gibt es zwar auch in der Java-Welt schon länger, sie fristet hier allerdings bisher ein Nischendasein. Die Verbreitung des Spring-Frameworks ist geeignet, das zu ändern.

Ein neues Programmierparadigma

Die Idee hinter reaktiver Programmierung ist das Reagieren auf Ereignisse. Ich werde das hier am Beispiel eines REST-Requests erläutern. Wir betrachten, wie die Implementierung eines GET-Requests auf der fiktiven Ressource /customers/5 verarbeitet würde. Nach klassischer prozeduraler oder objektorientierter Programmierung erfolgt die Abarbeitung prozedural. Das Webframework – egal welches – würde eine Methode aufrufen, die einen Parameter hätte, in dem der Wert der Kunden-ID übergeben würde, in unserem Fall 5. Daraufhin würde eine Datenbankabfrage abgesetzt, das Ergebnis würde nach JSON (oder XML) konvertiert und zurückgegeben. Warum sollte ein solcher Use Case nun auf reaktive Art umgesetzt werden?

Eines der Ziele reaktiver Programmierung ist es, blockierende Threads zu vermeiden. Wird dieses Ziel erreicht, hat das zur Folge, dass insgesamt weniger Threads benötigt würden und damit weniger Ressourcen. Zusätzlich würde man unnötige Threadwechsel vermeiden, wodurch ein Performancegewinn erzielt werden könnte.

Ein Programmierer, der prozedurale oder objektorientierte Programmierung gewohnt ist, würde an dieser Stelle vielleicht sagen: „Wenn ich dem Client, der meine REST-Ressource aufruft, eine Antwort geben will, muss ich natürlich auf das Ergebnis der Datenbankabfrage warten.“ Das ist zwar richtig, heißt aber nicht, dass irgendein Thread auf dem Weg zur Datenbank und zurück blockieren muss. Und genau hier setzt reaktive Programmierung an.

Ein klassischer Aufruf des obigen Beispiels würde an mehreren Stellen blockieren: Wenn die GET-Anfrage beginnt, würde zunächst darauf gewartet, dass alle Daten der Anfrage über das Netzwerk vom Client zum Server übertragen wurden. Im zweiten Schritt würden die Daten wieder über das Netzwerk vom Webserver zur Datenbank übertragen. Im dritten Schritt würde die Datenbank die Abfrage ausführen, wobei sie auf IO-Operationen (Speicherzugriff) warten würde. Im vierten Schritt würde das Ergebnis über das Netzwerk vom Datenbankserver zum Webserver übertragen, dort ggf. konvertiert und dann vom Webserver zum Client übertragen. In dem gesamten Prozess gibt es vier Netzwerkübertragungen und einen Speicherzugriff. Bei jedem beteiligten Client, Webserver und Datenbankserver gibt es dabei einen Thread, der blockierend auf den jeweils anderen wartet.

Mit reaktiver Programmierung können diese Wartezeiten vermieden werden. Es beginnt damit, dass ein Listener registriert wird, der auf das Ereignis „Request mit Pfadparameter 5“ reagiert. Im Listener, wenn der Parameter tatsächlich verfügbar ist, wird die Datenbankabfrage gestartet. Gleichzeitig wird ein Listener registriert, der darauf wartet, dass die Datenbank das Ergebnis liefert. In dem Listener wiederum würde die Konvertierung nach JSON angestoßen. Auch hier würde wieder ein Listener registriert, der auf das Ergebnis der Konvertierung wartet, um es auf den Stream zum Client zu übertragen.

Reaktive Programmierung in Java 9 mit dem Flow-API

Bisher spielte reaktive Programmierung im Java-Standard keine Rolle. Das wird sich allerdings mit Java 9 ändern. Mit dem Flow-API [2] und den Interfaces Flow.Publisher, Flow.Subscriber, Flow.Processor und Flow.Subscription wird ein erster Schritt Richtung reaktive Programmierung gegangen. Der Subscriber nimmt dabei die Rolle des Listeners ein und der Publisher die Rolle des Event Producers. Das Processor-Interface ist für Strukturen, die sowohl Listener als auch Producer sind. In den Listings 1 und 2 stelle ich die Implementierung des Datenbankzugriffs aus dem Beispiel einander gegenüber. Listing 1 zeigt dabei ein klassisches Interface und Listing 2 die Variante, wie sie mit dem Flow-API von Java 9 verwendet würde.

public class CustomerRepository {
  ...
  public Customer getCustomer(String id) {
    ...
  }
}

public class CustomerRepository {
  ...
  public Flow.Publisher<Customer> getCustomer(Flow.Publisher<String> id) {
    CustomerProcessor processor = new CustomerProcessor();
    id.subscribe(processor);
    return processor;
  }

  private static class CustomerProcessor
    implements Flow.Processor<String, Customer> {
    private Subscriber<? super Customer> customerSubscriber;
    
    public void subscribe(Subscriber<? super Customer> subscriber) {
      customerSubscriber = subscriber;
    }

    public void onNext(String customerId) {
      // receive customer
      customerSubscriber.onNext(customer);
    }
    ...
  }
}

Die Methode getCustomer in Listing 2 zeigt den tatsächlichen Paradigmenwechsel. Es gibt keinen blockierenden Code. Es wird lediglich ein Listener registriert, der dann direkt als Producer zurückgegeben wird. Betrachtet man weitere APIs in Java 9, fällt allerdings auch schnell auf, dass das Ganze recht schnell kompliziert werden kann. So verwundert es nicht, dass Spring mit seinem reaktiven Webframework nicht direkt auf das Java-9-API, sondern auf eine bessere Abstraktion setzt, die auch gleich im eigenen Hause umgesetzt wurde. Spring setzt auf das Open-Source-Framework Reactor [3], das auch von Pivotal, der Firma hinter Spring, ins Leben gerufen wurde.

Reaktive Programmierung in Enterprise Java

Schaut man in Java in die Enterprise-Welt und damit auf die Serverlandschaft, zeigt sich, dass die Idee der reaktiven Programmierung lange Zeit komplett ignoriert wurde. Lediglich das Open-Source-Projekt Netty [4] hielt hier bisher die Fahne hoch. Es hat allerdings auch einen Grund, warum sich Enterprise-Java-Server bisher an das Modell „Ein Thread pro Request“ gehalten haben: Verschiedene Ressourcen, wie die aktuelle Transaktion und der aktuelle Securitykontext, sind direkt mit dem aktiven Thread verbunden. Daher ist es auch in Java EE verboten, eigene Threads zu starten: Das Verhalten des Servers wäre undefiniert.

Die Idee, dass es gleichgültig ist, auf welchem Thread ein Stück Code ausgeführt wird, passt da erst einmal nicht direkt hinein. Das zeigte sich bereits bei der etwas überhastet eingeführten @Asynchronous-Annotation in EJB, bei der zunächst nicht klar war, wie bei ihrer Verwendung mit Transaktion und Securitykontext umgegangen wird. Mittlerweile hat man aber auch in den Standardisierungskomitees erkannt, dass das Konzept „Ein Thread pro Request“ in vielen Use Cases nicht sinnvoll ist. So gibt es mittlerweile einen eigenen Standard, um in Java EE asynchron Code auszuführen (Concurrency Utilities for Java EE). Zudem wurde Asynchronität bereits in vielen Standards nachträglich sauber spezifiziert.

Lesen Sie auch: Reaktiv in die Praxis: Reaktive Programmierung mit RxJava

Die Servlet-Spec hat sich bereits seit Version 3.0 von dem Ein-Thread-pro-Request-Konzept abgewandt, in dem ein asynchrones API eingeführt wurde, das unter anderem auch Listener unterstützt. JAX-RS ist in der Version 2.0 nachgezogen, und auch mit CDI 2.0 wird asynchrone Eventverarbeitung möglich sein. Lediglich Datenbankabfragen sind nach wie vor synchron und damit blockierend, sei es über das JDBC-API oder mit JPA.

Hier sind einige NoSQL-Datenbanken weiter, indem sie neben den Standard-APIs auch APIs zur reaktiven Programmierung zur Verfügung stellen. Aber auch bei den SQL-Datenbanken kommt die Idee der reaktiven Programmierung so langsam in den Communities an. So gibt es bereits erste Projekte in der Open-Source-Welt, die versuchen, Treiber für reaktive Datenbankkommunikation zur Verfügung zu stellen [5], [6].

Performance vs. Wartbarkeit

Reaktiver Code wird eigentlich in zwei Schritten ausgeführt. Im ersten Schritt wird der Ablauf definiert (in Listing 2 ist das die Methode getCustomer()), und erst im zweiten Schritt findet die tatsächliche Ausführung statt. Diese Trennung macht es nicht leicht, reaktiv geschriebenen Code zu lesen und zu warten. Wer schon einmal versucht hat, einen Java-8-Stream-Ausdruck zu debuggen, weiß, wovon hier die Rede ist. Die Entkopplung, die durch das Publish-Subscribe-Modell entsteht, bedeutet gleichzeitig, dass zur Entwicklungszeit nicht unbedingt klar ist, wer sich auf einen Daten-Stream subscribed hat, d. h. wer die Daten konsumiert. Die höhere Performance geht also einher mit einer schlechteren Wartbarkeit des entstehenden Codes.

Fazit

Reaktive Programmierung ist ein schon länger bekanntes Konzept, das sich nun langsam in der Mainstream-Java-Welt ankommt. Einzelne Projekte wie das Netty-Projekt propagieren die zugehörigen Paradigmen schon länger, und einzelne Konzepte sind auch schon in verschiedene Java-Standards eingeflossen. So bietet das Servlet-API seit Version 3.0 ein API für asynchrone Verarbeitung inklusive eines Listener-Konzepts, und auch in JAX-RS 2.0 gibt es ein Async-API. Im Alltag des Java-Entwicklers sind diese Konzepte bisher aber nicht angekommen.

Das könnte sich nun stark ändern. Java 9 geht einen ersten Schritt in die entsprechende Richtung, indem das Flow-API eingeführt wird. Noch interessanter ist aber das Release von Spring 5, in dem neben dem bekannten Spring Web MVC ein weiteres Webframework zur Verfügung steht, das die reaktive Programmierung kompletter Web-Applications ermöglicht, sofern man eine Datenbank einsetzt, die reaktive Programmierung unterstützt. Damit sind alle Voraussetzungen gegeben, dass die Konzepte der reaktiven Programmierung in der Enterprise-Java-Welt ankommen. Man darf gespannt sein, wie lange das dauert. Der erste Schritt ist jedenfalls getan.

In diesem Sinne: Stay tuned.

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

Schreibe einen Kommentar

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