Module 08 · Executable guide
Spring WebFlux
Xây reactive backend trong Spring ecosystem với controllers, WebClient, R2DBC, streaming, security và testing.
1. Architecture
Client → Reactor Netty → WebFlux → Controller/Handler → Service
↓
R2DBC / WebClient
WebFlux chỉ non-blocking end-to-end khi downstream không block hoặc blocking work được cô lập đúng.
2. Annotated Controller
@RestController
@RequestMapping("/users")
class UserController {
@GetMapping("/{id}")
Mono<UserDto> get(@PathVariable long id) {
return service.find(id);
}
@GetMapping
Flux<UserDto> list() {
return service.findAll();
}
}3. Functional Endpoints
RouterFunction<ServerResponse> routes(UserHandler h) {
return route()
.GET("/users/{id}", h::get)
.POST("/users", h::create)
.build();
}4. WebClient
return webClient.post()
.uri("/payments")
.bodyValue(req)
.retrieve()
.onStatus(HttpStatusCode::is5xxServerError,
response -> Mono.error(new PaymentUnavailable()))
.bodyToMono(PaymentResponse.class)
.timeout(Duration.ofSeconds(2));5. R2DBC
interface OrderRepository
extends ReactiveCrudRepository<OrderEntity, Long> {
Flux<OrderEntity> findByUserId(long userId);
}
WebFlux + JDBC không tự nhiên thành reactive. JDBC blocking trên event loop có thể gây latency.
6. Reactive transaction
Reactive transaction context không nên dựa vào ThreadLocal như imperative flow vì pipeline có thể đổi execution context.
7. Error handling
Service layer xử lý domain recovery bằng operators; HTTP boundary map domain errors thành 400/404/409/503 phù hợp.
8. Security
Reactive security chain cũng phải tránh blocking authentication lookup trong request path.
9. SSE
@GetMapping(value="/orders/{id}/events",
produces=MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<ServerSentEvent<OrderEvent>> events(@PathVariable long id) {
return eventService.stream(id)
.map(e -> ServerSentEvent.builder(e).build());
}10. Testing
webTestClient.get()
.uri("/users/42")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.id").isEqualTo(42);11. Anti-patterns
block()trong request path.- Blocking I/O trên Reactor Netty event loop.
- Retry payment không idempotent.
- Dùng parallel scheduler cho blocking call.
12. Lab
- GET /users/{id}/profile.
- Load user bằng R2DBC.
- Gọi Order Service bằng WebClient.
- Zip thành profile.
- Timeout 700 ms.
- Fallback orders=[] khi downstream timeout.
- Test 200/404/timeout.