Spring WebFlux, WebClient und Reactive Streams
Spring WebFlux ist das reaktive Webframework für nicht-blockierende HTTP-Workloads, Streaming und Backpressure-orientierte Verarbeitung.
Fachliche Einordnung
WebFlux ist sinnvoll, wenn viele I/O-bound Operationen parallel laufen, etwa Gateway, Streaming, Chat, SSE oder reaktive Datenquellen. Für klassische JPA-Workloads ist MVC oft einfacher.
Technische Darstellung
Kernkonzepte
- Reactor Mono und Flux als asynchrone Datenströme.
- Netty oder Servlet 3.1+ als non-blocking Runtime.
- WebClient als moderner HTTP-Client.
- Backpressure und Operatoren wie map, flatMap, timeout und retryWhen.
- Trennung zwischen reactive end-to-end und blockierenden Brücken.
Wann einsetzen?
- Du brauchst hohe Nebenläufigkeit bei externen I/O-Aufrufen.
- Du baust Streaming APIs oder Server-Sent Events.
- Du nutzt R2DBC oder reaktive Treiber.
Typische Fehler und Risiken
- Blocking JPA oder Thread.sleep in reaktiven Pipelines.
- subscribe() im Anwendungscode statt Rückgabe von Mono/Flux.
- Reactive nur wegen Trend nutzen, obwohl Team und Stack blockierend sind.
Legacy- und Modernisierungssicht
Alte SOAP/REST-Clients mit hoher Latenz können in einer WebClient-Fassade gekapselt werden; blockierende Adapter gehören dann auf boundedElastic oder in separate Services.
Ausführliches Beispiel
Das Beispiel zeigt bewusst nicht nur Annotationen, sondern auch die Verantwortung der Schicht. In echten Projekten sollte der technische Spring-Code an Adapter- oder Konfigurationsrändern bleiben, während die Fachlogik testbar und möglichst frameworkarm bleibt.
@Service
class CustomerCreditClient {
private final WebClient webClient;
CustomerCreditClient(WebClient.Builder builder) {
this.webClient = builder.baseUrl("https://credit.example.internal").build();
}
Mono<CreditDecision> check(CustomerId id) {
return webClient.get()
.uri("/credits/{id}", id.value())
.retrieve()
.bodyToMono(CreditDecision.class)
.timeout(Duration.ofSeconds(2))
.retryWhen(Retry.backoff(2, Duration.ofMillis(150)));
}
}
@RestController
class CreditController {
@GetMapping("/api/customers/{id}/credit")
Mono<CreditDecision> credit(@PathVariable String id, CustomerCreditClient client) {
return client.check(new CustomerId(id));
}
}
Checkliste für Reviews
- Ist die Verantwortung des Bausteins klar: Framework steuert Lebenszyklus, Library wird gezielt benutzt?
- Ist die Fachlogik außerhalb von Controller, Listener, Repository-Implementierung oder Konfiguration?
- Sind Fehlerfälle, Timeouts, Security, Monitoring und Tests sichtbar modelliert?
- Gibt es klare Grenzen zwischen DTO, Domäne, Persistence und Infrastruktur?