WebClient, WebFlux và reactive execution
Reactive stack tối ưu cho nhiều concurrent I/O khi toàn call path tôn trọng non-blocking execution. Nó không biến blocking dependency thành non-blocking chỉ bằng cách trả về Mono hoặc Flux.
Reactive Streams
Publisher phát onNext, onError và onComplete theo demand từ Subscriber. Mono biểu diễn 0..1 items, Flux 0..N. Pipeline lazy cho tới khi subscription xảy ra. Backpressure điều phối demand giữa reactive stages nhưng không tự giới hạn external API hoặc memory nếu operator buffering không bounded.
WebFlux architecture
WebFlux hỗ trợ annotated controllers và functional endpoints trên non-blocking runtime. Thay vì thread-per-request, một số ít event-loop threads xử lý nhiều connections; blocking call trên event loop làm trì hoãn hàng loạt requests chứ không chỉ request hiện tại.
| Mô hình | Phù hợp | Rủi ro |
|---|---|---|
| Servlet MVC | Blocking ecosystem, JPA/JDBC, code imperative. | Nhiều blocked threads khi concurrency cao. |
| WebFlux | End-to-end non-blocking I/O, streaming, many concurrent connections. | Event-loop stalls nếu blocking; reactive debugging/operations phức tạp hơn. |
| MVC + virtual threads | Imperative blocking stack cần concurrency lớn trên Java hiện đại. | Không loại connection-pool hoặc downstream capacity limits. |
WebClient configuration
WebClient được cấu hình qua builder, filters, codecs và exchange strategies. Production client cần connection pool, connect/response/read/write timeout, max in-memory body, TLS/DNS policy và observability.
client.get().uri("/orders/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is5xxServerError, this::mapRemoteError)
.bodyToMono(OrderDto.class)
.timeout(Duration.ofSeconds(2));
retrieve() tiện cho status/body phổ biến; exchangeToMono() cho quyền kiểm soát response đầy đủ. Khi dùng API mức thấp, phải bảo đảm body/resource được consume hoặc release.
Blocking boundary
JDBC/JPA, legacy file I/O và blocking SDK vẫn block khi được bọc trong Mono. Có thể offload tạm thời sang boundedElastic, nhưng queue và threads vẫn hữu hạn. Nếu phần lớn stack blocking, MVC hoặc virtual threads có thể đơn giản và dễ vận hành hơn.
block() trên event loop: nó có thể deadlock hoặc làm throughput collapse. Boundary imperative chỉ nên block ở nơi thread model cho phép và có timeout.Context, errors và cancellation
Reactor Context giữ metadata subscriber-specific thay vì dựa vào ThreadLocal. Error là terminal signal; onErrorResume chỉ dùng cho fallback có semantics rõ. Retry cần idempotency, backoff, jitter và shared deadline. Cancellation nên propagate tới HTTP request hoặc upstream operation nếu client hỗ trợ.
Backpressure và buffering
Backpressure không bảo vệ một external service không hỗ trợ demand. Operators như buffer, window, flatMap và prefetch phải có concurrency/capacity phù hợp. Khi producer không thể chậm lại, cần drop, latest, bounded queue hoặc load shedding policy cùng metrics.
Testing reactive behavior
StepVerifier kiểm signals, error, completion và timing; virtual time phù hợp delay/retry. Integration test với server/client thật cần thiết cho codecs, pool, disconnect, cancellation và streaming. BlockHound có thể phát hiện blocking call nếu được cấu hình tương thích.