# Chương 2: Hướng dẫn phát triển (Developer)

Chương này mô tả cấu trúc source code, quy ước coding, cách tạo module mới, cách viết API/UI, quy trình commit/branch, và cách chạy/debug dự án trong quá trình phát triển. Mục tiêu giúp developer hiểu kiến trúc và đóng góp code đúng chuẩn.

# Luồng xử lý API Backend (API Processing Flow)

### 1.1. Mục tiêu chương.

  
Mô tả luồng xử lý chuẩn của một API backend từ khi nhận request đến khi deploy production nhằm:

Thống nhất cách xây dựng API giữa các team

Đảm bảo tuân thủ đầy đủ các quy định kiến trúc

Dễ trace lỗi và audit

Liên kết các chuẩn đã quy định ở các chương trước

### 1.2. Phạm vi áp dụng:  


Áp dụng cho:

Tất cả REST API trong modules/\*\*

Core Team và Partner Team

Tất cả service Spring Boot

### 1.3. Tổng quan luồng xử lý API

  
Luồng chuẩn:

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/Tbximage.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/Tbximage.png)

###   
1.4. Các bước xử lý chi tiết

####   
Bước 1. Client gọi API

  
Ví dụ:

POST /api/v1/users  
Payload:

{  
 "username": "test",  
 "password": "123456"  
}  
Quy định liên quan:

👉 Chương 7 — API Design &amp; Response Standard

#### Bước 2. Security / Authentication

  
Hệ thống kiểm tra:

JWT / SSO token

Permission

Role

Nếu fail:

401 Unauthorized  
403 Forbidden  
Quy định liên quan:

👉 Chương 8 — Cơ chế xác thực &amp; SSO (WSO2)

#### Bước 3. Controller nhận request

@PostMapping  
public ApiResponse&lt;UserResponse&gt; create(  
 @Valid @RequestBody UserCreateRequest request) {  
 return ApiResponse.ok(userService.create(request));  
}  
Quy định:

Name Controller

Không chứa business logic

👉 Chương 3 — Coding Convention  
👉 Chương 7 — API Design

#### Bước 4. DTO Validation

  
DTO:

public class UserCreateRequest {

 @NotBlank  
 @Size(max = 100)  
 private String username;

 @NotBlank  
 private String password;  
}  
Spring tự validate trước khi vào service.

Nếu lỗi:

400 Bad Request  
Response chuẩn:

  
{  
 "code": "VALIDATION\_ERROR",  
 "message": "username is required"  
}  
Quy định:

👉 Chương 3 — Coding Convention (Validation)  
👉 Chương 7 — Response Standard

#### Bước 5. Service xử lý logic

public UserResponse create(UserCreateRequest req) {  
 log.info("Create user {}", req.getUsername());

 UserEntity e = mapper.toEntity(req);  
 repo.save(e);

 return mapper.toResponse(e);  
}  
Cấu trúc chuẩn 1 hàm service:

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/qalimage.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/qalimage.png)

  
Quy định:

👉 Chương 3 — Coding Convention  
👉 Chương 4 — Entity &amp; Database

#### Bước 6. Repiository &amp; Database

userRepository.save(entity);  
Nếu thay đổi schema:

update entity

migration script

Quy định:

👉 Chương 4 — Thay đổi Entity &amp; DB  
👉 Chương 5 — Schema &amp; Migration

#### Bước 7. Mapping Response

return UserResponse.builder()  
 .id(e.getId())  
 .username(e.getUsername())  
 .build();  
Response chuẩn:

  
{  
 "code": "SUCCESS",  
 "data": {  
 "id": 1,  
 "username": "test"  
 }  
}  
Quy định:

👉 Chương 7 — Response Standard

#### Bước 8. Logging &amp; Audit

  
Trong service:

  
log.info("User created id={}", e.getId());  
Nếu nghiệp vụ quan trọng:

ghi audit\_log

Quy định:

👉 Chương 3 — Logging  
👉 Chương 6 — Audit Log

#### Bước 9. Exception Handling

  
Ví dụ:

  
if (exists) {  
 throw new BusinessException("USER\_EXISTS");  
}  
Global handler:

  
@ExceptionHandler(BusinessException.class)  
Response:

  
{  
 "code": "USER\_EXISTS",  
 "message": "User already exists"  
}  
Quy định:

👉 Chương 7 — Response &amp; Error

### 1.5. Luồng phát triển &amp; release API

  
Sau khi code xong:

  
Dev → Commit → PR → Review → Merge → CI Build → Deploy

####   
Bước 10. Commit &amp; PR

  
Quy định:

👉 Chương 9 — Git Workflow &amp; Pull Request

#### Bước 11. Checklist trước merge

  
Quy định:

👉 Chương 10 — Checklist merge

#### Bước 12. Deploy &amp; Update DB

  
Nếu có migration:

chạy script

deploy service

Quy định:

👉 Chương 5 — Migration  
👉 Chương 12 — Connect DB

### 1.6. Sơ đồ tổng thể API lifecycle

  
https://docs.lifetex.vn/link/333#bkmrk-request-%E2%86%93-auth-%28ch8%29

Request  
 ↓  
Auth (Ch8)  
 ↓  
Controller (Ch3,7)  
 ↓  
DTO Validate (Ch3)  
 ↓  
Service Logic (Ch3)  
 ↓  
Entity/DB (Ch4,5)  
 ↓  
Mapping (Ch7)  
 ↓  
Logging/Audit (Ch3,6)  
 ↓  
Response (Ch7)  
 ↓  
Git/PR (Ch9,10)  
 ↓  
Deploy (Ch5,12)

### 1.7. Transaction trong xử lý API

#### 1.7.1. Nguyên tắc transaction

Mọi thao tác thay đổi dữ liệu nghiệp vụ trong API phải được thực hiện trong transaction nhằm đảm bảo:

- Tính toàn vẹn dữ liệu
- Tính nhất quán hệ thống
- Khả năng rollback khi lỗi
- Đồng bộ dữ liệu và audit log

Transaction phải được đặt tại **Service layer**.

---

#### 1.7.2. Quy định transaction

Transaction:

- đặt tại Service
- bao phủ toàn bộ nghiệp vụ
- bao gồm repository và audit
- rollback khi exception

Không được đặt transaction tại:

- Controller
- Repository
- Mapper

---

#### 1.7.3. Ví dụ transaction chuẩn

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40transactional-publi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Transactional</span><span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(UserCreateRequest req)</span> {    <span class="hljs-keyword">if</span> (repo.existsByUsername(req.getUsername())) {        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(<span class="hljs-string">"USER_EXISTS"</span>);    }    <span class="hljs-type">UserEntity</span> <span class="hljs-variable">e</span> <span class="hljs-operator">=</span> mapper.toEntity(req);    repo.save(e);    auditService.logCreate(<span class="hljs-string">"USER"</span>, e.getId());    <span class="hljs-keyword">return</span> mapper.toResponse(e);}`</div></div>---

#### 1.7.4. Quy tắc rollback

Transaction phải rollback khi:

- BusinessException
- RuntimeException
- Database error
- Validation fail trong service

Không được:

- ghi audit trước khi save dữ liệu
- commit từng phần nghiệp vụ
- xử lý lỗi nhưng vẫn commit

---

### 1.8. Security Context trong Service

#### 1.8.1. Khái niệm

Security Context là thông tin người dùng hiện tại sau khi xác thực (JWT / SSO).

Thông tin thường có:

- userId
- username
- role
- permission
- tenant
- organization

---

#### 1.8.2. Sử dụng trong Service

Service được phép truy cập Security Context để:

- xác định user thao tác
- kiểm tra permission
- ghi audit
- filter dữ liệu theo user

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-long-userid-%3D-securi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-type">Long</span> <span class="hljs-variable">userId</span> <span class="hljs-operator">=</span> SecurityUtils.getCurrentUserId();<span class="hljs-type">String</span> <span class="hljs-variable">username</span> <span class="hljs-operator">=</span> SecurityUtils.getCurrentUsername();`</div></div>---

#### 1.8.3. Quy định sử dụng

Không được:

- tin userId từ request
- truyền userId từ Controller nếu đã có context
- bypass permission check

Permission check phải tại Service layer.

---

#### 1.8.4. Ví dụ kiểm tra quyền

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-void-updateus"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">updateUser</span><span class="hljs-params">(Long id, UserUpdateRequest req)</span> {    <span class="hljs-type">Long</span> <span class="hljs-variable">currentUser</span> <span class="hljs-operator">=</span> SecurityUtils.getCurrentUserId();    <span class="hljs-keyword">if</span> (!permissionService.canUpdateUser(currentUser, id)) {        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AccessDeniedException</span>(<span class="hljs-string">"NO_PERMISSION"</span>);    }    ...}`</div></div>---

### 1.9. Xử lý API danh sách (List API)

#### 1.9.1. Nguyên tắc List API

API trả danh sách phải hỗ trợ:

- pagination
- sorting
- filter

Mục tiêu:

- tránh trả dữ liệu lớn
- đảm bảo hiệu năng DB
- hỗ trợ UI paging

---

#### 1.9.2. Chuẩn request List API

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-get-%2Fapi%2Fv1%2Fusers%3Fpa"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`GET /api/v1/users?page=0&size=20&<span class="hljs-built_in">sort</span>=createdAt,desc`</div></div>Tham số chuẩn:

- page (bắt đầu từ 0)
- size
- sort
- filter

---

#### 1.9.3. Chuẩn response List API

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%7B-%22code%22%3A-%22success%22%2C"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-punctuation">{</span>  <span class="hljs-attr">"code"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"SUCCESS"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>    <span class="hljs-attr">"content"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>    <span class="hljs-attr">"page"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">0</span><span class="hljs-punctuation">,</span>    <span class="hljs-attr">"size"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">20</span><span class="hljs-punctuation">,</span>    <span class="hljs-attr">"totalElements"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">100</span><span class="hljs-punctuation">,</span>    <span class="hljs-attr">"totalPages"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">5</span>  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">}</span>`</div></div>---

#### 1.9.4. Quy định bắt buộc

- Không trả toàn bộ bảng
- Pagination bắt buộc với list lớn
- Repository phải dùng paging query
- Không load toàn bộ relation

---

### 1.10. Gọi hệ thống ngoài trong API

#### 1.10.1. Phạm vi

API có thể cần gọi:

- SSO
- External API
- Message Queue
- Cache
- File Storage

---

#### 1.10.2. Kiến trúc gọi ngoài

Luồng chuẩn:

Service → Integration Client → External System

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-userinfo-info-%3D-ssoc"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-type">UserInfo</span> <span class="hljs-variable">info</span> <span class="hljs-operator">=</span> ssoClient.getUser(token);`</div></div>---

#### 1.10.3. Quy định

Không được gọi external tại:

- Controller
- Repository

Phải:

- gọi trong Service
- handle lỗi external
- timeout / retry nếu cần

---

#### 1.10.4. Xử lý lỗi external

Nếu external fail:

- map thành BusinessException hoặc SystemException
- không expose lỗi raw
- ghi log lỗi

---

### 1.11. Monitoring &amp; Metrics trong API

#### 1.11.1. Mục tiêu

Ngoài Logging &amp; Audit, API production cần hỗ trợ:

- theo dõi hiệu năng
- theo dõi lỗi
- theo dõi tải hệ thống

---

#### 1.11.2. Metrics cần có

- request count
- error rate
- response time
- DB query time
- external call time

---

#### 1.11.3. Trace request

Mỗi request phải có:

- traceId
- duration
- status

TraceId phải liên kết:

- log
- audit
- exception

---

### 1.12. Versioning API

#### 1.12.1. Chuẩn version

API phải có version trong URL:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%2Fapi%2Fv1%2F..."><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`/api/v1/...`</div></div>---

#### 1.12.2. Nguyên tắc version

- Không thay đổi breaking trong cùng version
- API cũ phải giữ backward compatibility
- API mới → tăng version
- Không reuse version

---

#### 1.12.3. Ví dụ

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%2Fapi%2Fv1%2Fusers-%2Fapi%2Fv"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`/api/v1/users/api/v2/users`</div></div>---

### 1.13. Testing trước merge

#### 1.13.1. Mục tiêu

Đảm bảo API hoạt động đúng trước khi merge và deploy.

---

#### 1.13.2. Loại test bắt buộc

- Unit test Service
- Validation test
- Error case
- Integration test API

---

#### 1.13.3. Quy định

API mới hoặc thay đổi logic phải có test:

- thành công
- validation fail
- business fail
- permission fail

---

### 1.14. Hiệu năng &amp; Database

#### 1.14.1. Nguyên tắc thiết kế DB trong API

API phải đảm bảo:

- query tối ưu
- index phù hợp
- không N+1 query
- pagination cho list

---

#### 1.14.2. Các lỗi cần tránh

- load toàn bộ relation
- query trong loop
- select \* bảng lớn
- thiếu index filter

---

#### 1.14.3. Kiểm tra khi review

Khi review API cần kiểm tra:

- query có index không
- list có paging không
- join có cần thiết không
- có N+1 không

# 1.Cấu trúc dự án

### 1.1. Mục tiêu chương

Chương này quy định cấu trúc dự án chuẩn và phạm vi quản lý mã nguồn giữa Core Team và các đơn vị Partner nhằm:

- Đảm bảo tính ổn định của hệ thống
- Tránh xung đột mã nguồn giữa các đơn vị phát triển
- Chuẩn hóa cách tổ chức code trong toàn dự án
- Dễ dàng bảo trì, mở rộng và tích hợp hệ thống

### 1.2. Khái niệm / phạm vi áp dụng

Tài liệu áp dụng cho:

- Tất cả các dự án sử dụng cấu trúc chuẩn của hệ thống
- Core Team (đội phát triển lõi)
- Partner Team (đối tác phát triển module)

Cấu trúc dự án chuẩn:

com.example.demo

 ├── common/ (thành phần dùng chung)

 ├── config/ (cấu hình hệ thống)

 ├── modules/ (các module nghiệp vụ)

 └── DemoApplication.java

#### Ý nghĩa từng khu vực

<div align="left" dir="ltr" id="bkmrk-th%C6%B0-m%E1%BB%A5c-vai-tr%C3%B2-ph%E1%BA%A1m"><table><colgroup><col width="77"></col><col width="261"></col><col width="130"></col></colgroup><tbody><tr><td>Thư mục

</td><td>Vai trò

</td><td>Phạm vi sử dụng

</td></tr><tr><td>common

</td><td>Thành phần dùng chung toàn hệ thống

</td><td>Core quản lý

</td></tr><tr><td>config

</td><td>Cấu hình hệ thống, security, hạ tầng

</td><td>Core quản lý

</td></tr><tr><td>modules

</td><td>Các module nghiệp vụ

</td><td>Core + Partner

</td></tr></tbody></table>

</div>### 1.3. Quy định chính

#### Phân quyền quản lý code

<div align="left" dir="ltr" id="bkmrk-khu-v%E1%BB%B1c-%C4%90%C6%A1n-v%E1%BB%8B-qu%E1%BA%A3n-"><table><colgroup><col width="91"></col><col width="116"></col><col width="167"></col></colgroup><tbody><tr><td>Khu vực

</td><td>Đơn vị quản lý

</td><td>Quyền chỉnh sửa

</td></tr><tr><td>common/\*\*

</td><td>Core Team

</td><td>Partner không được sửa

</td></tr><tr><td>config/\*\*

</td><td>Core Team

</td><td>Partner không được sửa

</td></tr><tr><td>modules/\*\*

</td><td>Core + Partner

</td><td>Được phép phát triển

</td></tr></tbody></table>

</div>#### Quy định bắt buộc

Đối tác KHÔNG được phép sửa trực tiếp:

common/\*\*

config/\*\*

Mọi thay đổi trong các khu vực này phải:

1. Tạo yêu cầu thay đổi (Core Change Request)
2. Được Core Team xem xét
3. Core Team phê duyệt và thực hiện

### 1.4. Cách thực hiện / quy trình

#### Quy trình thay đổi code dùng chung

Bước 1: Partner phát hiện nhu cầu thay đổi  
Bước 2: Tạo ticket với tiêu đề: các phần mềm quản lý công việc liên quan (Jira / Redmine / YouTrack) hoặc nội bộ

\[CORE CHANGE REQUEST\] Thêm field phone vào user\_account

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-8p4jpk77-png.png)

#### Luồng xử lý: 

1. Partner tạo ticket
2. Core Team review
3. Chuyển trạng thái:

<div align="left" dir="ltr" id="bkmrk-tr%E1%BA%A1ng-th%C3%A1i-%C3%9D-ngh%C4%A9a-o"><table><colgroup><col width="108"></col><col width="159"></col></colgroup><tbody><tr><td>Trạng thái

</td><td>Ý nghĩa

</td></tr><tr><td>Open

</td><td>Ticket mới

</td></tr><tr><td>Under Review

</td><td>Core đang đánh giá

</td></tr><tr><td>Approved

</td><td>Đồng ý thay đổi

</td></tr><tr><td>Rejected

</td><td>Từ chối

</td></tr><tr><td>Implemented

</td><td>Core đã code

</td></tr><tr><td>Merged

</td><td>Đã merge vào develop

</td></tr></tbody></table>

</div>Bước 3: Core Team đánh giá:

- Phạm vi ảnh hưởng
- Rủi ro hệ thống
- Tính cần thiết

Bước 4: Nếu hợp lệ:

- Core Team thực hiện thay đổi
- Merge vào nhánh develop

Bước 5: Partner pull code mới về và tiếp tục phát triển

### 1.5. Ví dụ minh họa

#### Trường hợp hợp lệ

Partner cần thêm logic trong module planning:

modules/planning/service/PlanningService.java

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-ewsuqxw7-png.png)

→ Được phép sửa trực tiếp và tạo PR.

#### Trường hợp không hợp lệ

Partner muốn sửa:

common/security/JwtAuthenticationFilter.java

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-jzlx82in-png.png)

→ Không được sửa trực tiếp.

Phải thực hiện:

1. Tạo Core Change Request
2. Chờ Core Team phê duyệt
3. Core Team thực hiện thay đổi

### 1.6. Checklist áp dụng

Trước khi commit hoặc tạo PR, cần kiểm tra:

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-wmafgmm0-png.png)

- Code nằm trong modules/\*\*
- Không sửa common/\*\*
- Không sửa config/\*\*
- PR đã được Core Team review (nếu cần)

<div align="left" dir="ltr" id="bkmrk--15"></div>

# 2.Quy chuẩn phát triển module

### 2.1. Mục tiêu chương

Chương này quy định cấu trúc chuẩn của mỗi module và trách nhiệm của từng layer nhằm:

- Tách biệt rõ ràng các tầng xử lý
- Giảm phụ thuộc giữa các thành phần
- Dễ bảo trì, mở rộng và test
- Đảm bảo tính nhất quán giữa các module trong hệ thống

### 2.2. Khái niệm / phạm vi áp dụng

Quy tắc này áp dụng cho:

- Tất cả các module trong thư mục modules/\*\*
- Cả Core Team và Partner Team
- Tất cả các chức năng mới được phát triển

#### Cấu trúc chuẩn mỗi module

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-q0uzrb7j-png.png)

modules/&lt;module-name&gt;/

 controller/

 service/

 repository/

 entity/

 dto/

#### Ý nghĩa từng thành phần

<div align="left" dir="ltr" id="bkmrk-layer-vai-tr%C3%B2-v%C3%AD-d%E1%BB%A5%C2%A0"><table><colgroup><col width="80"></col><col width="256"></col><col width="323"></col></colgroup><tbody><tr><td>Layer

</td><td>Vai trò

</td><td>ví dụ

</td></tr><tr><td>controller

</td><td>Nhận request từ client và trả response

</td><td>@PostMapping

public ApiResponse&lt;PlanningResponse&gt; create(@Valid @RequestBody PlanningCreateRequest req) {

 return ApiResponse.ok(service.create(req));

}

  
</td></tr><tr><td>service

</td><td>Xử lý logic nghiệp vụ

</td><td>public PlanningResponse get(Long id) {

 PlanningEntity e = repo.findById(id).orElseThrow(() -&gt;

 new AppException(ErrorCode.NOT\_FOUND, HttpStatus.NOT\_FOUND, "Planning not found: " + id)

 );

 return toResponse(e);

}

</td></tr><tr><td>repository

</td><td>Truy cập và thao tác dữ liệu

</td><td>public interface PlanQueryRepository {

 long count(PlanListFilter filter);

 List&lt;PlanListItem&gt; list(PlanListFilter filter);

}

  
</td></tr><tr><td>entity

</td><td>Mapping bảng database

</td><td>public class PlanningEntity {

 @Id

 @GeneratedValue(strategy = GenerationType.IDENTITY)

 private Long id;

}

</td></tr><tr><td>dto

</td><td>Model request và response

</td><td>public class PlanListFilter {

 private String keyword;

 private Integer page = 1; // 1-based

 private Integer size = 20;

 private String sortBy = "created\_at";

 private String sortDir = "desc";

 public String getKeyword() { return keyword; }

 public void setKeyword(String keyword) { this.keyword = keyword; }

 public Integer getPage() { return page; }

 public void setPage(Integer page) { this.page = page; }

 public Integer getSize() { return size; }

 public void setSize(Integer size) { this.size = size; }

 public String getSortBy() { return sortBy; }

 public void setSortBy(String sortBy) { this.sortBy = sortBy; }

 public String getSortDir() { return sortDir; }

 public void setSortDir(String sortDir) { this.sortDir = sortDir; }

}

</td></tr></tbody></table>

</div>### 2.3. Quy định chính

#### Trách nhiệm từng layer

##### Controller

- Nhận request từ client
- Gọi service tương ứng
- Trả response về client
- Không xử lý logic nghiệp vụ

##### Service

- Xử lý logic nghiệp vụ
- Điều phối repository
- Chuyển đổi entity ↔ DTO

##### Repository

- Truy cập database
- Thực hiện các thao tác CRUD
- Không chứa logic nghiệp vụ

##### Entity

- Mapping với bảng database
- Không chứa logic nghiệp vụ phức tạp

##### DTO

- Model cho request/response
- Không gắn trực tiếp với entity

---

#### Quy tắc bắt buộc

1. Controller không được chứa logic nghiệp vụ
2. Không trả trực tiếp Entity ra API
3. Luôn dùng DTO cho request/response
4. Response chuẩn toàn hệ thống: ApiResponse&lt;T&gt;

### 2.4. Cách thực hiện / quy trình

#### Quy trình phát triển một chức năng mới

Bước 1: Tạo DTO

Ví dụ:

UserCreateRequest

UserResponse

Bước 2: Tạo Entity

Mapping với bảng database:

UserEntity

Bước 3: Tạo Repository

UserRepository

Bước 4: Tạo Service

UserService

Xử lý logic:

- Validate dữ liệu
- Gọi repository
- Mapping entity → DTO

Bước 5: Tạo Controller

UserController

- Nhận request
- Gọi service

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/image-png.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/image-png.png)

### 2.5. Ví dụ minh họa

#### Ví dụ sai (vi phạm quy tắc)

Controller chứa logic:

@GetMapping("/users")

public List&lt;UserEntity&gt; getAll() {

 return userRepository.findAll();

}

Sai vì:

- Trả Entity trực tiếp
- Không dùng Service
- Không dùng ApiResponse

---

#### Ví dụ đúng

@GetMapping("/users")

public ApiResponse&lt;List&lt;UserResponse&gt;&gt; getAll() {

 return ApiResponse.ok(userService.getAll());

}

Service:

public List&lt;UserResponse&gt; getAll() {

 return userRepository.findAll()

 .stream()

 .map(this::toResponse)

 .toList();

}

### 2.6. Checklist áp dụng

Trước khi commit module mới:

- Có đủ:
- controller/
- service/
- repository/
- entity/
- dto/

- Controller không chứa logic nghiệp vụ
- Không trả Entity ra API
- Tất cả API trả về ApiResponse&lt;T&gt;
- DTO tách biệt với Entity

<div align="left" dir="ltr" id="bkmrk--3"></div>

# 3.Coding convention

### 3.1. Mục tiêu chương

Chương này quy định chuẩn coding và nguyên tắc thiết kế nhằm:

- Đảm bảo code thống nhất giữa các module và các đơn vị phát triển
- Giúp code dễ đọc, dễ hiểu và dễ bảo trì
- Giảm lỗi phát sinh do cách đặt tên hoặc coding không đồng nhất
- Tăng hiệu quả review và kiểm soát chất lượng code
- Đảm bảo code tuân thủ nguyên lý thiết kế SOLID
- Đảm bảo kiến trúc backend Spring Boot nhất quán toàn hệ thống

---

### 3.2. Khái niệm / phạm vi áp dụng

Quy định này áp dụng cho:

- Toàn bộ source code của hệ thống
- Core Team và Partner Team
- Tất cả các module trong `modules/**`
- Các thành phần trong `common/**` và `config/**` do Core Team quản lý
- Tất cả service, controller, repository, DTO, entity

---

### 3.3. Quy định chính

#### 3.3.1. Quy tắc đặt tên

Các thành phần phải đặt tên theo quy ước thống nhất:

<div class="TyagGW_tableContainer" id="bkmrk-th%C3%A0nh-ph%E1%BA%A7n-quy-t%E1%BA%AFc-%C4%91"><div class="group TyagGW_tableWrapper flex flex-col-reverse w-fit" tabindex="-1"><table class="w-fit min-w-(--thread-content-width)" data-end="1755" data-start="1269"><thead data-end="1309" data-start="1269"><tr data-end="1309" data-start="1269"><th class="" data-col-size="sm" data-end="1282" data-start="1269">Thành phần</th><th class="" data-col-size="sm" data-end="1300" data-start="1282">Quy tắc đặt tên</th><th class="" data-col-size="sm" data-end="1309" data-start="1300">Ví dụ</th></tr></thead><tbody data-end="1755" data-start="1349"><tr data-end="1401" data-start="1349"><td data-col-size="sm" data-end="1360" data-start="1349">Controller</td><td data-col-size="sm" data-end="1381" data-start="1360">`<Name>Controller`</td><td data-col-size="sm" data-end="1401" data-start="1381">`UserController`</td></tr><tr data-end="1445" data-start="1402"><td data-col-size="sm" data-end="1410" data-start="1402">Service</td><td data-col-size="sm" data-end="1428" data-start="1410">`<Name>Service`</td><td data-col-size="sm" data-end="1445" data-start="1428">`UserService`</td></tr><tr data-end="1498" data-start="1446"><td data-col-size="sm" data-end="1457" data-start="1446">Repository</td><td data-col-size="sm" data-end="1478" data-start="1457">`<Name>Repository`</td><td data-col-size="sm" data-end="1498" data-start="1478">`UserRepository`</td></tr><tr data-end="1539" data-start="1499"><td data-col-size="sm" data-end="1506" data-start="1499">Entity</td><td data-col-size="sm" data-end="1523" data-start="1506">`<Name>Entity`</td><td data-col-size="sm" data-end="1539" data-start="1523">`UserEntity`</td></tr><tr data-end="1622" data-start="1540"><td data-col-size="sm" data-end="1552" data-start="1540">DTO Request</td><td data-col-size="sm" data-end="1599" data-start="1552">`<Name>CreateRequest`, `<Name>UpdateRequest`</td><td data-col-size="sm" data-end="1622" data-start="1599">`UserCreateRequest`</td></tr><tr data-end="1673" data-start="1623"><td data-col-size="sm" data-end="1636" data-start="1623">DTO Response</td><td data-col-size="sm" data-end="1655" data-start="1636">`<Name>Response`</td><td data-col-size="sm" data-end="1673" data-start="1655">`UserResponse`</td></tr><tr data-end="1714" data-start="1674"><td data-col-size="sm" data-end="1681" data-start="1674">Mapper</td><td data-col-size="sm" data-end="1698" data-start="1681">`<Name>Mapper`</td><td data-col-size="sm" data-end="1714" data-start="1698">`UserMapper`</td></tr><tr data-end="1755" data-start="1715"><td data-col-size="sm" data-end="1720" data-start="1715">Enum</td><td data-col-size="sm" data-end="1735" data-start="1720">`<Name>Enum`</td><td data-col-size="sm" data-end="1755" data-start="1735">`UserStatusEnum`</td></tr></tbody></table>

</div></div>---

#### 3.3.2. Quy tắc chung Java

Dùng camelCase cho:

- Biến
- Method

Dùng PascalCase cho:

- Class
- Interface
- Enum

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-private-string-fulln"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">private</span> String fullName;      <span class="hljs-comment">// đúng</span><span class="hljs-keyword">private</span> String FullName;      <span class="hljs-comment">// sai</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> { }  <span class="hljs-comment">// đúng</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">userService</span> { }  <span class="hljs-comment">// sai</span>`</div></div>---

#### 3.3.3. Quy định về logging

Không được dùng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-system.out.println%28%22"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`System.out.println(<span class="hljs-string">"Error"</span>);`</div></div>Phải dùng logging chuẩn:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-private-static-final"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span>        LoggerFactory.getLogger(UserService.class);log.info(<span class="hljs-string">"User created"</span>);log.error(<span class="hljs-string">"Create user failed"</span>, ex);`</div></div>---

#### 3.3.4. Validation dữ liệu đầu vào

Tất cả dữ liệu đầu vào phải được validate bằng annotation.

Annotation chuẩn:

- `@NotNull`
- `@NotBlank`
- `@Size`
- `@Email`
- `@Pattern`

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-usercre"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserCreateRequest</span> {    <span class="hljs-meta">@NotBlank</span>    <span class="hljs-meta">@Size(max = 100)</span>    <span class="hljs-keyword">private</span> String username;    <span class="hljs-meta">@NotBlank</span>    <span class="hljs-keyword">private</span> String password;}`</div></div>---

### 3.4. Nguyên lý thiết kế SOLID áp dụng trong dự án

#### 3.4.1. S — Single Responsibility

Một class chỉ có một trách nhiệm.

Quy định dự án:

- Controller → chỉ xử lý HTTP
- Service → xử lý nghiệp vụ
- Repository → truy cập DB
- Mapper → convert DTO ↔ Entity

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-usercon"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {    <span class="hljs-meta">@Autowired</span> UserRepository repo;   <span class="hljs-comment">// sai tầng</span>}`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-usercon-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {    <span class="hljs-meta">@Autowired</span> UserService userService;}`</div></div>---

#### 3.4.2. O — Open/Closed

Code phải mở rộng được nhưng không sửa code cũ.

Áp dụng:

- Dùng interface Service
- Không sửa class gốc khi thêm logic

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-interface-not"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">NotificationService</span> {    <span class="hljs-keyword">void</span> <span class="hljs-title function_">send</span><span class="hljs-params">(Message msg)</span>;}`</div></div>Triển khai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-emailno"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">EmailNotificationService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">NotificationService</span> { }<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SmsNotificationService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">NotificationService</span> { }`</div></div>---

#### 3.4.3. L — Liskov Substitution

Class con có thể thay thế class cha.

Quy định:

- ServiceImpl phải tuân interface
- Không throw exception mới trái contract

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-userser"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">UserService</span> {    <span class="hljs-keyword">public</span> User <span class="hljs-title function_">get</span><span class="hljs-params">(String id)</span> {        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UnsupportedOperationException</span>();    }}`</div></div>---

#### 3.4.4. I — Interface Segregation

Không tạo interface quá lớn.

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-interface-use"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserService</span> {    create();    update();    delete();    exportExcel();    sendEmail();}`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-interface-use-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserService</span> { }<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserExportService</span> { }<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">UserNotificationService</span> { }`</div></div>---

#### 3.4.5. D — Dependency Inversion

Phụ thuộc abstraction, không phụ thuộc implementation.

Quy định:

- Inject interface
- Không new service

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-userservice-service-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-type">UserService</span> <span class="hljs-variable">service</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserServiceImpl</span>();`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40autowired-userservi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Autowired</span>UserService userService;`</div></div>---

### 3.5. Quy tắc kiến trúc Spring Boot trong dự án

#### 3.5.1. Luồng chuẩn tầng

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-controller-%E2%86%92-service"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Controller</span> → Service → Repository → Database`</div></div>Không được:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-controller-%E2%86%92-reposit"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Controller</span> → RepositoryController → EntityManager`</div></div>---

#### 3.5.2. Controller không chứa nghiệp vụ

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40postmapping-public-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@PostMapping</span><span class="hljs-keyword">public</span> User <span class="hljs-title function_">create</span><span class="hljs-params">(...)</span> {    user.setCreatedDate(LocalDateTime.now()); <span class="hljs-comment">// sai</span>}`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40postmapping-public--1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@PostMapping</span><span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(...)</span> {    <span class="hljs-keyword">return</span> userService.create(request);}`</div></div>---

#### 3.5.3. Service không chứa logic HTTP

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-throw-new-responsest"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResponseStatusException</span>(HttpStatus.BAD_REQUEST);`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-throw-new-businessex"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(<span class="hljs-string">"USER_EXISTS"</span>);`</div></div>---

#### 3.5.4. Repository chỉ truy vấn dữ liệu

Không chứa:

- validation
- business logic
- mapping

---

### 3.6. Design pattern sử dụng trong hệ thống

#### 3.6.1. Service pattern

Service là tầng nghiệp vụ trung tâm.

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-controller-%E2%86%92-service-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Controller</span> → Service → Repository`</div></div>---

#### 3.6.2. DTO pattern

Tách DTO khỏi Entity.

Không trả Entity ra API.

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-userentity-cr"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> UserEntity <span class="hljs-title function_">create</span><span class="hljs-params">(...)</span> { }`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-userresponse-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(...)</span> { }`</div></div>---

#### 3.6.3. Mapper pattern

Convert DTO ↔ Entity.

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-userentity-entity-%3D-"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-type">UserEntity</span> <span class="hljs-variable">entity</span> <span class="hljs-operator">=</span> mapper.toEntity(request);`</div></div>---

#### 3.6.4. Exception pattern

Dùng BusinessException thống nhất.

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-throw-new-businessex-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.USER_NOT_FOUND);`</div></div>---

### 3.7. Cách thực hiện / quy trình

#### Quy trình tạo DTO đúng chuẩn

Bước 1: Tạo class DTO theo quy tắc đặt tên

Ví dụ:

- UserCreateRequest
- UserUpdateRequest
- UserResponse

Bước 2: Thêm validation annotation

Bước 3: Dùng DTO trong controller

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40postmapping-public--2"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@PostMapping</span><span class="hljs-keyword">public</span> ApiResponse<UserResponse> <span class="hljs-title function_">create</span><span class="hljs-params">(        </span><span class="hljs-meta">@Valid</span> <span class="hljs-meta">@RequestBody</span> UserCreateRequest request) {    <span class="hljs-keyword">return</span> ApiResponse.ok(userService.create(request));}`</div></div>---

#### Quy trình logging chuẩn

Bước 1: Khai báo logger trong class

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-private-static-final-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span>        LoggerFactory.getLogger(UserService.class);`</div></div>Bước 2: Dùng logger thay cho System.out.println

---

### 3.8. Ví dụ minh họa

#### Trường hợp sai

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-usercon-2"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">usercontroller</span> {    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">CreateUser</span><span class="hljs-params">()</span> {        System.out.println(<span class="hljs-string">"Create user"</span>);    }}`</div></div>Sai vì:

- Tên class không đúng chuẩn
- Tên method không camelCase
- Dùng System.out.println
- Không theo kiến trúc tầng

---

#### Trường hợp đúng

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40restcontroller-publ"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@RestController</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">log</span> <span class="hljs-operator">=</span>            LoggerFactory.getLogger(UserController.class);    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserService userService;    <span class="hljs-keyword">public</span> <span class="hljs-title function_">UserController</span><span class="hljs-params">(UserService userService)</span> {        <span class="hljs-built_in">this</span>.userService = userService;    }    <span class="hljs-meta">@PostMapping</span>    <span class="hljs-keyword">public</span> ApiResponse<UserResponse> <span class="hljs-title function_">create</span><span class="hljs-params">(            </span><span class="hljs-meta">@Valid</span> <span class="hljs-meta">@RequestBody</span> UserCreateRequest request) {        log.info(<span class="hljs-string">"Create user {}"</span>, request.getUsername());        <span class="hljs-keyword">return</span> ApiResponse.ok(userService.create(request));    }}`</div></div>---

#### Ví dụ DTO sai

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-userdto"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">userDTO</span> {    <span class="hljs-keyword">public</span> String name;}`</div></div>Sai vì:

- Tên class không chuẩn
- Không có validation
- Field public
- Không theo DTO pattern

---

#### Ví dụ DTO đúng

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-usercre-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserCreateRequest</span> {    <span class="hljs-meta">@NotBlank</span>    <span class="hljs-meta">@Size(max = 255)</span>    <span class="hljs-keyword">private</span> String name;    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> { <span class="hljs-keyword">return</span> name; }    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> { <span class="hljs-built_in">this</span>.name = name; }}`</div></div>---

### 3.9. Checklist áp dụng

Trước khi commit hoặc tạo PR:

### Naming

- [ ]  [ ]  Controller → `*Controller`
- [ ]  [ ]  Service → `*Service`
- [ ]  [ ]  Repository → `*Repository`
- [ ]  [ ]  Entity → `*Entity`
- [ ]  [ ]  DTO → `*Request / *Response`

---

### Coding

- [ ]  [ ]  Biến và method dùng camelCase
- [ ]  [ ]  Class dùng PascalCase
- [ ]  [ ]  Không dùng System.out.println
- [ ]  [ ]  Dùng logging chuẩn slf4j

---

### Validation

- [ ]  [ ]  DTO có validation annotation
- [ ]  [ ]  Không có field public trong DTO

---

### SOLID

- [ ]  [ ]  Controller không chứa nghiệp vụ
- [ ]  [ ]  Service không chứa HTTP logic
- [ ]  [ ]  Repository chỉ truy vấn DB
- [ ]  [ ]  Inject interface, không new
- [ ]  [ ]  Class có 1 trách nhiệm

---

### Architecture

- [ ]  [ ]  Không Controller → Repository
- [ ]  [ ]  Không trả Entity ra API
- [ ]  [ ]  Có DTO mapping
- [ ]  [ ]  Exception dùng chuẩn hệ thống

### 3.10. Cấu trúc chuẩn của một hàm xử lý nghiệp vụ

#### 3.10.1. Mục tiêu

Quy định cấu trúc thống nhất của một hàm xử lý nghiệp vụ nhằm:

- Dễ đọc và dễ review
- Tách rõ validate – logic – log – exception
- Tránh thiếu validate hoặc thiếu log
- Chuẩn hóa xử lý lỗi và response
- Giúp dev mới đọc code hiểu ngay luồng xử lý

---

#### 3.10.2. Cấu trúc chuẩn bắt buộc

Một hàm service/controller phải theo thứ tự:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-1.-validate-input-2."><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-bullet">1.</span> Validate input<span class="hljs-bullet">2.</span> Log input<span class="hljs-bullet">3.</span> Business logic<span class="hljs-bullet">4.</span> Catch exception (nếu cần)<span class="hljs-bullet">5.</span> Trả response / throw BusinessException`</div></div>---

#### 3.10.3. Quy định chi tiết từng phần

##### 1. Validate

- Validate annotation ở DTO
- Validate nghiệp vụ ở service

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-if-%28repo.existsbyuse"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">if</span> (repo.existsByUsername(request.getUsername())) {    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.USER_EXISTS);}`</div></div>---

##### 2. Log

Phải log:

- input chính
- hành động nghiệp vụ
- lỗi

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-log.info%28%22create-use"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`log.info(<span class="hljs-string">"Create user {}"</span>, request.getUsername());`</div></div>---

##### 3. Business logic

Chỉ chứa xử lý nghiệp vụ:

- mapping
- tính toán
- gọi repository
- gọi service khác

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-userentity-entity-%3D--1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-type">UserEntity</span> <span class="hljs-variable">entity</span> <span class="hljs-operator">=</span> mapper.toEntity(request);repo.save(entity);`</div></div>---

##### 4. Exception handling

Không catch Exception chung chung nếu không xử lý.

Sai:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-try-%7B-repo.save%28enti"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">try</span> {   repo.save(entity);} <span class="hljs-keyword">catch</span> (Exception e) {}`</div></div>Đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-try-%7B-repo.save%28enti-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">try</span> {   repo.save(entity);} <span class="hljs-keyword">catch</span> (DataIntegrityViolationException ex) {   log.error(<span class="hljs-string">"DB error"</span>, ex);   <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.DB_ERROR);}`</div></div>---

##### 5. Response

Service:

- trả DTO
- hoặc throw BusinessException

Controller:

- trả ApiResponse

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-return-userresponse."><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">return</span> UserResponse.from(entity);`</div></div>Controller:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-return-apiresponse.o"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">return</span> ApiResponse.ok(userService.create(request));`</div></div>---

#### 3.10.4. Ví dụ hàm sai

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-user-create%28u"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> User <span class="hljs-title function_">create</span><span class="hljs-params">(UserCreateRequest req)</span> {    <span class="hljs-type">UserEntity</span> <span class="hljs-variable">e</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserEntity</span>();    e.setUsername(req.getUsername());    repo.save(e);    <span class="hljs-keyword">return</span> e;}`</div></div>Sai vì:

- Không validate
- Không log
- Trả entity
- Không xử lý lỗi
- Không theo DTO pattern

---

#### 3.10.5. Ví dụ hàm đúng chuẩn dự án

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-userresponse--1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(UserCreateRequest request)</span> {    <span class="hljs-comment">// 1. Validate</span>    <span class="hljs-keyword">if</span> (repo.existsByUsername(request.getUsername())) {        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BusinessException</span>(ErrorCode.USER_EXISTS);    }    <span class="hljs-comment">// 2. Log</span>    log.info(<span class="hljs-string">"Create user {}"</span>, request.getUsername());    <span class="hljs-comment">// 3. Business logic</span>    <span class="hljs-type">UserEntity</span> <span class="hljs-variable">entity</span> <span class="hljs-operator">=</span> mapper.toEntity(request);    repo.save(entity);    <span class="hljs-comment">// 4. Response</span>    <span class="hljs-keyword">return</span> mapper.toResponse(entity);}`</div></div>---

#### 3.10.6. Checklist

Khi review hàm service/controller:

- [ ]  [ ]  Có validate nghiệp vụ
- [ ]  [ ]  Có log input chính
- [ ]  [ ]  Không xử lý logic trong controller
- [ ]  [ ]  Không trả Entity
- [ ]  [ ]  Throw BusinessException đúng chuẩn
- [ ]  [ ]  Không catch Exception vô nghĩa
- [ ]  [ ]  Trả DTO / ApiResponse đúng chuẩn

# 4. Quy định thay đổi cấu trúc dữ liệu (Entity & Database)

### 4.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định cách kiểm soát các thay đổi liên quan đến cấu trúc dữ liệu nhằm:

- Tránh mất dữ liệu hoặc lỗi hệ thống
- Đảm bảo tương thích giữa các module
- Ngăn xung đột schema khi nhiều đơn vị cùng phát triển
- Bảo vệ các field và bảng dữ liệu chuẩn của hệ thống

### 4.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Quy định này áp dụng cho:

- Tất cả các entity trong hệ thống
- Tất cả các bảng database
- Core Team và Partner Team
- Mọi thay đổi liên quan đến:

#### Các loại thay đổi cần kiểm soát

- Thêm field mới
- Xóa field
- Sửa field
- Đổi kiểu dữ liệu
- Thay đổi quan hệ entity
- Thêm/xóa bảng database

### 4.3. Quy định chính

<div id="bkmrk--2">  
</div>#### 4.3.1. Không được tự ý thay đổi các field chuẩn

Các field chuẩn hệ thống:

- id
- created\_at
- created\_by
- updated\_at
- updated\_by
- deleted / status
- tenant\_id (nếu có)

##### Quy định bắt buộc

Không được:

- Xóa field chuẩn
- Đổi kiểu dữ liệu
- Đổi tên field
- Thay đổi ý nghĩa nghiệp vụ của field

Các field này được coi là system fields và được dùng chung cho:

- Audit
- Logging
- Multi-tenant
- Phân quyền
- Tích hợp hệ thống

---

#### 4.3.2. Thêm field mới trong module

Partner chỉ được phép thêm field khi:

- Phục vụ nghiệp vụ của module
- Không trùng với dữ liệu core
- Không phá vỡ cấu trúc hiện tại
- Có migration script
- Có mô tả trong Pull Request
- Được Core Team review

---

#### 4.3.3. Các thay đổi bắt buộc xin phép Core Team

Các thay đổi sau không được tự ý thực hiện:

- Đổi kiểu dữ liệu field
- Xóa field
- Đổi tên field
- Thay đổi quan hệ entity
- Thêm field liên quan:
- user
- role
- permission
- tenant
- audit


Tất cả các trường hợp trên phải qua:

Data Model Change Request

### 4.4. Cách thực hiện / quy trình

<div id="bkmrk--5">  
</div>#### Trường hợp 1: Thêm field trong module

Bước 1: Cập nhật entity trong:

modules/&lt;module&gt;/entity/\*\*

Bước 2: Tạo migration script:

V&lt;timestamp&gt;\_\_add\_&lt;field&gt;\_to\_&lt;table&gt;.sql

Bước 3: Tạo Pull Request, trong PR phải có:

- Mô tả field mới
- Lý do nghiệp vụ
- Migration script

Bước 4: Core Team review:

- Không trùng core data
- Không phá vỡ hệ thống

Bước 5: Merge vào develop

---

#### Trường hợp 2: Thay đổi field hoặc quan hệ entity

Bước 1: Tạo ticket: các phần mềm quản lý công việc liên quan (Jira / Redmine / YouTrack) hoặc nội bộ

\[DATA MODEL CHANGE REQUEST\] &lt;mô tả thay đổi&gt;

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-3augpwwi-png.png)

Bước 2: Cung cấp thông tin:

- Entity bị ảnh hưởng
- Field thay đổi
- Kiểu dữ liệu cũ và mới
- Migration script
- Lý do thay đổi

Bước 3: Core Team review:

- Đánh giá ảnh hưởng hệ thống
- Kiểm tra backward compatibility

Bước 4: Nếu được duyệt:

- Core Team thực hiện thay đổi
- Merge vào develop

### 4.5. Ví dụ minh họa

<div id="bkmrk--12">  
</div>#### Trường hợp hợp lệ

Partner thêm field vào entity module:

@Column(name = "PRIORITY")

private Integer priority;

Và có migration:

ALTER TABLE PLANNING ADD PRIORITY INT;

→ Được phép.

---

#### Trường hợp không hợp lệ

Partner sửa:

@Column(name = "CREATED\_AT")

private String createdAt;

→ Đổi kiểu dữ liệu từ timestamp sang string.

→ Không được phép.

Phải tạo:

\[DATA MODEL CHANGE REQUEST\]

---

#### Trường hợp cần xin phép

Partner muốn:

- Xóa field status
- Đổi tên description → detail
- Thêm quan hệ với bảng user\_account

### 4.6. Checklist áp dụng

<div id="bkmrk--24">  
</div>Trước khi commit hoặc tạo PR:

- Có thay đổi field chuẩn không?
- Nếu có → phải tạo Data Model Change Request

- Có đổi kiểu dữ liệu field không?
- Nếu có → phải xin phép Core Team

- Có migration script chưa?
- Field mới có trùng dữ liệu core không?
- PR đã mô tả rõ thay đổi chưa?

# 5.Quy định về Database, Schema và Migration

### 5.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định cách quản lý cấu trúc database và các thay đổi schema nhằm:

- Đảm bảo tính ổn định của dữ liệu hệ thống
- Tránh xung đột schema giữa các module và các đơn vị phát triển
- Đảm bảo khả năng triển khai tự động qua các môi trường
- Kiểm soát lịch sử thay đổi database

### 5.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Quy định này áp dụng cho:

- Toàn bộ database của hệ thống
- Core Team và Partner Team
- Tất cả các thay đổi liên quan đến:
- Bảng dữ liệu
- Cột dữ liệu
- Constraint
- Index
- Trigger


Hệ thống phân loại bảng dữ liệu thành hai nhóm:

---

#### 1. Core tables (dùng chung toàn hệ thống)

Bao gồm các bảng:

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-uotpqlrv-png.png)

- app\_user
- theme\_config
- feature\_management

Đây là các bảng dữ liệu lõi, được dùng chung cho:

- Xác thực
- Phân quyền
- Cấu hình hệ thống

---

#### 2. Module tables

Bao gồm:

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-p2sgulvy-png.png)

- Các bảng thuộc từng module riêng
- Nằm trong phạm vi nghiệp vụ của module

Ví dụ:

- planning
- document
- task
- notification

### 5.3. Quy định chính

#### 5.3.1. Quy định với Core tables

Partner không được phép:

- Thay đổi cấu trúc bảng
- Thêm cột
- Xóa cột
- Đổi kiểu dữ liệu
- Sửa constraint
- Sửa index
- Sửa trigger

Mọi thay đổi liên quan đến core tables phải thông qua:

DB Core Change Request -&gt; sử dụng jira/… hoặc các phần mềm quản lý công việc nội bộ

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-o2jooqjp-png.png)

Và do Core Team thực hiện.

---

#### 5.3.2. Quy định với Module tables

Partner được phép:

- Tạo bảng mới cho module
- Thêm cột
- Sửa cấu trúc bảng module

Nhưng phải tuân thủ:

- Không ảnh hưởng core tables
- Có migration script
- Có mô tả rõ trong Pull Request
- Được Core Team review

---

#### 5.3.3. Migration bắt buộc

Mọi thay đổi database phải tuân thủ các nguyên tắc sau:

- Không sửa trực tiếp database production
- Không chỉnh sửa thủ công trên DB staging
- Tất cả thay đổi phải qua migration script
- Migration phải được commit vào source code

Công cụ sử dụng:

- Flyway hoặc Liquibase

---

#### Format migration

Tên file migration:

V&lt;timestamp&gt;\_\_&lt;description&gt;.sql

Ví dụ:

V20260209\_\_create\_planning\_table.sql

V20260210\_\_add\_priority\_to\_planning.sql

### 5.4. Cách thực hiện / quy trình

<div id="bkmrk--16">  
</div>#### Trường hợp 1: Thêm hoặc sửa bảng trong module

Bước 1: Cập nhật entity trong:

modules/&lt;module&gt;/entity/\*\*

Bước 2: Tạo migration script:

V&lt;timestamp&gt;\_\_&lt;description&gt;.sql

Bước 3: Commit:

- Entity
- Migration script

Bước 4: Tạo Pull Request

Bước 5: Core Team review:

- Migration hợp lệ
- Không ảnh hưởng core tables

Bước 6: Merge vào develop

---

#### Trường hợp 2: Thay đổi core tables

Bước 1: Tạo ticket:

\[DB CORE CHANGE REQUEST\] &lt;mô tả thay đổi&gt;

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-hfcg93xe-png.png)

Bước 2: Cung cấp thông tin:

- Bảng bị ảnh hưởng
- Cột thay đổi
- Kiểu dữ liệu
- Migration script đề xuất
- Lý do thay đổi

Bước 3: Core Team review

Bước 4: Nếu được duyệt:

- Core Team thực hiện migration

**Merge vào develop**

### 5.5. Ví dụ minh họa

<div id="bkmrk--23">  
</div>#### Trường hợp hợp lệ

Partner thêm bảng mới:

CREATE TABLE PLANNING (

 ID BIGINT PRIMARY KEY,

 NAME VARCHAR(255),

 DESCRIPTION VARCHAR(1000)

);

→ Thuộc module table → được phép.

---

#### Trường hợp không hợp lệ

Partner chạy trực tiếp trên production:

ALTER TABLE USER\_ACCOUNT ADD PHONE VARCHAR(20);

→ Sai quy định vì:

- Sửa core table
- Sửa trực tiếp DB production
- Không có migration

---

#### Trường hợp cần xin phép

Partner muốn:

ALTER TABLE ROLE ADD DESCRIPTION VARCHAR(255);

→ Đây là core table.

Phải tạo: trên phần mềm quản lý công việc nội bộ hoặc phần mềm nghiệp vụ tương đương

\[DB CORE CHANGE REQUEST\]

### 5.6. Checklist áp dụng

<div id="bkmrk--33">  
</div>Trước khi commit hoặc tạo PR:

- Có thay đổi database không?
- Nếu có → phải có migration script

- Có sửa core table không?
- Nếu có → phải tạo DB Core Change Request

- Migration có đúng format tên file không?
- Không sửa trực tiếp DB production
- Migration đã được commit vào source code

### 5.7. Quy định thay đổi cấu trúc dữ liệu (Entity &amp; Database)

<div id="bkmrk--6">  
</div>#### 5.7.1. Mục tiêu chương

<div id="bkmrk--34">  
</div>Chương này quy định cách kiểm soát các thay đổi liên quan đến cấu trúc dữ liệu nhằm:

- Tránh mất dữ liệu hoặc lỗi hệ thống
- Đảm bảo tương thích giữa các module
- Ngăn xung đột schema khi nhiều đơn vị cùng phát triển
- Bảo vệ các field và bảng dữ liệu chuẩn của hệ thống

#### 4.7.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--35">  
</div>Quy định này áp dụng cho:

- Tất cả các entity trong hệ thống
- Tất cả các bảng database
- Core Team và Partner Team
- Mọi thay đổi liên quan đến:

#### Các loại thay đổi cần kiểm soát

- Thêm field mới
- Xóa field
- Sửa field
- Đổi kiểu dữ liệu
- Thay đổi quan hệ entity
- Thêm/xóa bảng database

### 5.7.3. Quy định chính

<div id="bkmrk--36">  
</div>#### 5.7.3.1. Không được tự ý thay đổi các field chuẩn

Các field chuẩn hệ thống:

- id
- created\_at
- created\_by
- updated\_at
- updated\_by
- deleted / status
- tenant\_id (nếu có)

##### Quy định bắt buộc

Không được:

- Xóa field chuẩn
- Đổi kiểu dữ liệu
- Đổi tên field
- Thay đổi ý nghĩa nghiệp vụ của field

Các field này được coi là system fields và được dùng chung cho:

- Audit
- Logging
- Multi-tenant
- Phân quyền
- Tích hợp hệ thống

---

#### 5.7.3.2. Thêm field mới trong module

Partner chỉ được phép thêm field khi:

- Phục vụ nghiệp vụ của module
- Không trùng với dữ liệu core
- Không phá vỡ cấu trúc hiện tại
- Có migration script
- Có mô tả trong Pull Request
- Được Core Team review

---

#### 5.7.3.3. Các thay đổi bắt buộc xin phép Core Team

Các thay đổi sau không được tự ý thực hiện:

- Đổi kiểu dữ liệu field
- Xóa field
- Đổi tên field
- Thay đổi quan hệ entity
- Thêm field liên quan:
- user
- role
- permission
- tenant
- audit


Tất cả các trường hợp trên phải qua:

Data Model Change Request

### 5.7.4. Cách thực hiện / quy trình

<div id="bkmrk--44">  
</div>#### Trường hợp 1: Thêm field trong module

Bước 1: Cập nhật entity trong:

modules/&lt;module&gt;/entity/\*\*

Bước 2: Tạo migration script:

V&lt;timestamp&gt;\_\_add\_&lt;field&gt;\_to\_&lt;table&gt;.sql

Bước 3: Tạo Pull Request, trong PR phải có:

- Mô tả field mới
- Lý do nghiệp vụ
- Migration script

Bước 4: Core Team review:

- Không trùng core data
- Không phá vỡ hệ thống

Bước 5: Merge vào develop

---

#### Trường hợp 2: Thay đổi field hoặc quan hệ entity

Bước 1: Tạo ticket: các phần mềm quản lý công việc liên quan (Jira / Redmine / YouTrack) hoặc nội bộ

\[DATA MODEL CHANGE REQUEST\] &lt;mô tả thay đổi&gt;

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-3augpwwi-png.png)

Bước 2: Cung cấp thông tin:

- Entity bị ảnh hưởng
- Field thay đổi
- Kiểu dữ liệu cũ và mới
- Migration script
- Lý do thay đổi

Bước 3: Core Team review:

- Đánh giá ảnh hưởng hệ thống
- Kiểm tra backward compatibility

Bước 4: Nếu được duyệt:

- Core Team thực hiện thay đổi
- Merge vào develop

### 5.7.5. Ví dụ minh họa

<div id="bkmrk--51">  
</div>#### Trường hợp hợp lệ

Partner thêm field vào entity module:

@Column(name = "PRIORITY")

private Integer priority;

Và có migration:

ALTER TABLE PLANNING ADD PRIORITY INT;

→ Được phép.

---

#### Trường hợp không hợp lệ

Partner sửa:

@Column(name = "CREATED\_AT")

private String createdAt;

→ Đổi kiểu dữ liệu từ timestamp sang string.

→ Không được phép.

Phải tạo:

\[DATA MODEL CHANGE REQUEST\]

---

#### Trường hợp cần xin phép

Partner muốn:

- Xóa field status
- Đổi tên description → detail
- Thêm quan hệ với bảng user\_account

### 5.7.6. Checklist áp dụng

<div id="bkmrk--63">  
</div>Trước khi commit hoặc tạo PR:

- Có thay đổi field chuẩn không?
- Nếu có → phải tạo Data Model Change Request

- Có đổi kiểu dữ liệu field không?
- Nếu có → phải xin phép Core Team

- Có migration script chưa?
- Field mới có trùng dữ liệu core không?
- PR đã mô tả rõ thay đổi chưa?

# 6.Audit Log & System Metadata

### 6.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định cách quản lý dữ liệu audit nhằm:

- Đảm bảo khả năng truy vết toàn bộ hoạt động hệ thống
- Phục vụ kiểm tra, giám sát và điều tra sự cố
- Đáp ứng yêu cầu kiểm toán và tuân thủ
- Bảo vệ dữ liệu hệ thống khỏi việc chỉnh sửa trái phép

### 6.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Audit log là dữ liệu ghi nhận:

- Hành động của người dùng
- Thay đổi dữ liệu quan trọng
- Sự kiện hệ thống
- Hoạt động bảo mật

Các bảng audit:

- audit\_log
- action\_history
- system\_event

Các bảng này được coi là dữ liệu core của hệ thống.

Quy định này áp dụng cho:

- Tất cả module trong hệ thống
- Core Team và Partner Team

**Mọi thay đổi liên quan đến audit**

### 6.3. Quy định chính

<div id="bkmrk--2">  
</div>Partner không được phép:

- Thay đổi cấu trúc bảng audit
- Thêm hoặc xóa cột trong bảng audit
- Xóa dữ liệu audit
- Chỉnh sửa dữ liệu audit thủ công
- Thay đổi logic ghi log

Các bảng audit được coi là:

Core system data

Mọi thay đổi liên quan đến:

- Cấu trúc bảng audit
- Logic ghi log
- Cơ chế lưu trữ audit

phải được:

Core Team phê duyệt

### 6.4. Cách thực hiện / quy trình

<div id="bkmrk--4">  
</div>#### Trường hợp 1: Module cần ghi thêm audit

Bước 1: Sử dụng cơ chế audit có sẵn của hệ thống

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-mpdagfqp-png.png)  
Ví dụ:

- AuditService
- Annotation audit
- Event audit

Bước 2: Không được tự tạo bảng audit riêng

Bước 3: Nếu cần mở rộng dữ liệu audit:

Tạo ticket:

\[AUDIT CHANGE REQUEST\] &lt;mô tả thay đổi&gt;

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-yjltkdar-png.png)

Bước 4: Core Team review:

- Xem xét nhu cầu
- Đánh giá ảnh hưởng hệ thống
- Quyết định phương án mở rộng

---

#### Trường hợp 2: Cần thay đổi cấu trúc bảng audit

Bước 1: Tạo ticket:

\[DB CORE CHANGE REQUEST\] Audit table change

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-mword8z7-png.png)

Bước 2: Cung cấp thông tin:

- Bảng audit bị ảnh hưởng
- Field cần thêm/sửa
- Lý do thay đổi
- Migration script

**Bước 3: Core Team review và quyết định**

### 6.5. Ví dụ minh họa

#### Trường hợp hợp lệ

Module planning muốn ghi log khi tạo kế hoạch:

auditService.logCreate("PLANNING", planningId, userId);

→ Sử dụng cơ chế audit có sẵn → hợp lệ.

---

#### Trường hợp không hợp lệ

Partner tự tạo bảng:

CREATE TABLE planning\_audit (

 id BIGINT,

 action VARCHAR(50)

);

→ Sai quy định vì:

- Tự tạo bảng audit riêng
- Không dùng hệ thống audit chung

---

#### Trường hợp bị cấm

Partner chạy trực tiếp:

DELETE FROM audit\_log WHERE created\_at &lt; '2024-01-01';

→ Không được phép xóa dữ liệu audit.

### 6.6. Checklist áp dụng

<div id="bkmrk--17">  
</div>Trước khi commit hoặc tạo PR:

- Có thay đổi bảng audit không?
- Nếu có → phải xin Core Team

- Có xóa dữ liệu audit không?
- Nếu có → không được phép

- Có tự tạo bảng audit riêng không?
- Nếu có → phải chuyển sang audit chung

- Có thay đổi logic audit không?
- Nếu có → phải tạo Audit Change Request

# 7.API Design & Response Standard

### 7.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định chuẩn API và định dạng response nhằm:

- Đảm bảo tính nhất quán giữa các module
- Dễ tích hợp giữa FE và BE
- Hỗ trợ versioning API trong tương lai
- Giảm rủi ro khi thay đổi cấu trúc dữ liệu

### 7.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Quy định này áp dụng cho:

- Tất cả API trong hệ thống
- Core Team và Partner Team
- Tất cả module trong thư mục modules/\*\*

Mọi API phải tuân theo:

- Chuẩn base path
- Chuẩn định dạng response

### 7.3. Quy định chính

<div id="bkmrk--2">  
</div>#### 7.3.1. Chuẩn Base Path

Tất cả API phải có dạng:

/api/v1/&lt;module-name&gt;

**vd: http://localhost:8080/api/v1/auth/config**

Trong đó:

- api: tiền tố chung của hệ thống
- v1: version của API
- &lt;module-name&gt;: tên module nghiệp vụ

---

##### Ví dụ base path hợp lệ

<div align="left" dir="ltr" id="bkmrk-module-base-path-aut"><table><colgroup><col width="78"></col><col width="127"></col></colgroup><tbody><tr><td>Module

</td><td>Base path

</td></tr><tr><td>auth

</td><td>/api/v1/auth

</td></tr><tr><td>planning

</td><td>/api/v1/planning

</td></tr><tr><td>document

</td><td>/api/v1/document

</td></tr><tr><td>user

</td><td>/api/v1/user

</td></tr></tbody></table>

</div>---

##### 7.3.2. Chuẩn Response

Tất cả API phải trả về:

ApiResponse&lt;T&gt;

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/UW3image-png.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/UW3image-png.png)

Không được trả:

- Entity
- DTO
- List
- Map
- String
- Boolean

trực tiếp ra API.

---

##### Cấu trúc chuẩn của ApiResponse

Ví dụ:

{

 "success": true,

 "message": "OK",

 "data": {

 "id": 1,

 "name": "Test"

 }

}

Hoặc với danh sách:

{

 "success": true,

 "message": "OK",

 "data": \[

 { "id": 1, "name": "A" },

 { "id": 2, "name": "B" }

 \]

}

### 7.4. Cách thực hiện / quy trình

#### Bước 1: Tạo controller theo base path

Ví dụ:

@RestController

@RequestMapping("/api/v1/planning")

public class PlanningController {

}

---

#### Bước 2: Service trả DTO

public PlanningResponse getById(Long id) {

 // xử lý nghiệp vụ

}

---

#### Bước 3: Controller trả ApiResponse

@GetMapping("/{id}")

public ApiResponse&lt;PlanningResponse&gt; getById(@PathVariable Long id) {

 return ApiResponse.ok(planningService.getById(id));

}

### 7.5. Ví dụ minh họa

#### Trường hợp sai

@GetMapping("/planning")

public List&lt;PlanningEntity&gt; getAll() {

 return planningRepository.findAll();

}

Sai vì:

- Trả entity trực tiếp
- Không dùng ApiResponse
- Bỏ qua service layer

---

#### Trường hợp đúng

@GetMapping

public ApiResponse&lt;List&lt;PlanningResponse&gt;&gt; getAll() {

 return ApiResponse.ok(planningService.getAll());

}

---

#### Trường hợp sai về base path

@RequestMapping("/planning")

→ Sai vì thiếu:

/api/v1/

### 7.6. Checklist áp dụng

<div id="bkmrk--28">  
</div>Trước khi commit hoặc tạo PR:

- API có đúng base path /api/v1/&lt;module&gt; không?
- Controller có trả ApiResponse&lt;T&gt; không?
- Có trả raw object không?
- Nếu có → phải sửa

- Có trả Entity trực tiếp không?
- Nếu có → phải chuyển sang DTO

- API có đúng version (v1) không?

# 8. Cơ chế xác thực & tích hợp SSO (WSO2)

### 8.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Quy định cơ chế xác thực linh hoạt giữa:

- Local Login
- SSO qua WSO2 IS

Nhằm:

- Phục vụ dev/test
- Hỗ trợ định danh tập trung khi triển khai

### 8.2. Khái niệm / phạm vi áp dụng

Hai chế độ xác thực:

<div align="left" dir="ltr" id="bkmrk-ch%E1%BA%BF-%C4%91%E1%BB%99-m%C3%B4-t%E1%BA%A3-local-l"><table><tbody><tr><td>Chế độ

</td><td>Mô tả

</td></tr><tr><td>Local

</td><td>Login nội bộ

</td></tr><tr><td>WSO2

</td><td>Login qua SSO OIDC

</td></tr></tbody></table>

</div>### 8.3. Quy định chính

<div id="bkmrk--1">  
</div>#### Nguyên tắc chung

Logic xác thực nằm trong:  
  
modules/auth

common/security

- Partner không được sửa logic xác thực
- Không thêm cơ chế login khác

---

#### Cấu hình chế độ đăng nhập

auth.type = local

auth.type = wso2

---

#### Local Login

- Xác thực username/password
- Dữ liệu ở bảng user\_account
- BE sinh token

---

#### WSO2 SSO flow

1. FE redirect sang WSO2
2. User login
3. WSO2 trả access\_token

FE gọi BE với:  
  
Authorization: Bearer &lt;token&gt;

4. BE validate token

---

#### Quy tắc FE

- FE đọc authType từ backend
- local → hiển thị form login
- wso2 → auto redirect

Không được hardcode.

---

#### Quy tắc BE

- Tất cả API qua security filter
- Không bypass auth

**Không hardcode user**

### 8.4. Quy trình thêm FE mới vào SSO

<div id="bkmrk--8">  
</div>1. Partner cung cấp:
- Domain FE
- Redirect URI
- Môi trường

3. Core tạo OAuth client
4. Cấp client\_id, scope

### 8.5. Checklist tích hợp xác thực

<div id="bkmrk--9">  
</div>- Token gửi qua Authorization: Bearer
- API trả 401 nếu token sai
- Audit log ghi nhận login

# 9.Git Workflow & Pull Request

### Chú ý: tham chiếu sang tài liệu để theo dõi rõ ràng đầy đủ hơn: https://docs.lifetex.vn/books/quy-dinh-git-workflow-va-quy-uoc-code c  
9.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định chuẩn commit và quy trình merge code nhằm:

- Đảm bảo lịch sử thay đổi rõ ràng, dễ truy vết
- Giảm xung đột giữa các nhóm phát triển
- Đảm bảo chất lượng code trước khi đưa vào nhánh chính
- Chuẩn hóa quy trình phát triển giữa Core Team và Partner

### 9.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Quy định này áp dụng cho:

- Tất cả repository của dự án
- Core Team và Partner Team
- Mọi commit và Pull Request (PR)

Quy trình phát triển tuân theo:

- Chuẩn format commit
- Chiến lược branch

**Điều kiện merge bắt buộc**

### 9.3. Quy định chính

#### 9.3.1. Format commit

Tất cả commit phải theo format:

&lt;type&gt;: &lt;description&gt;

Trong đó:

- &lt;type&gt;: loại thay đổi
- &lt;description&gt;: mô tả ngắn gọn nội dung thay đổi

#### Ví dụ commit hợp lệ

feat: add planning API

fix: validate file upload

refactor: optimize planning service

docs: update API guideline

test: add unit test for planning service

chore: update dependency versions

---

<span style="color: rgb(34, 34, 34); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Roboto, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 1.666em; font-weight: 400;">Type hợp lệ</span>

<div align="left" dir="ltr" id="bkmrk-type-%C3%9D-ngh%C4%A9a-feat-th"><table><colgroup><col width="65"></col><col width="256"></col></colgroup><tbody><tr><td>Type

</td><td>Ý nghĩa

</td></tr><tr><td>feat

</td><td>Thêm chức năng mới

</td></tr><tr><td>fix

</td><td>Sửa lỗi

</td></tr><tr><td>refactor

</td><td>Tối ưu, không đổi logic

</td></tr><tr><td>docs

</td><td>Cập nhật tài liệu

</td></tr><tr><td>test

</td><td>Thêm hoặc sửa test

</td></tr><tr><td>chore

</td><td>Thay đổi cấu hình, build, dependency

</td></tr></tbody></table>

</div>---

#### 9.3.2. Branch strategy

Hệ thống sử dụng mô hình branch chuẩn:

<div align="left" dir="ltr" id="bkmrk-branch-m%E1%BB%A5c-%C4%91%C3%ADch-main"><table><colgroup><col width="72"></col><col width="244"></col></colgroup><tbody><tr><td>Branch

</td><td>Mục đích

</td></tr><tr><td>main

</td><td>Bản stable, dùng cho production

</td></tr><tr><td>develop

</td><td>Bản tích hợp, nơi merge các feature

</td></tr><tr><td>feature/\*

</td><td>Phát triển chức năng mới

</td></tr><tr><td>fix/\*

</td><td>Sửa lỗi

</td></tr></tbody></table>

</div>---

#### Quy tắc làm việc với branch

- Không commit trực tiếp vào main
- Không commit trực tiếp vào develop
- Mọi thay đổi phải qua Pull Request
- Branch phải được đặt tên đúng quy tắc

---

<span style="color: rgb(34, 34, 34); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Roboto, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 1.666em; font-weight: 400;">Ví dụ tên branch hợp lệ</span>

feature/planning-create-api

feature/document-upload

fix/login-null-pointer

fix/planning-date-validation

---

<span style="color: rgb(34, 34, 34); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Roboto, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 2.333em; font-weight: 400;">9.3.3. Điều kiện merge Pull Request</span>

Pull Request chỉ được merge khi đáp ứng đầy đủ các điều kiện:

1. Build thành công

Không sửa:  
  
common/\*\*

config/\*\*

2. Không sửa shared entity

**Có review từ Core Team**

### 9.4. Cách thực hiện / quy trình

<div id="bkmrk--12">  
</div>#### Quy trình phát triển tính năng

Bước 1: Tạo branch từ develop

git checkout develop

git pull

git checkout -b feature/planning-create-api

Bước 2: Thực hiện commit theo chuẩn

git commit -m "feat: add planning create API"

Bước 3: Push branch lên remote

git push origin feature/planning-create-api

Bước 4: Tạo Pull Request vào develop

Bước 5: Core Team review

Bước 6: Nếu đạt yêu cầu → merge vào develop

---

#### Quy trình sửa lỗi

Bước 1: Tạo branch fix

git checkout develop

git checkout -b fix/planning-null-error

Bước 2: Commit

git commit -m "fix: handle null planning name"

Bước 3: Tạo PR và chờ review

### 9.5. Ví dụ minh họa

#### Trường hợp commit sai

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-slvhkcbl-png.png)

Sai vì:

- Không theo format chuẩn
- Không có type

---

<span style="color: rgb(34, 34, 34); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Roboto, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 1.666em; font-weight: 400;">Trường hợp commit đúng</span>

fix: handle null planning name

![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-xikz8bws-png.png)

---

#### Trường hợp merge sai

Partner:

- Commit trực tiếp vào develop
- Không tạo PR
- Không có review

→ Vi phạm quy trình.

---

#### Trường hợp merge đúng

1. Tạo branch feature/planning-api
2. Commit theo chuẩn
3. Tạo PR vào develop
4. Core Team review
5. Build pass
6. Merge

**![](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/embedded-image-g3tqv2lw-png.png)**

### 9.6. Checklist áp dụng

<div id="bkmrk--37">  
</div>Trước khi tạo Pull Request:

- Commit đúng format &lt;type&gt;: &lt;description&gt;
- Branch đúng quy tắc feature/\* hoặc fix/\*
- Không commit trực tiếp vào main hoặc develop
- Build thành công
- Không sửa:
- common/\*\*
- config/\*\*
- shared entity

- PR đã được Core Team review

# 10.Checklist kiểm tra trước khi merge

### 10.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định checklist bắt buộc trước khi merge code nhằm:

- Đảm bảo chất lượng code trước khi tích hợp
- Tránh vi phạm các quy định về kiến trúc và dữ liệu
- Giảm rủi ro lỗi khi build hoặc triển khai
- Chuẩn hóa quy trình review giữa Core Team và Partner

### 10.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Checklist này áp dụng cho:

- Tất cả Pull Request (PR)
- Core Team và Partner Team
- Mọi module trong hệ thống

Checklist được dùng trong:

- Code review
- Kiểm tra trước khi merge vào develop

**Kiểm tra trước khi release**

### 10.3. Quy định chính

<div id="bkmrk--2">  
</div>Pull Request chỉ được phép merge khi đáp ứng đầy đủ các điều kiện sau:

Code nằm trong khu vực được phép:  
  
modules/\*\*

1. Không sửa các khu vực bị cấm:

  
common/\*\*

config/\*\*

2. Không sửa shared entity trong:

  
common/entity/\*\*

3. Nếu có thay đổi database:
- Phải có migration script

5. API phải đúng chuẩn:

/api/v1/&lt;module-name&gt;

6. Response phải dùng:

ApiResponse&lt;T&gt;

 7.Build phải thành công

### 10.4. Cách thực hiện / quy trình

#### Quy trình kiểm tra trước khi merge

Bước 1: Dev hoàn thành feature hoặc fix bug  
Bước 2: Tạo Pull Request vào develop  
Bước 3: Dev tự kiểm tra checklist  
Bước 4: Core Team review theo checklist  
Bước 5: Nếu đạt yêu cầu → merge

Nếu không đạt:

- PR bị reject
- Dev phải sửa lại theo checklist

### 10.5. Ví dụ minh họa

#### Trường hợp hợp lệ

PR:

Chỉ sửa code trong:  
  
modules/planning/\*\*

- Có migration: V20260210\_\_add\_priority\_to\_planning.sql
- API: /api/v1/planning
- Build thành công

→ Được merge.

---

#### Trường hợp không hợp lệ

PR:

Sửa file:  
  
common/entity/UserEntity.java

- 

→ Vi phạm quy định shared entity.

→ PR bị từ chối.

---

#### Trường hợp bị từ chối do thiếu migration

PR:

Thêm field trong entity:  
  
private Integer priority;

- 
- Không có migration SQL

→ PR bị reject.

### 10.6. Checklist áp dụng

<div id="bkmrk--11">  
</div>Dev phải tick đủ các mục sau trước khi merge:

\[ \] Code nằm trong modules/\*\*

\[ \] Không sửa common/\*\*

\[ \] Không sửa config/\*\*

\[ \] Không sửa shared entity

\[ \] Có migration nếu thay đổi DB

\[ \] API đúng chuẩn /api/v1/

\[ \] Response dùng ApiResponse

\[ \] Build thành công

Core Team chỉ merge khi checklist đạt đầy đủ.

# 11.Quy trình phối hợp & quản lý thay đổi liên nhóm

### 11.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chương này quy định cách phối hợp giữa nhiều đơn vị phát triển nhằm:

- Tránh xung đột giữa FE, BE và Database
- Đảm bảo tích hợp ổn định
- Giữ tính tương thích giữa các module
- Kiểm soát các thay đổi ảnh hưởng hệ thống

### 11.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Áp dụng khi:

- Có nhiều đơn vị cùng phát triển
- FE và BE tách source
- Nhiều FE dùng chung BE
- BE tách thành nhiều service

Mọi thay đổi liên quan:

- API contract
- Entity/Database
- Auth
- Domain/CORS

đều phải thông qua Core Team.

### 11.3. Quy định chính

#### Nguyên tắc chung

- Dùng contract-first (OpenAPI là nguồn chuẩn)
- Mọi thay đổi API hoặc DB phải được kiểm soát
- Mọi thay đổi breaking phải qua Core Team

---

#### Trường hợp FE và BE khác source, dùng chung DB

Rủi ro:

- Sai contract API
- Response làm FE vỡ
- Migration xung đột

Quy định:

- BE thay đổi API/DB phải update OpenAPI
- Mọi thay đổi DB phải có migration
- Staging dùng DB chung

Quy trình:

1. FE tạo ticket: API CONTRACT ISSUE
2. BE/Core xác nhận
3. Fix backward compatible hoặc tạo API v2

---

#### Trường hợp FE và BE dùng DB riêng khi dev

Quy định:

- Có DB baseline chung
- Staging dùng DB chung

Quy trình:

1. Nếu staging lỗi → so version migration
2. Không cho merge nếu thiếu migration

---

#### Trường hợp nhiều FE dùng chung một BE

Quy định:

- Dùng IdP chung (WSO2/Keycloak)
- Mỗi FE là một OAuth client riêng
- Gọi API bằng JWT

Quy trình:

1. FE đăng ký client\_id
2. Core cấu hình redirectUri, scope
3. Core cấp quyền

---

#### Trường hợp FE chung source, BE nhiều service

Quy định:

- FE cấu hình baseURL theo service
- Auth dùng token chung
- CORS do Core Team quản lý

Quy trình:

1. Thêm service → tạo Integration Checklist
2. Core cấu hình CORS và token audience

---

#### Trường hợp thay đổi response làm FE vỡ

Quy định:

- Chỉ được thêm field (non-breaking)
- Không đổi kiểu dữ liệu
- Không rename field
- Breaking change phải tạo /api/v2

Quy trình:

1. Tạo BREAKING CHANGE REQUEST
2. Core duyệt
3. Thông báo FE

---

#### Trường hợp xung đột entity/model

Quy định:

- Entity dùng chung → coi là shared model
- Phải qua Data Model Change Request

Quy trình:

1. Partner đề xuất field + migration
2. Core review
3. Approve hoặc yêu cầu sửa

---

#### Trường hợp migration xung đột

Quy định:

- Không sửa migration đã merge
- Tạo migration mới để fix forward

Quy trình:

1. Rebase
2. Tạo migration mới
3. Test staging

---

#### Trường hợp seed/master data bị sửa

Quy định:

- Seed core do Core Team quản lý
- Partner chỉ được đề xuất

Quy trình:

1. Tạo MASTER DATA REQUEST
2. Core duyệt và triển khai

### 11.4. Quy trình triển khai tính năng nhiều tầng

<div id="bkmrk--11">  
</div>1. Tạo ticket Feature
2. Chốt API contract (OpenAPI)
3. Chốt entity/migration
4. BE implement
5. FE integrate
6. Test staging
7. Release

### 11.5.Ví dụ minh họa

<div id="bkmrk--12">  
</div>**1. bảng mô tả các môi trường hệ thống:**

<table class="w-fit min-w-(--thread-content-width)" data-end="1244" data-start="953" id="bkmrk-env-fe-url-be-url-db"><thead data-end="990" data-start="953"><tr data-end="990" data-start="953"><th class="" data-col-size="sm" data-end="959" data-start="953">Env</th><th class="" data-col-size="sm" data-end="968" data-start="959">FE URL</th><th class="" data-col-size="sm" data-end="977" data-start="968">BE URL</th><th class="" data-col-size="sm" data-end="982" data-start="977">DB</th><th class="" data-col-size="sm" data-end="990" data-start="982">Auth</th></tr></thead><tbody data-end="1244" data-start="1029"><tr data-end="1099" data-start="1029"><td data-col-size="sm" data-end="1035" data-start="1029">dev</td><td data-col-size="sm" data-end="1059" data-start="1035"><a class="decorated-link cursor-pointer" data-end="1058" data-start="1037" rel="noopener" target="_new">http://localhost:4200<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a></td><td data-col-size="sm" data-end="1083" data-start="1059"><a class="decorated-link cursor-pointer" data-end="1082" data-start="1061" rel="noopener" target="_new">http://localhost:8080<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a></td><td data-col-size="sm" data-end="1091" data-start="1083">local</td><td data-col-size="sm" data-end="1099" data-start="1091">mock</td></tr><tr data-end="1182" data-start="1100"><td data-col-size="sm" data-end="1110" data-start="1100">staging</td><td data-col-size="sm" data-end="1135" data-start="1110"><a class="decorated-link cursor-pointer" data-end="1134" data-start="1112" rel="noopener" target="_new">https://staging.app.vn<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a></td><td data-col-size="sm" data-end="1160" data-start="1135"><a class="decorated-link cursor-pointer" data-end="1159" data-start="1137" rel="noopener" target="_new">https://staging.api.vn<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a></td><td data-col-size="sm" data-end="1169" data-start="1160">shared</td><td data-col-size="sm" data-end="1182" data-start="1169">WSO2 thật</td></tr><tr data-end="1244" data-start="1183"><td data-col-size="sm" data-end="1190" data-start="1183">prod</td><td data-col-size="sm" data-end="1207" data-start="1190">[https://app.vn<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span>](https://app.vn/)</td><td data-col-size="sm" data-end="1224" data-start="1207"><a class="decorated-link cursor-pointer" data-end="1223" data-start="1209" rel="noopener" target="_new">https://api.vn<span aria-hidden="true" class="ms-0.5 inline-block align-middle leading-none"><svg aria-hidden="true" class="block h-[0.75em] w-[0.75em] stroke-current stroke-[0.75]" data-rtl-flip="" height="20" width="20" xmlns="http://www.w3.org/2000/svg"></svg></span></a></td><td data-col-size="sm" data-end="1231" data-start="1224">prod</td><td data-col-size="sm" data-end="1244" data-start="1231">WSO2 thật</td></tr></tbody></table>

# 12. Connect Database & mẫu CRUD & switch Oracle/MSSQL

### 12.Quy định kết nối Database và Message Queue

### 12.1. Mục tiêu chương

<div id="bkmrk-">  
</div>Chuẩn hóa kết nối DB và Message Queue nhằm:

- Tránh hardcode cấu hình
- Đảm bảo tính ổn định hạ tầng
- Kiểm soát tài nguyên hệ thống

### 12.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--1">  
</div>Áp dụng cho:

- Database
- RabbitMQ
- Kafka
- Core Team và Partner

### 12.3. Quy định chính

#### Kết nối Database

Cấu hình datasource tại:  
  
config/\*\*

application-\*.yml

- 
- Không hardcode:
- host
- port
- user
- password

- Không tạo JDBC connection thủ công
- Mọi truy cập DB qua repository/service

---

#### Message Queue

- Kết nối queue do Core Team quản lý

Cấu hình tại:  
  
config/\*\*

common/messaging/\*\*

- 

Partner không được:

- Tự tạo connection
- Hardcode config
- Tự tạo queue/topic

---

#### Chuẩn đặt tên queue/topic

&lt;system&gt;.&lt;module&gt;.&lt;purpose&gt;

Ví dụ:

core.audit.log

planning.project.created

auth.user.synced

### 12.4. Quy trình tạo queue/topic

<div id="bkmrk--7">  
</div>Tạo ticket:  
  
\[QUEUE REQUEST\] &lt;module&gt; - &lt;purpose&gt;

1. 
2. Cung cấp:
- module
- mục đích
- producer
- consumer
- retry/DLQ


**Core tạo queue và cấp quyền**

### 12.5. Checklist DB &amp; Queue

<div id="bkmrk--8">  
</div>Database:

- Không hardcode kết nối
- Có migration khi đổi DB

Queue:

- Queue do Core cấp
- Có log và retry/DLQ

### 13.1. Mục tiêu chương

<div id="bkmrk--2">  
</div>Thiết kế kiến trúc đa database nhằm:

- Tách logic nghiệp vụ và truy vấn
- Switch Oracle/MSSQL bằng cấu hình
- Không sửa service khi đổi DB
- Áp dụng nguyên tắc SOLID

### 13.2. Khái niệm / phạm vi áp dụng

<div id="bkmrk--6">  
</div>Áp dụng cho:

- Các module hỗ trợ đa DB
- Các hệ thống cần deploy trên:
- Oracle
- SQL Server


### 13.3. Quy định chính

<div id="bkmrk--9">  
</div>#### Nguyên tắc thiết kế

- Service không phụ thuộc DB
- Query tách theo dialect:
- Oracle
- MSSQL

- Repository chỉ thực thi SQL
- Switch DB bằng config:

db.vendor = oracle | mssql

---

#### Cấu trúc khuyến nghị

modules/&lt;module&gt;/

 controller/

 service/

 repository/

 query/

 QueryProvider.java

 OracleQueryProvider.java

 SqlServerQueryProvider.java

 entity/

 dto/

---

#### Quy định đa DB

- Không viết SQL trong Service/Controller
- Không if/else theo DB trong Service
- Khác biệt DB chỉ nằm ở QueryProvider
- Dùng named parameters: :param

### 13.4. Cách thực hiện / quy trình

#### Khi thay đổi nghiệp vụ

1. Sửa Service
2. Cập nhật query tương ứng trong provider

---

#### Khi khác DB

1. Chỉ sửa query trong:
- OracleQueryProvider
- SqlServerQueryProvider

3. Không sửa Service

---

#### Khi tạo Pull Request

PR phải:

- Ghi rõ ảnh hưởng
- Có smoke test trên env tương ứng

### 13.5. Ví dụ minh họa

#### Cấu hình

db.vendor = mssql

Hệ thống tự inject:

SqlServerQueryProvider

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/IaDimage-png.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/IaDimage-png.png)

---

#### Ví dụ sai

if (dbType.equals("oracle")) {

 // SQL

} else {

 // SQL

}

Sai vì logic DB nằm trong Service.

---

#### Ví dụ đúng

Service:

queryProvider.getFindAllPlanningSql();

QueryProvider quyết định SQL theo DB.

### 13.6. Checklist áp dụng

<div id="bkmrk--34">  
</div>- Service không chứa SQL
- Không if/else theo DB trong service
- SQL nằm trong QueryProvider
- Query dùng named parameters
- PR có test trên DB tương ứng

# 13.Swagger API Documentation & JWT Testing

### 13.1. Mục tiêu chương

Chương này quy định cách:

- Tạo tài liệu API bằng Swagger/OpenAPI
- Cấu hình Swagger cho Spring Boot
- Tích hợp xác thực JWT vào Swagger
- Test API có auth trực tiếp trên Swagger UI

Mục tiêu:

- Dev và QA có thể test API nhanh
- Chuẩn hóa tài liệu API
- Tránh test sai do thiếu token
- Đồng nhất cách dùng Swagger giữa các team

---

### 13.2. Truy cập Swagger UI

Sau khi chạy service:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui/index.html`</div></div>Hoặc:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui.html`</div></div>Swagger hiển thị danh sách API theo controller.

---

### 13.3. Cấu hình Swagger trong Spring Boot

Dependency Maven:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%3Cdependency%3E-%3Cgroupi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-tag"><<span class="hljs-name">dependency</span></span>>    <span class="hljs-tag"><<span class="hljs-name">groupId</span></span>>org.springdoc<span class="hljs-tag"></<span class="hljs-name">groupId</span></span>>    <span class="hljs-tag"><<span class="hljs-name">artifactId</span></span>>springdoc-openapi-starter-webmvc-ui<span class="hljs-tag"></<span class="hljs-name">artifactId</span></span>>    <span class="hljs-tag"><<span class="hljs-name">version</span></span>>2.7.0<span class="hljs-tag"></<span class="hljs-name">version</span></span>><span class="hljs-tag"></<span class="hljs-name">dependency</span></span>>`</div></div>---

#### 13.3.1. Cấu hình OpenAPI + JWT

File:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-config%2Fopenapiconfig"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`config/OpenApiConfig.java`</div></div><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40configuration-publi"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@Configuration</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">OpenApiConfig</span> {    <span class="hljs-meta">@Bean</span>    <span class="hljs-keyword">public</span> OpenAPI <span class="hljs-title function_">api</span><span class="hljs-params">()</span> {        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">OpenAPI</span>()                .info(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Info</span>()                        .title(<span class="hljs-string">"Company Backend API"</span>)                        .version(<span class="hljs-string">"v1.0"</span>)                        .description(<span class="hljs-string">"API documentation"</span>))                .components(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Components</span>()                        .addSecuritySchemes(<span class="hljs-string">"bearerAuth"</span>,                                <span class="hljs-keyword">new</span> <span class="hljs-title class_">SecurityScheme</span>()                                        .type(SecurityScheme.Type.HTTP)                                        .scheme(<span class="hljs-string">"bearer"</span>)                                        .bearerFormat(<span class="hljs-string">"JWT"</span>)                        )                )                .addSecurityItem(<span class="hljs-keyword">new</span> <span class="hljs-title class_">SecurityRequirement</span>().addList(<span class="hljs-string">"bearerAuth"</span>));    }}`</div></div>---

### 13.4. Đánh dấu API cần JWT trong Controller

Controller cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40restcontroller-%40req"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@RestController</span><span class="hljs-meta">@RequestMapping("/api/v1/users")</span><span class="hljs-meta">@SecurityRequirement(name = "bearerAuth")</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserController</span> {`</div></div>Swagger sẽ hiển thị icon:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%F0%9F%94%92-get-%2Fapi%2Fv1%2Fusers"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`🔒 GET /api/v1/users`</div></div>---

### 13.5. Cấu hình Spring Security cho Swagger

Swagger UI phải được phép truy cập không cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.requestmatchers%28-%22%2F"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`.requestMatchers(    <span class="hljs-string">"/swagger-ui/**"</span>,    <span class="hljs-string">"/swagger-ui.html"</span>,    <span class="hljs-string">"/v3/api-docs/**"</span>).permitAll()`</div></div>API business:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.anyrequest%28%29.authen"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-selector-class">.anyRequest</span>()<span class="hljs-selector-class">.authenticated</span>()`</div></div>---

### 13.6. Test API có JWT trên Swagger (Step-by-Step)

#### Bước 1. Login lấy JWT

Gọi API login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-post-%2Fapi%2Fv1%2Fauth%2Flo"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`POST /api/v1/auth/login`</div></div>Response:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%7B-%22token%22%3A-%22eyjhbgci"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-punctuation">{</span>  <span class="hljs-attr">"token"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."</span><span class="hljs-punctuation">}</span>`</div></div>---

#### Bước 2. Mở Swagger

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-http%3A%2F%2Flocalhost%3A808-2"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`http://localhost:8080/swagger-ui/index.html`</div></div>---

#### Bước 3. Click Authorize

Góc phải Swagger:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorize-%F0%9F%94%92"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Authorize</span> 🔒`</div></div>Click vào.

---

#### Bước 4. Nhập JWT

Nhập:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-bearer-eyjhbgcioijiu"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Bearer</span> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`</div></div>Lưu ý:

- Phải có chữ **Bearer**
- Có khoảng trắng sau Bearer

Ví dụ đúng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-bearer-eyjhbgcioijiu-1"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Bearer</span> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`</div></div>---

#### Bước 5. Authorize

Click:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorize"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Authorize</span>`</div></div>Swagger sẽ lưu token cho toàn bộ API.

---

#### Bước 6. Test API

Chọn API có 🔒:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-get-%2Fapi%2Fv1%2Ftheme-co"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`GET /api/v1/theme-config`</div></div>Click:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-execute"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-keyword">Execute</span>`</div></div>Swagger gửi request:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-authorization%3A-beare"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-section">Authorization: Bearer eyJhbGciOiJIUzI1Ni...</span>`</div></div>---

### 13.7. Kiểm tra Swagger đã gửi token chưa

Trong Swagger phần Curl phải có:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk--h-%22authorization%3A-b"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`-H <span class="hljs-string">"Authorization: Bearer eyJhbGciOiJIUzI1Ni..."</span>`</div></div>Nếu không có → chưa Authorize.

---

### 13.8. Lỗi thường gặp

#### 403 Forbidden

Nguyên nhân:

- Chưa nhập token
- Token sai
- Token hết hạn
- API cần auth nhưng Swagger chưa Authorize

---

#### Không thấy nút Authorize

Nguyên nhân:

- Chưa cấu hình SecurityScheme
- Swagger config sai
- Cache browser

Khắc phục:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-ctrl-%2B-f5"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Ctrl</span> + F5`</div></div>---

#### Swagger call được API không cần token

Nguyên nhân:

Security config permitAll API.

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-.requestmatchers%28%22%2Fa"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-selector-class">.requestMatchers</span>("/api/v1/**")<span class="hljs-selector-class">.permitAll</span>()`</div></div>Swagger không cần JWT.

---

### 13.9. Quy định chuẩn sử dụng Swagger trong dự án

Controller cần auth:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40securityrequirement"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-meta">@SecurityRequirement(name = <span class="hljs-string">"bearerAuth"</span></span>)`</div></div>Controller public:

không cần annotation.

---

API test phải:

- Login lấy JWT
- Authorize Swagger
- Test API

---

### 13.10. Quy định commit &amp; PR

API mới phải:

- Hiển thị trên Swagger
- Có request/response schema
- Có SecurityRequirement nếu cần auth
- Test được trên Swagger

PR bị reject nếu:

- API không có Swagger
- Swagger lỗi
- Không test được

---

### 13.11. Checklist Swagger cho Dev

Trước khi merge:

- Swagger mở được
- API hiển thị
- Authorize hoạt động
- JWT call OK
- Response đúng schema

---

### 13.12. Best Practice

Không permitAll API production.

Swagger chỉ permit:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%2Fswagger-ui%2F%2A%2A-%2Fv3%2Fa"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
</div></div></div><div class="overflow-y-auto p-4" dir="ltr">`/swagger-ui/**/v3/api-docs/**`</div></div>JWT luôn test qua Authorize.