Error Handling ใน Spring WebFlux ด้วย Functional Approach

Sharing is caring!

ในยุคที่การพัฒนาแอปพลิเคชันเปลี่ยนไปสู่แนวทาง Reactive มากขึ้น Spring WebFlux กลายเป็นตัวเลือกสำคัญของนักพัฒนา Java ที่ต้องการรองรับการประมวลผลแบบ Asynchronous และ Non-Blocking ซึ่งมีประโยชน์มากเมื่อรับโหลดจำนวนมาก

บทความนี้จะพาคุณไปเรียนรู้การจัดการข้อผิดพลาด (Error Handling) ใน Spring WebFlux โดยใช้แนวทางแบบ Functional ที่ใช้ร่วมกับ RouterFunction และ HandlerFunction ซึ่งต่างจากแนว MVC แบบเดิม

🔍 ทำไมต้องจัดการ Error แบบ Functional?

  • เหมาะสำหรับ WebFlux Routing ที่ใช้ RouterFunction
  • รองรับ Mono และ Flux ได้อย่างสมบูรณ์
  • แยกการจัดการ Error ออกจาก logic หลัก
  • รองรับรูปแบบ JSON Response ที่กำหนดเอง

🧱 โครงสร้าง Router และ Handler

@Configuration
public class RouterConfig {
  @Bean
  public RouterFunction<ServerResponse> route(MyHandler handler) {
    return RouterFunctions.route()
        .GET("/api/hello", handler::hello)
        .GET("/api/user/{id}", handler::getUser)
        .build();
  }
}
@Component
public class MyHandler {
  public Mono<ServerResponse> hello(ServerRequest request) {
    return ServerResponse.ok().bodyValue("Hello WebFlux!");
  }

  public Mono<ServerResponse> getUser(ServerRequest request) {
    String id = request.pathVariable("id");
    if (id.equals("0")) {
      throw new NotFoundException("User ID not found");
    }
    return ServerResponse.ok().bodyValue("User ID: " + id);
  }
}

🛑 สร้าง Custom Exception

public class NotFoundException extends RuntimeException {
  public NotFoundException(String message) {
    super(message);
  }
}

⚙️ สร้าง Global Error Handler

@Component
@Order(-2)
public class GlobalErrorHandler extends AbstractErrorWebExceptionHandler {

  public GlobalErrorHandler(ErrorAttributes errorAttributes,
                            ApplicationContext applicationContext,
                            ServerCodecConfigurer configurer) {
    super(errorAttributes, new WebProperties.Resources(), applicationContext);
    super.setMessageReaders(configurer.getReaders());
    super.setMessageWriters(configurer.getWriters());
  }

  @Override
  protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
    return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
  }

  private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    Map<String, Object> errorProps = getErrorAttributes(request, ErrorAttributeOptions.defaults());
    return ServerResponse
        .status((int) errorProps.getOrDefault("status", 500))
        .contentType(MediaType.APPLICATION_JSON)
        .bodyValue(Map.of(
            "error", errorProps.get("error"),
            "message", errorProps.get("message"),
            "path", errorProps.get("path"),
            "status", errorProps.get("status")
        ));
  }
}

✨ Custom ErrorAttributes (Optional)

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
  @Override
  public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
    Throwable error = getError(request);
    return Map.of(
        "message", error.getMessage(),
        "error", error.getClass().getSimpleName(),
        "timestamp", Instant.now().toString(),
        "status", 500
    );
  }
}

💬 ตัวอย่าง Response เมื่อเกิด Error

{
  "error": "NotFoundException",
  "message": "User ID not found",
  "status": 500,
  "path": "/api/user/0"
}

🌐 ใช้ร่วมกับ WebClient

webClient.get()
    .uri("/external-api")
    .retrieve()
    .onStatus(HttpStatus::is4xxClientError, resp ->
        Mono.error(new RuntimeException("Client error")))
    .onStatus(HttpStatus::is5xxServerError, resp ->
        Mono.error(new RuntimeException("Server error")))
    .bodyToMono(String.class);

📊 สรุปแนวทาง

  • แยก Logic Error ออกจาก Handler อย่างชัดเจน
  • ปรับแต่งรูปแบบ Error Response ได้ตามต้องการ
  • ไม่ต้องพึ่ง @ControllerAdvice แบบใน MVC
  • ทำงานร่วมกับ Mono/Flux ได้ลื่นไหล

📸 ภาพประกอบแนะนำ

  • WebFlux Error Lifecycle
  • GlobalErrorHandler Diagram
  • Custom Error Response Flow

🔎 คำค้น SEO

Spring WebFlux Error Handling, Functional Router WebFlux, GlobalErrorWebExceptionHandler, Spring Boot Reactive Exception, Spring ErrorAttributes Custom, JSON error response Spring WebFlux, Spring WebFlux Mono Flux Error, Functional Exception Flow

Leave a Reply

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *