Backend

99 Ngày Spring — Ngày 98: Best Practices Tổng hợp

SSite Admin
2 tháng 11, 2026 3 phút đọc 5 lượt xem
99 Ngày Spring — Ngày 98: Best Practices Tổng hợp

Sau 97 ngày, phần khó không còn là biết thêm một annotation mà là dùng nhất quán những gì đã biết. Bài này gom lại ba thứ: cách tổ chức một project Spring Boot để nó lớn lên mà không rối, những lỗi lặp lại nhiều nhất trong các bài trước, và một checklist ngắn để rà mỗi pull request.

Best Practices Tổng hợp

1. Cấu trúc project

Đặt lớp có @SpringBootApplication ở package gốc để component scan bao phủ mọi thứ bên dưới. Chia package theo nghiệp vụ; trong mỗi package, controller chỉ chuyển đổi HTTP, service giữ quy tắc nghiệp vụ và transaction, repository lo truy cập dữ liệu. DTO ở biên API tách khỏi entity, để thay đổi schema không vô tình đổi hợp đồng với client.

vn.motdev.shop
├── ShopApplication.java
├── order
│   ├── OrderController.java      // HTTP <-> DTO
│   ├── OrderService.java         // rules + @Transactional
│   ├── OrderRepository.java
│   ├── Order.java                // entity
│   └── dto/OrderResponse.java
├── payment
└── shared
    ├── config                    // @ConfigurationProperties, security
    └── error                     // @RestControllerAdvice, ProblemDetail

2. Lỗi hay gặp về bean và transaction

Field injection giấu phụ thuộc và làm lớp khó test; constructor injection với trường final là mặc định nên dùng. @Transactional hoạt động qua proxy, nên nó không có tác dụng khi một phương thức gọi phương thức khác trong cùng lớp, hoặc khi đặt trên phương thức private. Mặc định chỉ RuntimeException mới gây rollback. Giữ transaction ngắn và không gọi API ngoài bên trong nó.

@Service
class OrderService {
    private final OrderRepository orders;
    private final PaymentGateway payments;

    OrderService(OrderRepository orders, PaymentGateway payments) {
        this.orders = orders;
        this.payments = payments;
    }

    @Transactional
    public OrderResponse place(PlaceOrder command) {
        var order = orders.save(Order.from(command));
        return OrderResponse.from(order);
    }
}

3. Lỗi hay gặp về dữ liệu và API

Trả entity JPA thẳng ra controller kéo theo lazy loading ngoài ý muốn, vòng lặp JSON và lộ trường nội bộ. N+1 truy vấn và danh sách không phân trang chạy tốt với mười hàng dữ liệu test rồi sập ở production. Lỗi nên có một định dạng thống nhất qua @RestControllerAdvice và ProblemDetail. Dữ liệu vào phải được kiểm tra bằng @Valid ngay ở controller.

  • Tắt spring.jpa.open-in-view và nạp dữ liệu có chủ đích trong service.

  • Mọi endpoint trả danh sách đều nhận Pageable.

  • Đặt timeout cho mọi client HTTP và mọi truy vấn.

  • Không bắt Exception chung rồi im lặng; log một lần và trả lỗi có nghĩa.

  • Cấu hình qua @ConfigurationProperties có @Validated, không rải @Value.

4. Checklist review

Một checklist ngắn và được dùng thật tốt hơn một tài liệu dài không ai đọc. Dưới đây là những câu hỏi đáng hỏi với mỗi pull request của một service Spring Boot; phần lớn đã có một bài riêng trong series.

  • Thay đổi có test ở đúng tầng không: unit cho logic, slice hoặc Testcontainers cho tích hợp?

  • Ranh giới transaction có rõ, và có lời gọi ra ngoài nào nằm bên trong không?

  • Truy vấn mới có index, có phân trang, có gây N+1 không?

  • Endpoint mới đã có phân quyền, validation và định dạng lỗi thống nhất chưa?

  • Có secret, dữ liệu cá nhân hay stack trace nào bị ghi ra log hoặc response không?

  • Lời gọi ra ngoài có timeout, retry có giới hạn và xử lý trùng lặp chưa?

  • Thay đổi schema có tương thích ngược với phiên bản đang chạy không?

  • Có metric hoặc log đủ để biết tính năng này hỏng ở production không?

Thực hành tốt không phải là biết nhiều hơn mà là làm đều những điều cơ bản. Ngày 99 khép lại series bằng một capstone để bạn dùng tất cả cùng lúc.

Tài liệu đối chiếu

  • https://docs.spring.io/spring-boot/reference/using/structuring-your-code.html

  • https://docs.spring.io/spring-boot/reference/using/spring-beans-and-dependency-injection.html

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Java — Ngày 98: HTTP Client

java.net.http với HttpClient, HttpRequest và HttpResponse: gọi đồng bộ, bất đồng bộ, timeout, xử lý lỗi và gọi một REST API thực tế.

2 thg 11, 20263 phút6
99 Ngày Spring — Ngày 97: Security Hardening cho Production

Security header, không lộ thông tin qua lỗi và Actuator, quét dependency và nguyên tắc quyền tối thiểu từ database tới container.

1 thg 11, 20263 phút0
99 Ngày Java — Ngày 97: JDBC

Connection và PreparedStatement, chống SQL injection, transaction thủ công và vì sao luôn cần connection pool.

1 thg 11, 20263 phút0