Spring WebFlux / Reactor

Diese Seite bleibt auch ohne JavaScript lesbar. Suche und Buttons sind Zusatzkomfort.

Spring WebFlux und Reactive Streams

Nicht-blockierende Architektur verstehen, ohne jede Anwendung unnötig reactive zu machen.

WebFlux: nicht-blockierende Verarbeitung als Pipeline RequestEvent Loop Mono/FluxTransformation Non-blocking IOWebClient/R2DBC BackpressureDruck steuern Nicht jedes Enterprise-System braucht Reactive. Es lohnt sich bei vielen gleichzeitigen IO-Wartezeiten.
1. Wann Reactive sinnvoll ist

Reactive lohnt sich, wenn viele parallele Requests überwiegend auf IO warten: externe APIs, Streaming, WebSockets, nicht-blockierende Datenbanktreiber oder lange laufende Datenflüsse. Für klassische CRUD-Systeme mit blockierendem JDBC/JPA ist WebFlux oft zusätzlicher Denkaufwand ohne klaren Gewinn.

Das größte Risiko ist Mischbetrieb: Ein blockierender Aufruf in einer Event-Loop kann die Skalierung zerstören. Deshalb muss bei WebFlux die gesamte Kette betrachtet werden: Controller, Service, HTTP-Clients, Datenbanktreiber, Messaging und Threading.

Reactive Controller mit Mono
@RestController
@RequestMapping("/api/customers")
class CustomerReactiveController {
    private final CustomerClient customerClient;

    CustomerReactiveController(CustomerClient customerClient) {
        this.customerClient = customerClient;
    }

    @GetMapping("/{id}/risk")
    Mono<RiskResponse> risk(@PathVariable String id) {
        return customerClient.fetchCustomer(id)
                .flatMap(customer -> customerClient.fetchRisk(customer.riskProfileId()))
                .map(risk -> new RiskResponse(id, risk.score()));
    }
}
WebClient mit Timeout und Fehlerabbildung
@Component
class CustomerClient {
    private final WebClient webClient;

    CustomerClient(WebClient.Builder builder) {
        this.webClient = builder.baseUrl("https://customer.internal").build();
    }

    Mono<CustomerDto> fetchCustomer(String id) {
        return webClient.get()
                .uri("/customers/{id}", id)
                .retrieve()
                .onStatus(HttpStatusCode::is5xxServerError,
                    response -> Mono.error(new RemoteSystemUnavailableException("customer")))
                .bodyToMono(CustomerDto.class)
                .timeout(Duration.ofSeconds(2));
    }
}
2. Backpressure und fachliche Realität

Backpressure heißt: ein Consumer kann signalisieren, wie viel er verarbeiten kann. Das schützt Systeme vor Überlast. Fachlich ersetzt es aber keine Idempotenz, keine fachliche Reihenfolge und keine Retry-Strategie.

3. Debugging Reactive

Reactive-Code ist schwerer linear zu lesen. Daher sind StepVerifier-Tests, strukturierte Operator-Ketten, klare Namen und gezieltes Logging wichtig. Nicht jede Business-Regel muss in einer einzigen langen Chain stehen.

StepVerifier Beispiel
@Test
void calculatesRisk() {
    Mono<RiskResponse> result = service.calculateRisk("C-42");

    StepVerifier.create(result)
            .expectNextMatches(r -> r.customerId().equals("C-42") && r.score() >= 0)
            .verifyComplete();
}

Enterprise-Prüffragen

  • Gibt es blockierende Aufrufe in der reactive Pipeline?
  • Ist Timeout/Retry begrenzt?
  • Sind Mono/Flux-Ketten testbar?
  • Ist Reactive fachlich begründet oder nur Mode?