Clean Controller: เลิกเขียน If เช็ค null ให้รกรุงรัง จัดการ Input Validation ด้วย Bean Validation (@Valid)
หนึ่งใน "Bad Smell" ที่ผมเจอบ่อยที่สุดเวลา Code Review คือ Controller ที่เต็มไปด้วย Logic การตรวจสอบข้อมูล (Validation Logic) ครับ
เคยเห็น Code หน้าตาแบบนี้ไหมครับ?
@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:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
ขั้นตอนที่ 2: ประกาศกฎที่ DTO
แทนที่จะเขียน If ใน Controller เรามาแปะป้ายบอกที่ DTO เลยว่า field ไหนห้ามว่าง field ไหนต้องเป็นอีเมล
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 อย่าง:
- ใส่
@Validหน้า RequestBody - ลบ If-Else ทิ้งให้หมด!
@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):
{
"timestamp": "...",
"status": 400,
"error": "Bad Request",
"path": "/register"
}
Pro Tip: จัดการ Error Message ให้สวยงาม
แน่นอนว่า Error Default อาจจะดูไม่รู้เรื่องว่าตกลงผิดที่ Field ไหน เราสามารถใช้ Global Exception Handler (@ControllerAdvice) มาดักจับ Exception นี้ แล้วแปลงร่างเป็น Response สวยๆ ที่บอกรายละเอียดได้ครับ
สรุป: ทำไมต้องใช้ @Valid?
- Code สะอาดขึ้นมาก: Controller ทำหน้าที่แค่รับของ แล้วส่งต่อ Business Logic
- Reusable: DTO ตัวเดิม เอาไปใช้ที่อื่น กฎการ Validate ก็ตามไปด้วย ไม่ต้องเขียน If ซ้ำซ้อน
- มาตรฐาน: ทีมงานคนอื่นมาอ่านก็เข้าใจทันทีว่า Field นี้มีข้อห้ามอะไรบ้าง
เลิกเขียน If เช็ค Null พร่ำเพรื่อ แล้วหันมาใช้ @Valid กันเถอะครับ ชีวิต Developer จะมีความสุขขึ้นอีกเยอะ!
No responses yet. Be the first to respond.