Hier entwickeln wir Microservices und IT-Systemen für die Cloud mit Java oder Kotlin. Für die einfache Nutzung von Datenbanken (z.B. MongoDB), Security- oder Frontend-Technologien stelle ich das Spring Framework mit dem Spring Boot Projekt vor. Mit Clean Code Prinzipen und Test-Automatisierungstechniken zeige ich, wie Wartbarkeit und Qualität sichergestellt wird.
Mit Spring WebFlux entwickeln wir deutlich performanterer Web-Anwendungen und REST-Services. WebFlux ist Teil vom Spring Reactor Projekt. Es ist die moderne, reaktive Alternative zu Spring MVC. In diesem Artikel baue ich mit Mono und Flux eine reaktive API.
Unterschied zwischen WebFlux und Spring MVC
Das Reactor Projekt bildet die Grundlage des reaktiven Stacks in Spring. Es bietet eine Event-basierte, nicht blockierende Architektur, so dass darauf aufbauende Anwendungen mehr Leistung aus ihren CPU-Ressourcen herausholen.
Spring WebFlux ist Teil des reaktiven Stacks. Es ist das reaktive Gegenstück zu Spring MVC im klassischen Servlet Stack. Weitere Infos zu Spring MVC findet ihr in meinem Blog: spring-mvc-thymeleaf.html Weitere Details zum Spring Reactor Projekt findet ihr hier: https://spring.io/reactive Von dort stammt die folgende Gegenüberstellung zur besseren Einordnung der einzelnen Komponenten aus dem Servlet und reaktivem Stack.
Gegenüberstellung Reactive und Servlet Stack
Im ersten Blog-Artikel zur reaktiven Programmierung stellte ich den Spring WebClient vor und zeigte, dass dieser deutlich performanter ist als der klassische HTTP Client: reactive-webclient.html
Hier zeige ich die Vorteile der reaktiven Programmierung anhand einer mit Spring WebFlux geschriebene Serverseite. Konkret entwickle ich eine REST-API und schicke Mono und Flux als Antwort an einen aufrufenden Spring WebClient.
Video zur Umstellung von Spring MVC auf WebFlux
Design Pattern: Publisher-Subscriber
Die Kommunikation zwischen Client und Server funktioniert in Spring WebFlux nach dem Publisher und Subscriber Entwurfsmuster. Serverseitig agiert Spring WebFlux als Publisher und publiziert Daten als Mono oder Flux, sobald diese verfügbar sind. Clientseitig meldet sich der Spring WebClient beim Publisher an (subscribe) und wartet dann in einem separaten Thread auf die Daten der Server-Antwort. Der Haupt-Thread auf Client-Seite kann ist nicht blockiert und läuft weiter, weil das Warten in einem anderen Clientseitigen Thread geschieht. Dadurch haben wir eine Nachrichten-basierte, nicht blockierende Kommunikation, so wie in der reaktiven Programmierung benötigt. Weitere Details zum Design Pattern findet ihr hier: Wikipedia_Publish_Subscribe
Was ist ein Mono?
Wenn die Serverseite mit Spring WebFlux einen oder keinen Datensatz als Ergebnis liefert, dann verwenden wir als Antworttyp Mono. Dazu ein Beispiel aus der analogen Welt:
Im Restaurant bestellt der Gast (Clientseite) ein Hauptgericht (Mono). Die Küche (Serverseite) fängt nun an das Gericht zu kochen. Sie bringt es dem Gast auf einem Teller (Mono), wenn es fertig ist. In der Zwischenzeit kann der Gast noch andere Dinge tun: Trinken, Reden, Handy spielen usw. - der Gast ist also nicht blockiert (reaktiv), während er auf die Nachricht Hauptgang ist fertig wartet.
Was ist ein Flux?
Liefert die Serverseite eine beliebig große Menge an Datensätzen als Ergebnis, so ist der Antworttyp ein Flux. Ein Flux ist ein Datenstrom der vom Server zum Client fließt. Die Flussgeschwindigkeit kann sich dabei beliebig ändern. Dazu wieder ein Beispiel aus der analogen Gastronomie:
Im Sterne-Restaurant kocht die Küche (Serverseite) ein 9 Gänge Menu (Flux) für die Gäste (Clientseite). Dabei kommen die einzelnen Gänge getrennt von einander beim Gast an, manche Gänge kommen schneller hintereinander andere brauchen etwas länger (Datenstrom). Wie schon beim Mono-Beispiel ist der Gast im Restaurant nicht blockiert bzw. reaktiv, da er sich in der Zwischenzeit beliebig beschäftigen kann.
Mono als Antwort einer REST-API
Eine reaktive REST-API schickt den Datensatz nach dem Publisher-Subscriber Prinzip an den Client. Wenn es höchstens ein Datensatz ist, verwenden wir serverseitig einen Mono. Im folgenden wird ein Spring RestController gezeigt, der ein Mapping für eine GET Methode hat:
@RestController
public class ReactiveEmployeeController {
@ResponseStatus(HttpStatus.OK)
@GetMapping("/employee/mono")
public Mono<Employee> receiveMono() {
Mono<Employee> result = Mono.fromSupplier( () -> generateEmployee())
Die Methode generateEmployee ist ausgeblendet, da sie lediglich eine Employee Instanz erstellt. In meiner Demo wartet die Methode einige Sekunden bevor sie als Ergebnis eine Employee Instanz liefert. Das Warten simuliert eine lang dauernde Suche nach einem Employee in einer großen Datenbank. Je länger die Such- oder Rechenoperationen zum Beschaffen des Ergebnisses dauern und je mehr Anfragen ankommen, desto mehr profitieren wir hinsichtlich Performance von der reaktiven Programmierung.
Mono<Employee> ist der Rückgabetyp unserer GET Methode.
Den HTTP Response-Code definiere ich mit der Annotation @ResponseStatus(HttpStatus.OK).
return Mono.fromSupplierantwortet dem Client direkt, schickt aber noch nicht den eigentlichen Datensatz. Der Parameter der Methode fromSupplier ist ein Lambda Ausdruck bzw. ein Supplier. Der Supplier berechnet den eigentlichen Datensatz (Employee) und publiziert ihn dann durch den Mono an den Client.
doOnSuccess hat als Parameter einen Consumer, den ich hier nur zum Loggen verwende. doOnSuccess wird ausgeführt, wenn der Mono erfolgreich seinen Datensatz publiziert hat.
Supplierund Consumer sind funktionale Interfaces, die in Java 8 eingeführt wurden.
Wie man clientseitig mit dem reaktiven Spring WebClient einen Mono abfragt und einen Lambda Ausdruck als Consumer zur Verarbeitung des Datensatzes registriert (subscribe), zeige ich in reactive-webclient.html. Daher hier ohne weitere Erklärungen der Code der Clientseite:
WebClient reactiveClient = WebClient.builder()
.baseUrl("http://localhost:8080").build();
reactiveClient.get().uri("/employee/mono")
.retrieve().bodyToMono(Employee.class)
.subscribe(employee -> {
log.info("Response received: " + employee);
});
log.info("Request send. " +
"Data published in Mono will be handled in another thread.");
Folgende 4 Log-Einträge werden vom Client und Server durch den Logger (log.info) in dieser Reihenfolge geschrieben:
2023-02-21 21:46:12.255 INFO [ main] Client : Request send. Data published in Mono will be handled in another thread.
2023-02-21 21:46:12.492 INFO [ctor-http-nio-4] ReactiveEmployeeController : Returning Mono.
2023-02-21 21:46:14.515 INFO [ctor-http-nio-4] ReactiveEmployeeController : Mono published: Employee(id=-772...)
2023-02-21 21:46:14.654 INFO [ctor-http-nio-3] Client : Response received: Employee(id=-772...)
Beachtet die verschiedenen clientseitig eingesetzten Threads: main und ctor-http-nio-3. Aufgrund der geringen Last wurde serverseitig nur ein Thread ctor-http-nio-4 verwendet, das können bei höherer Last mehr sein. Die Reihenfolge der Logeinträge zeigt, dass die Threads weder client- noch serverseitig blockiert wurde. Bei einer synchronen, blockierenden Kommunikation wären, die ersten beiden Log-Einträge am Ende:
Clientseitig, weil der Log-Eintrag "Request send..." erst nach erhaltener Antwort vom Server geschrieben wird - vorher ist der main Thread blockiert.
Serverseitig, weil zuerst das Ergebnis (Employee Instanz) berechnet wird und erst danach "Returning..." geloggt werden würde. (Inhaltlich machen die geloggten Texte bei einer synchronen Kommunikation natürlich keinen Sinn.)
Flux als Antwort einer REST-API
Wie zuvor erwähnt ist der Unterschied zwischen Mono und Flux die Anzahl der Datensätze, welche der reaktive Server publiziert. Beim Flux können es beliebig viele sein, es ist also eine Art Stream. Im folgenden Beispiel werde ich serverseitig die Datensätze im Flux aus einem Stream beziehen. Das Stream Interface wurde in Java 8 eingeführt und wird zum Beispiel hier vorgestellt: https://ertan-toker.de/java-streams-tutorial-and-examples/
Flux<Employee> ist der Rückgabetyp unserer Methode. Damit der Client erkennt, dass die Antwort ein reaktiver Datenstrom (Flux) ist, muss der Content Type für unsere HTTP Response entsprechend als "application/x-ndjson" definiert werden. Das mache ich in der Annotation @GetMapping mit: produces = MediaType.APPLICATION_NDJSON_VALUE
Ansonsten habe ich @GetMapping und @ResponseStatus bereits im Mono Beispiel weiter oben erklärt.
Die Supplier<Employee> Instanz ist ein Lambda Ausdruck, der die Methode generateEmployee aufruft. Diese Methode wird auch im Mono Beispiel verwendet und erklärt.
Stream.generate erstellt mit Hilfe der Supplier<Employee> Instanz einen Datenstrom vom Typ Stream<Employee>. Die Supplier Instanz befüllt also kontinuierlich den Stream mit neu generierten Employee Objekten bzw. Daten. Dieses Beispiel ist ein endloser Datenstrom. In der Praxis könnte ein Stream aus einer endlichen Liste erstellt werden oder eine reaktive Datenbank-Abfrage, die mit längerer Rechenzeit immer mehr Ergebnisse liefert. Die reaktive Datenbank-Abfrage stelle ich hier vor: spring-data-reactive.html
Flux.fromStream ist eine von vielen Möglichkeit eine Flux Instanz zu erstellen. Schaut euch einfach die abstrakte Klasse Flux im Quellcode an, um weitere Möglichkeiten zu sehen.
Analog zu Mono (.doOnSuccess) gibt es auch beim Flux diverse Methode, um in den reaktiven Datenstrom einzugreifen. Mit .doOnNext greift Ihr jedes im Flux publizierte Objekt zu - ich logge hier einfach nur das publizierte Objekt.
Die Clientseite sieht beim Flux vergleichbar zum Mono aus:
log.info("Part of response received: " + employee);
});
reactiveClient ist dieselbe Instanz von WebClient, die zuvor im Mono Beispiel verwendet wurde.
bodyToFlux wird anstatt bodyToMono verwendet und legt fest, dass die Antwort des Servers ein Datenstrom ist.
Jedes vom Server durch den Flux publizierte Employee Objekt wird in einem Consumer verarbeitet. Der Consumer bindet sich mit der subscribe Methode als Lambda Funktion an den Flux<Employee>.
Hier ein kleiner Ausschnitt aus den client- und serverseitigen Logs:
2023-02-23 21:08:31.258 INFO [ctor-http-nio-4] ReactiveEmployeeController: Flux emits: Employee(id=-835...)
2023-02-23 21:08:31.447 INFO [ctor-http-nio-3] Client: Parts of response received: Employee(id=-835...)
2023-02-23 21:08:33.353 INFO [ctor-http-nio-4] ReactiveEmployeeController: Flux emits: Employee(id=-526...)
2023-02-23 21:08:33.358 INFO [ctor-http-nio-3] Client: Parts of response received: Employee(id=-526...)
2023-02-23 21:08:35.363 INFO [ctor-http-nio-4] ReactiveEmployeeController: Flux emits: Employee(id=494...)
2023-02-23 21:08:35.367 INFO [ctor-http-nio-3] Client: Parts of response received: Employee(id=494...)
Der Client bzw. ein Thread auf der Clientseite verarbeitet immer dann einen Datensatz, wenn der Datensatz vom Server publiziert wurde. Damit haben wir mit einem HTTP Request einen Datenstrom zwischen Client und Server aufgebaut, der effizient und Nachrichten-basiert in Threads verarbeitet wird. Diese Konstellation ist für mich ein Highlight der reaktiven Programmierung! 🎆
Die langsameren Alternativen in der klassischen Programmierung bzw. bei der Verwendung von synchronen Requests sind:
langes Warten auf eine riesige Antwort mit allen Datensätzen
oder viele kleine Abfragen (Pagination), um die Datensätze in kleineren Teilmengen abzufragen.
Fazit
Ich habe gezeigt, wie man mit reaktiver Programmierung und Spring WebFlux eine Client-Server-Kommunikation nach dem Publisher und Subscriber Entwurfsmuster aufbauen kann. Sowohl die Client- als auch die Serverseite sind nicht blockierend und damit besonders performant auf aktuellen Rechnern mit mehreren Kernen.
Die Unterschiede zwischen Mono (höchstens ein Datensatz als Antwort) und Flux (beliebig viele Datensätze als Antwort) habe ich anhand von Beispielen erklärt. Wie immer findet ihr den kompletten Code mit JUnit-Tests, die als Clientseite fungieren, in GitHub:
Authentifizierung mit OpenID Connect geht einfach dank Spring Boot. Wir bauen den Login Deiner Web-Anwendung mit dem Autorisierungsserver Deiner Firma. Wie wir dazu OpenID Connect mit Spring Boot 3 konfigurieren, zeige ich in diesem Blog-Artikel. OpenID Connect - Authorization Code Prozess OpenID Connect ist ein Single Sign-On Login-Verfahren. Zur Umsetzung komplexer Anwendungsfälle verwenden größere Firmen meist verteilte Systeme. Damit der Benutzer z. B. beim Online-Shopping den Systemwechsel von Produktseiten zum Einkaufswagen und zur Kasse nicht wahrnimmt, loggt er sich mittels Single Sign-On nur einmal ein. Die beteiligten Systeme authentifizieren den eingeloggten Benutzer anhand seiner Single Sign-On Session. Detaillierte Informationen über OpenID Connect und Single Sign-On findet ihr hier . In diesem Artikel fokussiere ich mich auf das Anwendungs-System, welches zur Benutzer-Authentifizierung den firmeneigenen OpenID Identity Provider verwendet. Bevor wir uns die Implementierung
Mit Spring können zeitgesteuerte Aufgaben in Java Code integriert werden. CronJobs wie wir sie in Linux kennen, definieren wir mit Spring einfach per Annotation. In diesem Artikel zeige ich wie das geht und wie Spring die CronJobs entsprechend unserer Definition ausführt. Was ist ein CronJob? Unter CronJob verstehen wir die zeitlich gesteuerte Ausführung eines Kommandos zur Erledigung einer Aufgabe bzw. eines Jobs. Das Kommando wird durch einen bestimmten Zeitpunkt oder eine zeitliche Bedingung angestoßen. Typische Beispiele für durch CronJobs gestartete Aufgaben sind: Regelmäßiges Aufräumen der Datenbank - z. B. um veraltete Daten zu löschen oder DSGVO konform persönliche Daten nach einer definierten Zeit zu löschen. Wöchentlicher Versand von Newslettern oder Werbung per Email Nächtliche Datenbank-Backups Monatliches Erstellen von Rechnungen (z.B. Telefon-Rechnung) Das Betriebssystem Linux bietet crontab zum Erstellen von CronJobs an. Mit crontab könnten wir eine Spring Anwendung star
In diesem Blog-Post besprechen wir die Basics von Spring: Spring Beans und wie man diese miteinander vernetzt bzw. referenziert (Dependency Injection). Den kompletten Code zu diesem Blog-Post findet ihr in GitHub: https://github.com/elmar-brauch/beans Spring Bean Spring Beans sind Java Objekte, die durch den Spring IoC Container instanziiert und verwaltet werden. Der IoC (Inversion of Control) Container erstellt Beans anhand einer Bean Definition, die der Entwickler in Form von Annotationen oder xml Konfiguration bereitstellt. IoC ist ein Umsetzungsparadigma und bedeutet Steuerungsumkehr. Im Kontext des Spring Frameworks versteht man darunter, dass das Framework die Erstellung des Objektnetzes (Beans) anstelle des Entwicklers übernimmt. In vorherigen Blog-Artikel haben wir bereits Projekte mit Spring Boot aufgesetzt, siehe z.B. microservices-mit-spring-boot-erstellen.html . In diesen Projekten verwendeten wir die Annotation @SpringBootApplication , welche eine Aggregation diverser
Kommentare