5 Lỗi Spring Boot Mình Thấy Ở Gần Như Mọi Codebase
Mình đã review và tham gia khá nhiều dự án Spring Boot — từ startup nhỏ đến enterprise lớn. Có một điều khá buồn cười là dù team junior hay senior, mình vẫn thường xuyên gặp đi gặp lại cùng một nhóm vấn đề.
Không phải bug “nổ production” ngay lập tức. Nhưng nếu để tích tụ theo thời gian, chúng sẽ khiến codebase:
- khó maintain
- khó test
- khó scale
- và cực kỳ dễ sinh bug về sauSpring Boot
Dưới đây là 5 lỗi mình thấy nhiều nhất.
1. Field Injection – @Autowired trên field
Đây gần như là lỗi phổ biến nhất mình gặp.
@RestController
public class UserController {
@Autowired
private UserService userService;
}
Nhìn thì gọn thật. Nhưng vấn đề là:
- dependency bị “ẩn”
- khó unit test
- khó maintain
- vi phạm nguyên tắc explicit dependency
Spring đã khuyến khích constructor injection từ rất lâu rồi, nhưng nhiều codebase cũ vẫn giữ thói quen field injection vì “viết ít hơn”.
Cách mình thường dùng
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
}
Hoặc viết constructor thủ công nếu không dùng Lombok.
2. Trả Entity trực tiếp ra API
Đây là lỗi kinh điển mà surprisingly mình vẫn gặp rất nhiều.
return ResponseEntity.ok(user);
Thoạt đầu có vẻ tiện. Nhưng khi project lớn dần, hậu quả bắt đầu xuất hiện:
- lộ field nhạy cảm (
password,resetToken, ...) LazyInitializationException- API contract bị phụ thuộc vào database schema
- over-fetching / under-fetching
- khó version API
Mình từng gặp một case entity User bị trả thẳng ra API và vô tình expose luôn internal flag dùng cho admin system.
Những lỗi kiểu này thường không ai để ý cho tới lúc production có vấn đề.
Cách xử lý tốt hơn
Tạo DTO riêng cho request/response.
Ví dụ:
public record UserResponse(
Long id,
String username,
String email
) {}
Nếu project nhiều mapper, mình khá thích dùng MapStruct để tránh viết mapping thủ công.
3. Fat Controller
Một số controller mình từng thấy làm luôn tất cả:
- validate dữ liệu
- gọi repository
- xử lý business logic
- xử lý exception
- mapping response
Kết quả:
UserController.java → 700 dòng
Đọc cực mệt. Test cũng cực mệt.
Rule mình hay dùng
Controller chỉ nên làm 3 việc:
- Nhận request + validate
- Gọi service
- Trả response
Business logic nên nằm ở Service layer (hoặc Domain layer nếu hệ thống phức tạp).
Controller càng mỏng thì codebase càng dễ maintain.
4. Exception Handling kiểu “cục bộ”
Đây là pattern mình gặp rất nhiều:
try {
...
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
Hoặc tệ hơn:
throw new RuntimeException("Something went wrong");
Hậu quả:
- response format không đồng nhất
- log khó đọc
- khó tracking lỗi
- frontend không biết phải handle kiểu gì
Cách mình thường tổ chức
Dùng:
@ControllerAdvice@ExceptionHandler
Ví dụ:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> handleNotFound(...) {
...
}
}
Và tạo custom exception rõ nghĩa:
ResourceNotFoundExceptionBusinessExceptionInvalidRequestException
Khi API có error format thống nhất, frontend và monitoring đều dễ sống hơn rất nhiều.
5. Dùng @Transactional không đúng cách
Đây là lỗi khó thấy hơn, nhưng hậu quả thường khá đau.
Ví dụ phổ biến:
- đặt transaction cho method chỉ đọc
- self-invocation khiến transaction không hoạt động
- wrap luôn external API call bên trong transaction
- transaction scope quá lớn
Ví dụ kiểu này:
@Transactional
public void processOrder() {
orderRepository.save(order);
paymentClient.call(); // network I/O
emailService.send();
}
Nếu external API chậm:
- DB connection bị giữ lâu
- connection pool dễ nghẽn
- throughput giảm mạnh
Một vài rule mình hay áp dụng
Query-only → dùng readOnly
@Transactional(readOnly = true)
Transaction càng nhỏ càng tốt
Chỉ bao quanh phần thật sự cần atomicity.
Tránh network call bên trong transaction
Đặc biệt là:
- payment gateway
- third-party API
Kết luận
Phần lớn codebase không “chết” vì thiếu design pattern.
Nó chết dần vì hàng trăm quyết định kiểu:
“Code này tạm ổn rồi.”
Những lỗi trên không phải lúc nào cũng gây bug ngay lập tức. Nhưng khi project bắt đầu scale:
- team đông hơn
- feature nhiều hơn
- traffic lớn hơn
thì chúng sẽ trở thành technical debt rất khó chịu.
Nếu đang làm Spring Boot, thử review lại codebase hiện tại xem có bao nhiêu lỗi trong danh sách trên.
Mình đoán đa số project sẽ dính ít nhất 2–3 cái 😄
All rights reserved