© isaxar/Shutterstock.com
Ratpack für asynchrone Architekturen

Asynchron trifft einfach


Java EE, Spring oder lieber Spring Boot? Die Frameworks wurden in unzähligen Projekten erfolgreich eingesetzt, und es gibt wohl kaum einen Java-Programmierer, der nicht zumindest mit einem dieser Frameworks in Berührung gekommen ist. Allerdings haben diese Frameworks im Laufe der Zeit eine gewisse Komplexität angehäuft, die nicht für jeden Anwendungsfall unbedingt notwendig ist.

Gerade die zunehmende Verbreitung von Microservices oder Self-contained Systems verlangt nach Frameworks, die sowohl genügsamer beim Ressourcenverbrauch (Speicher und CPU) als auch schlanker, was die zur Verfügung gestellten Funktionalitäten betrifft, sind. Eines dieser Frameworks ist Ratpack.

Das Besondere an Ratpack ist, dass ähnlich wie bei Node.js vollständig auf das asynchrone Programmiermodell gesetzt wird und die eingebauten Funktionalitäten auf das Wesentliche beschränkt sind. So wird man in Ratpack all das finden, was mit HTTP-Handling zu tun hat, doch einen ausgewachsenen ORM Mapper wird man vergeblich suchen. Allerdings lässt sich Ratpack einfach durch zusätzliche Module erweitern. So existieren Module für Hikari, um einen Connection-Pool zu integrieren, oder Module für Guice oder Spring, um einen ausgewachsenen DI-Container verwenden zu können.

Das klingt im ersten Moment nicht wirklich weltbewegend. Für sich allein genommen sind das noch keine Features, die Ratpack positiv aus der Masse der bereits etablierten Frameworks herausheben. Ratpack kann aber mehr. Es ist in der Lage, asynchrone Programmierung verstehbar und beherrschbar zu machen. Wieso beherrschbar? Asynchrone Programmierung ist doch nicht wirklich schwer? Die Praxis zeigt, dass das eben nicht der Fall ist. Insbesondere das Debugging von asynchronem Code bereitet häufig Schwierigkeiten und kostet viel Zeit. Die Gründe dafür sind vielfältig. Häufig ist es aber so, dass es ab einer gewissen Komplexität schwerfällt, ein vollständiges mentales Modell mehrerer asynchroner Prozesse im Kopf zu haben. Das ist in etwa so wie bei zu stark verschachtelten if-Anweisungen. Ab einer gewissen Tiefe wird auch das zunehmend unverständlich. Hier setzt Ratpack an, indem es für asynchrone Prozesse ein wohl definiertes Execution Model (Ausführungsmodell) beschreibt. Wohl definiert bedeutet in diesem Zusammenhang, eine reproduzierbare Ausführungsreihenfolge der einzelnen Bestandteile einer Ratpack-Applikation zu garantieren.

Das Execution Model

Ein sehr einfaches Ratpack-Programm, das aber schon alle wesentlichen Bestandteile enthält, sehen Sie in Listing 1. Die Beispiele verwenden durchgehend Groovy, da zum einen die Listings für diesen Artikel dadurch kompakter werden und die von Ratpack zur Verfügung gestellte Groovy DSL die Programmierung insgesamt deutlich angenehmer gestaltet.

Aber zurück zu Listing 1. In der ersten Zeile wird dem Groovy-Skript mit @Grab die Ratpack Library als Abhängigkeit hinzugefügt. Der Rest ist eine Ansammlung von verschachtelten Closures, die für das Java-8-verwöhnte Auge etwas seltsam wirken, aber eigentlich nur konsequent eine Besonderheit von Groovy verwenden. In Groovy kann eine Closure, wenn sie das letzte Argument einer Methode ist, auch in geschweiften Klammern hinter den Methodenaufruf geschrieben werden. Der Java-8-Ausdruck zahlenListe.filter(value -> value > 5) kann in Groovy also auch als zahlenListe.filter {value -> value > 5} umgesetzt werden. Kleine Ursache, große Wirkung. Durch diesen simplen Trick kann Ratpack mit den Sprachmitteln von Groovy eine wirklich komfortable DSL zur Verfügung stellen.

Nachdem das Programm mit groovy listing1.groovy gestartet wurde, passieren zwei Dinge: Auf der Konsole erscheinen einige Loginformationen von Ratpack, und ein Netty-Server wartet auf Port 5050 auf eingehende get-Requests, um den Text „Hello World“ auszuliefern. Damit wären wir auch schon direkt beim Execution Model von Ratpack. Wenn nun der get-Request (in diesem Fall auf http://localhost:5050) ausgeführt wird, werden folgende Schritte umgesetzt:

  • Es wird nach einem passenden Handler gesucht

  • Der get-Handler wird gefunden

  • Dann wird zuerst die Zahl 4 auf der Konsole ausgegeben

  • Durch die Verwendung von Blocking.get werden neue Promises registriert

  • Nach einer Sekunde wird die Zahl 7 auf der Konsole ausgegeben

  • Danach werden die Promises abgearbeitet und die Werte 1 und 2 auf der Konsole ausgegeben

  • Schließlich wird der Text „Hello World“ als Ergebnis des get-Requests zurückgegeben.

Listing 1: „listing1.groovy“

@Grab('io.ratpack:ratpack-groovy:1.4.4') import static ratpack.groovy.Groovy.ratpack import ratpack.exec.Blocking ratpack { handlers { get { print 4 Blocking.get { return "Hello World" }.then { text -> Blocking.get { print 1 return text }.then { text2 -> print 2 render text2 } } sleep 1000 print 7 } } }

Dieses Ergebnis ist reproduzierbar. Egal wie oft Sie den get-Request ausführen, auf der Konsole wird immer die Zahl 4712 erscheinen. Zumindest für diejenigen, die schon mit Promises im JavaScript-Umfeld in Berührung gekommen sind, dürfte das eine Überraschung sein. In JavaScript sind Promises nämlich wirklich asynchron. Die Ausführung wird sofort angestoßen, und ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang