Chia tầng (layered) nghiệp vụ cho ứng dụng RESTful CRUD trên Spring Boot

Việc chia ứng dụng thành các tầng nghiệp vụ một cách hợp lý sẽ giúp cho chương trình dễ dàng phát triển, bảo trì, và viết Unit Test hơn.
Có nhiều cách chia tầng khác nhau phụ thuộc vào loại pattern áp dụng vào việc phát triển, các Teachinal leader sẽ là người quyết định sử dụng cách chia nào.
Tầng khi được implement trong Java có thể coi là các
package
.
Bài viết dưới đây trình bày một mô hình chia tầng thông dụng và được sử dụng nhiều trong các dự án Spring Boot thông thường.
Mình dùng từ thông thường ở đây để phân biệt với một số dự án sử dụng pattern đặc biệt như
port and adaptor
thì sẽ có cách phân tầng khác.
Mã nguồn (source code) cho bài viết các bạn có thể tìm tại đây

└───src
├───main
│ ├───java
│ │ └───com
│ │ └───koder
│ │ └───course
│ │ └───applayered
│ │ ├───api
│ │ ├───dto
│ │ ├───entity
│ │ ├───exception
│ │ ├───mapper
│ │ ├───repository
│ │ ├───service
│ │ └───validator
│ └───resources
└───test
└───java
└───com
└───koder
└───course
└───applayered
api
Đây là nơi chứa các interface
/implement
cho các endpoint
của ứng dụng, ngoài ra nó cũng có thể chứa advice controller
.
Trước khi gọi đến tầng service
để xử lý dữ liệu, thì nó sẽ gọi validator
để xác thực dữ liệu đầu vào.
advice controller là một controller hứng (lắng nghe) các ngoại lệ sảy ra và trả về thông báo lỗi (error response) tương ứng cho người dùng.
exception
Trong trường hợp ta muốn sử dụng các custom exception (mở rộng từ RuntimeException
) ta có thể định nghĩa vào đây.
nếu có thể sử dụng tối đa các exception của JDK, có gần như hầu hết các exception chung
validator
Ta viết các validator cho logic nghiệp vụ vào tầng này, mình nhấn mạnh là các nghiệp vụ của ứng dụng dạng như: kiểm tra tên không được trùng với tên của nhóm, nếu là nữ thì không quá 32 tuổi… Các validator khác như độ dài, email, … có thể tận dụng lớp valid
của JDK.
Hàm validator thường trả về
void
và throw ra exception nếu không thỏa mãn.
dto
Các POJO class cho request và response body cho APIs. Trong một số mô hình triển khai class trong DTO được sử dụng luôn làm Model cho Persistence Layer, cái này tùy tính huống có thể áp dụng.
Chúng ta lưu ý tách riêng khi:
- Số lượng trường request/response không đồng nhất với model của persistence
- Khi model của perisistence được sử lý đa luồng, khi này model sẽ cần tính
bất biến
(immutable), do vậy ta nghĩ đến case sử dụng kiểurecord
cho model của persistence
mapper
Chứa các class tiện ích (Util Classes) cho phép mapping qua lại giữa DTO và Model.
Có nhiều cách viết mapper, có thể sử dụng build-in của JDK, các thư viện như myBatis,… nếu như phải viết manual, mình thấy quy tắc đặt tên sau có thể áp dụng được, và cũng dễ hiểu:
- bắt đầu tên hàm với
map***
- tên param là
from
- tên trả về là
to
- khi mapping list to list sử dụng tối đa khả năng chạy đa luồng của Java
entity
và repository
Thường đi một cặp với nhau, thể hiện cho model của persistence layer. Khi ta làm việc với các persistence khác nhau như: Redis, Elasticseach, Mongo, Postgres… thì đểu được thể hiện qua Entity và Repository.
Khi ta tìm hiểu thêm về Reactive Programming thì cặp Entity và Repository cũng được thể hiện trong lập trình Reactive. Phần này sẽ có các bài riêng về reactive (non blocking) programing.
service
Là tầng vận hành logic nghiệp vụ của ứng dụng. Nó kết nối với các tầng khác như mapper
hoặc repository
hay gọi qua các dịch vụ bên ngoài để xử lý nghiệp vụ.
Tổng kết
- Tận dụng những công cụ AI như một Coding Assistant hiệu quả (bạn có thể đọc bài này để tìm hiểu thêm)
- Cố gắng sử dụng loombok nhiều nhất có thể để cho code được gọn gàng hơn
- Với Entity cần nghĩ đến việc sử dụng
record
nếu trong sử lý dữ liệu có xử lý đa luồng - Nên tài liệu hóa đầy đủ API bằng OpenAPI/Swagger document
Related Posts