Clean Controller: เลิกเขียน If เช็ค null ให้รกรุงรัง จัดการ Input Validation ด้วย Bean Validation (@Valid)

February 06, 2026 · 2 min read · 0 views

หนึ่งใน "Bad Smell" ที่ผมเจอบ่อยที่สุดเวลา Code Review คือ Controller ที่เต็มไปด้วย Logic การตรวจสอบข้อมูล (Validation Logic) ครับ

เคยเห็น Code หน้าตาแบบนี้ไหมครับ?

java
@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody UserRequest request) {
    if (request.getUsername() == null || request.getUsername().isEmpty()) {
        return ResponseEntity.badRequest().body("Username is required");
    }
    if (request.getEmail() == null || !request.getEmail().contains("@")) {
        return ResponseEntity.badRequest().body("Invalid Email");
    }
    if (request.getAge() < 18) {
        return ResponseEntity.badRequest().body("Age must be 18+");
    }
    
    // ... กว่าจะเริ่ม Business Logic ได้ ก็ปาไป 20 บรรทัดแล้ว ...
    userService.register(request);
    return ResponseEntity.ok("Success");
}

Code แบบนี้ไม่ได้ผิดครับ แต่มัน "อ่านยาก" (Hard to read), "ดูแลรักษายาก" (Hard to maintain) และที่สำคัญคือ "มันไม่ใช่หน้าที่ของ Controller" ที่ต้องมานั่งเช็คเงื่อนไขยิบย่อยพวกนี้

วันนี้เราจะมาล้างบาง Code รกๆ นี้ออกไป ให้เหลือแค่ Business Logic เนื้อๆ ด้วย Spring Boot Validation และ Annotation มหัศจรรย์ที่ชื่อว่า @Valid

พระเอกของเรา: Bean Validation (JSR-380)

Spring Boot มีตัวช่วยจัดการเรื่องนี้ที่มาตรฐานมาก คือ spring-boot-starter-validation (ซึ่งข้างในใช้ Hibernate Validator) หลักการคือ เราจะย้าย Logic การเช็คค่าต่างๆ ไปแปะเป็น Annotation ไว้ที่ตัว Model หรือ DTO แทน

ขั้นตอนที่ 1: ติดตั้ง Dependency

(สำหรับใครที่ใช้ Spring Boot Starter Web ปกติมันอาจจะไม่ได้แถมมาให้ในเวอร์ชั่นใหม่ๆ ต้องเช็คดูดีๆ ครับ)

Maven:

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

ขั้นตอนที่ 2: ประกาศกฎที่ DTO

แทนที่จะเขียน If ใน Controller เรามาแปะป้ายบอกที่ DTO เลยว่า field ไหนห้ามว่าง field ไหนต้องเป็นอีเมล

java
import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class UserRequest {

    @NotBlank(message = "Username ห้ามเป็นค่าว่างนะครับ")
    @Size(min = 4, max = 20, message = "Username ต้องยาว 4-20 ตัวอักษร")
    private String username;

    @NotBlank
    @Email(message = "รูปแบบอีเมลไม่ถูกต้อง")
    private String email;

    @NotNull
    @Min(value = 18, message = "ต้องอายุ 18 ปีขึ้นไป")
    private Integer age;
}

ดูสะอาดตาขึ้นเยอะเลยใช่ไหมครับ? เราอ่านปุ๊บรู้ปั๊บว่า Field นี้ต้องการอะไร โดยไม่ต้องไปแกะ Logic

ขั้นตอนที่ 3: เปิดใช้งานด้วย @Valid ใน Controller

กลับมาที่ Controller ของเรา สิ่งที่เราต้องทำมีแค่ 2 อย่าง:

  1. ใส่ @Valid หน้า RequestBody
  2. ลบ If-Else ทิ้งให้หมด!
java
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody UserRequest request) {
    // เข้า Business Logic ได้ทันที! 
    // ถ้าข้อมูลไม่ผ่าน Validation Code บรรทัดนี้จะไม่ทำงาน
    // Spring จะดีด Error กลับไปหา User ให้เองอัตโนมัติ
    userService.register(request);
    return ResponseEntity.ok("Success");
}

แล้วถ้า Validate ไม่ผ่าน จะเกิดอะไรขึ้น?

ถ้า User ส่งข้อมูลผิดมา Spring Boot จะโยน Exception ที่ชื่อว่า MethodArgumentNotValidException ออกมาครับ

โดย Default แล้ว Spring จะตอบกลับไปเป็น JSON หน้าตาประมาณนี้ (Status 400 Bad Request):

json
{
    "timestamp": "...",
    "status": 400,
    "error": "Bad Request",
    "path": "/register"
}

Pro Tip: จัดการ Error Message ให้สวยงาม

แน่นอนว่า Error Default อาจจะดูไม่รู้เรื่องว่าตกลงผิดที่ Field ไหน เราสามารถใช้ Global Exception Handler (@ControllerAdvice) มาดักจับ Exception นี้ แล้วแปลงร่างเป็น Response สวยๆ ที่บอกรายละเอียดได้ครับ

สรุป: ทำไมต้องใช้ @Valid?

  1. Code สะอาดขึ้นมาก: Controller ทำหน้าที่แค่รับของ แล้วส่งต่อ Business Logic
  2. Reusable: DTO ตัวเดิม เอาไปใช้ที่อื่น กฎการ Validate ก็ตามไปด้วย ไม่ต้องเขียน If ซ้ำซ้อน
  3. มาตรฐาน: ทีมงานคนอื่นมาอ่านก็เข้าใจทันทีว่า Field นี้มีข้อห้ามอะไรบ้าง

เลิกเขียน If เช็ค Null พร่ำเพรื่อ แล้วหันมาใช้ @Valid กันเถอะครับ ชีวิต Developer จะมีความสุขขึ้นอีกเยอะ!


Responses (0)

No responses yet. Be the first to respond.