# Tài liệu hướng dẫn quy trình phát triển Api sử dụng Springboot

# Tổng quan kiến trúc hệ thống

# Nội dung chi tiết

### 1. Tổng quan kiến trúc hệ thống

#### 1.1. Mục tiêu kiến trúc

Kiến trúc backend được thiết kế nhằm:

- Đảm bảo khả năng mở rộng và bảo trì lâu dài
- Tách biệt rõ trách nhiệm giữa các layer
- Chuẩn hóa cách tổ chức code giữa các module
- Hỗ trợ phát triển song song nhiều developer
- Dễ dàng tích hợp các hệ thống ngoài (SSO, Cache, MQ…)

Tất cả các service backend phải tuân thủ kiến trúc chuẩn được định nghĩa trong tài liệu này.

---

#### 1.2. Mô hình kiến trúc

Hệ thống backend sử dụng mô hình:

**Modular Monolith – Layered Architecture**

Đặc điểm:

- Một service Spring Boot deploy độc lập
- Bên trong chia module theo domain
- Mỗi module có đầy đủ layer Controller → Service → Repository
- Có thể tách thành microservice khi cần

Sơ đồ tổng thể:

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

Tích hợp ngoài:

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

---

### 1.3. Quy tắc phân lớp (Layered Architecture)

Mỗi module phải tuân thủ cấu trúc phân lớp sau:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-controller-service-r"><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">controller</span>servicerepositoryentitydtomapperexception`</div></div>#### 1.3.1. Controller Layer (API Layer)

Chức năng:

- Nhận request HTTP
- Validate input DTO
- Gọi service xử lý
- Trả response chuẩn

Quy định:

- Không chứa business logic
- Không truy cập DB trực tiếp
- Không xử lý transaction
- Không map entity

Ví dụ:

<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/users")</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">final</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 class="hljs-meta">@Valid</span></span> <span class="hljs-meta">@RequestBody</span> UserRequest req) {        <span class="hljs-keyword">return</span> ApiResponse.success(userService.create(req));    }}`</div></div>---

#### 1.3.2. Service Layer (Business Layer)

Chức năng:

- Chứa toàn bộ business logic
- Điều phối repository
- Xử lý transaction
- Mapping entity ↔ DTO

Quy định:

- Không nhận HttpServletRequest
- Không trả Entity ra ngoài
- Phải qua DTO/Response
- Transaction đặt tại Service

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40service-public-clas"><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">@Service</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> {    <span class="hljs-meta">@Transactional</span>    <span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(UserRequest req)</span> {        <span class="hljs-type">User</span> <span class="hljs-variable">entity</span> <span class="hljs-operator">=</span> mapper.toEntity(req);        repository.save(entity);        <span class="hljs-keyword">return</span> mapper.toResponse(entity);    }}`</div></div>---

#### 1.3.3. Repository Layer (Persistence Layer)

Chức năng:

- Truy cập DB
- Query dữ liệu
- Mapping ORM

Quy định:

- Không chứa business logic
- Không gọi service
- Chỉ thao tác entity

Ví dụ:

<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_">UserRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">JpaRepository</span><User, Long> {}`</div></div>---

#### 1.3.4. Entity Layer

Chức năng:

- Mapping bảng DB
- Quan hệ ORM

Quy định:

- Không chứa logic nghiệp vụ
- Không trả trực tiếp ra API
- Không dùng cho request/response

---

#### 1.3.5. DTO Layer

Gồm:

- Request DTO
- Response DTO

Chức năng:

- Trao đổi dữ liệu với client
- Tách biệt entity và API

Quy định:

- Controller chỉ nhận/trả DTO
- Không expose entity

---

#### 1.3.6. Mapper Layer

Chức năng:

- Chuyển đổi DTO ↔ Entity

Có thể dùng:

- MapStruct
- Manual mapping

Quy định:

- Không đặt trong controller
- Không đặt trong repository

---

### 1.4. Quy tắc phụ thuộc (Dependency Direction)

Dependency chỉ được phép đi theo một chiều:

<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 → DB`</div></div>Không được phép:

- Repository gọi Service
- Service gọi Controller
- Controller gọi Repository trực tiếp
- DTO gọi Repository

Nguyên tắc:

**Layer trên được gọi layer dưới  
Layer dưới không được gọi layer trên**

---

### 1.5. Tổ chức module theo domain

Project được tổ chức theo domain thay vì technical layer toàn cục.

Cấu trúc chuẩn:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-com.company.project-"><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">`com.company.project ├── common │    ├── config │    ├── <span class="hljs-keyword">exception</span> │    ├── util │ ├── <span class="hljs-keyword">user</span> │    ├── controller │    ├── service │    ├── repository │    ├── entity │    ├── dto │    ├── mapper │ ├── document │    ├── controller │    ├── service │    ├── repository │    ├── entity │    ├── dto │    ├── mapper`</div></div>---

### 1.6. Tích hợp hệ thống ngoài

Backend có thể tích hợp các thành phần sau:

- SSO / Identity Server
- Cache (Redis)
- Message Queue
- File Storage
- External API

Quy tắc tích hợp:

- Gọi qua Service
- Không gọi trực tiếp từ Controller
- Tách client class riêng

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-integration-%E2%94%9C%E2%94%80%E2%94%80-sso-"><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">`integration ├── sso │    ├── SsoClient │ ├── redis │    ├── RedisService │ ├── <span class="hljs-keyword">external</span> │    ├── ExternalApiClient`</div></div>---

### 1.7. Nguyên tắc kiến trúc bắt buộc

Tất cả backend service phải tuân thủ:

- Không expose entity ra API
- Không viết business logic trong controller
- Không truy cập DB ngoài repository
- Không phụ thuộc ngược layer
- Transaction đặt tại service
- Mapping qua DTO

Vi phạm kiến trúc được xem là lỗi nghiêm trọng trong review code.

---

### 1.8. Khả năng mở rộng Microservice

Kiến trúc hiện tại cho phép tách module thành microservice khi cần:

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-user-module-%E2%86%92-user-s"><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">`user <span class="hljs-variable language_">module</span> → user-service<span class="hljs-variable language_">document</span> <span class="hljs-variable language_">module</span> → <span class="hljs-variable language_">document</span>-serviceauth <span class="hljs-variable language_">module</span> → auth-service`</div></div>### 1.9. Xử lý ngoại lệ toàn cục (Exception Handling Strategy)

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

Chuẩn hóa cơ chế xử lý lỗi toàn hệ thống nhằm:

- Đảm bảo response lỗi thống nhất giữa các API
- Ẩn thông tin nội bộ hệ thống
- Dễ dàng log và truy vết lỗi
- Hỗ trợ frontend xử lý lỗi chính xác
- Tránh trả lỗi không kiểm soát (stacktrace, SQL…)

Tất cả API backend phải sử dụng cơ chế xử lý ngoại lệ toàn cục.

---

#### 1.9.2. Global Exception Handler

Hệ thống sử dụng Global Exception Handler thông qua `@ControllerAdvice`.

Chức năng:

- Bắt tất cả exception phát sinh từ Controller / Service
- Mapping exception → HTTP status
- Trả response lỗi chuẩn
- Ghi log lỗi hệ thống

Quy định:

- Không xử lý lỗi trực tiếp trong Controller
- Không trả stacktrace ra client
- Không trả message DB/SQL
- Không throw Exception chung chung

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40restcontrolleradvic"><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">@RestControllerAdvice</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">GlobalExceptionHandler</span> {    <span class="hljs-meta">@ExceptionHandler(ResourceNotFoundException.class)</span>    <span class="hljs-keyword">public</span> ResponseEntity<ApiError> <span class="hljs-title function_">handleNotFound</span><span class="hljs-params">(ResourceNotFoundException ex)</span> {        <span class="hljs-keyword">return</span> build(HttpStatus.NOT_FOUND, ex.getCode(), ex.getMessage());    }    <span class="hljs-meta">@ExceptionHandler(AccessDeniedException.class)</span>    <span class="hljs-keyword">public</span> ResponseEntity<ApiError> <span class="hljs-title function_">handleForbidden</span><span class="hljs-params">(AccessDeniedException ex)</span> {        <span class="hljs-keyword">return</span> build(HttpStatus.FORBIDDEN, <span class="hljs-string">"ACCESS_DENIED"</span>, <span class="hljs-string">"Access denied"</span>);    }    <span class="hljs-meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span>    <span class="hljs-keyword">public</span> ResponseEntity<ApiError> <span class="hljs-title function_">handleValidation</span><span class="hljs-params">(MethodArgumentNotValidException ex)</span> {        <span class="hljs-keyword">return</span> build(HttpStatus.BAD_REQUEST, <span class="hljs-string">"VALIDATION_ERROR"</span>, <span class="hljs-string">"Invalid request"</span>);    }    <span class="hljs-meta">@ExceptionHandler(Exception.class)</span>    <span class="hljs-keyword">public</span> ResponseEntity<ApiError> <span class="hljs-title function_">handleSystem</span><span class="hljs-params">(Exception ex)</span> {        <span class="hljs-keyword">return</span> build(HttpStatus.INTERNAL_SERVER_ERROR, <span class="hljs-string">"SYSTEM_ERROR"</span>, <span class="hljs-string">"Internal server error"</span>);    }    <span class="hljs-keyword">private</span> ResponseEntity<ApiError> <span class="hljs-title function_">build</span><span class="hljs-params">(HttpStatus status, String code, String message)</span> {        <span class="hljs-type">ApiError</span> <span class="hljs-variable">error</span> <span class="hljs-operator">=</span> ApiError.of(code, message);        <span class="hljs-keyword">return</span> ResponseEntity.status(status).body(error);    }}`</div></div>---

#### 1.9.3. Mapping Exception → HTTP Status

Chuẩn mapping bắt buộc:

<div class="TyagGW_tableContainer" id="bkmrk-exception-http-statu"><div class="group TyagGW_tableWrapper flex flex-col-reverse w-fit" tabindex="-1"><table class="w-fit min-w-(--thread-content-width)" data-end="2826" data-start="2379"><thead data-end="2406" data-start="2379"><tr data-end="2406" data-start="2379"><th class="" data-col-size="sm" data-end="2391" data-start="2379">Exception</th><th class="" data-col-size="sm" data-end="2406" data-start="2391">HTTP Status</th></tr></thead><tbody data-end="2826" data-start="2434"><tr data-end="2473" data-start="2434"><td data-col-size="sm" data-end="2454" data-start="2434">ValidationException</td><td data-col-size="sm" data-end="2473" data-start="2454">400 Bad Request</td></tr><tr data-end="2516" data-start="2474"><td data-col-size="sm" data-end="2497" data-start="2474">MethodArgumentNotValid</td><td data-col-size="sm" data-end="2516" data-start="2497">400 Bad Request</td></tr><tr data-end="2561" data-start="2517"><td data-col-size="sm" data-end="2542" data-start="2517">IllegalArgumentException</td><td data-col-size="sm" data-end="2561" data-start="2542">400 Bad Request</td></tr><tr data-end="2601" data-start="2562"><td data-col-size="sm" data-end="2584" data-start="2562">AccessDeniedException</td><td data-col-size="sm" data-end="2601" data-start="2584">403 Forbidden</td></tr><tr data-end="2646" data-start="2602"><td data-col-size="sm" data-end="2626" data-start="2602">AuthenticationException</td><td data-col-size="sm" data-end="2646" data-start="2626">401 Unauthorized</td></tr><tr data-end="2690" data-start="2647"><td data-col-size="sm" data-end="2673" data-start="2647">ResourceNotFoundException</td><td data-col-size="sm" data-end="2690" data-start="2673">404 Not Found</td></tr><tr data-end="2730" data-start="2691"><td data-col-size="sm" data-end="2709" data-start="2691">BusinessException</td><td data-col-size="sm" data-end="2730" data-start="2709">422 Unprocessable</td></tr><tr data-end="2776" data-start="2731"><td data-col-size="sm" data-end="2747" data-start="2731">SystemException</td><td data-col-size="sm" data-end="2776" data-start="2747">500 Internal Server Error</td></tr><tr data-end="2826" data-start="2777"><td data-col-size="sm" data-end="2797" data-start="2777">Exception (default)</td><td data-col-size="sm" data-end="2826" data-start="2797">500 Internal Server Error</td></tr></tbody></table>

</div></div>Không được trả:

- 200 với lỗi business
- 500 với lỗi validation
- 500 với not found

---

#### 1.9.4. Chuẩn format response lỗi

Tất cả API khi lỗi phải trả format thống nhất:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%7B-%22timestamp%22%3A-%222026"><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">"timestamp"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2026-02-23T10:15:30Z"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"code"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"USER_NOT_FOUND"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"User not found"</span><span class="hljs-punctuation">,</span>  <span class="hljs-attr">"traceId"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"abc123"</span><span class="hljs-punctuation">}</span>`</div></div>Ý nghĩa:

<div class="TyagGW_tableContainer" id="bkmrk-field-m%C3%B4-t%E1%BA%A3-timestam"><div class="group TyagGW_tableWrapper flex flex-col-reverse w-fit" tabindex="-1"><table class="w-fit min-w-(--thread-content-width)" data-end="3296" data-start="3156"><thead data-end="3173" data-start="3156"><tr data-end="3173" data-start="3156"><th class="" data-col-size="sm" data-end="3164" data-start="3156">Field</th><th class="" data-col-size="sm" data-end="3173" data-start="3164">Mô tả</th></tr></thead><tbody data-end="3296" data-start="3190"><tr data-end="3217" data-start="3190"><td data-col-size="sm" data-end="3200" data-start="3190">timestamp</td><td data-col-size="sm" data-end="3217" data-start="3200">Thời điểm lỗi</td></tr><tr data-end="3242" data-start="3218"><td data-col-size="sm" data-end="3223" data-start="3218">code</td><td data-col-size="sm" data-end="3242" data-start="3223">Mã lỗi hệ thống</td></tr><tr data-end="3268" data-start="3243"><td data-col-size="sm" data-end="3251" data-start="3243">message</td><td data-col-size="sm" data-end="3268" data-start="3251">Thông báo lỗi</td></tr><tr data-end="3296" data-start="3269"><td data-col-size="sm" data-end="3277" data-start="3269">traceId</td><td data-col-size="sm" data-end="3296" data-start="3277">ID truy vết log</td></tr></tbody></table>

</div></div>Quy định:

- `code` phải ổn định, dùng cho frontend
- `message` có thể hiển thị user
- Không trả stacktrace
- Không trả SQL/exception message nội bộ

---

#### 1.9.5. Quy tắc định nghĩa Exception

Mỗi loại lỗi phải có class riêng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-exception-%E2%94%9C%E2%94%80%E2%94%80-resour"><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-variable">exception</span> ├── <span class="hljs-variable">ResourceNotFoundException</span> ├── <span class="hljs-variable">ValidationException</span> ├── <span class="hljs-variable">BusinessException</span> ├── <span class="hljs-built_in">SystemException</span>`</div></div>Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-resourc"><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_">ResourceNotFoundException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">RuntimeException</span> {    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String code;    <span class="hljs-keyword">public</span> <span class="hljs-title function_">ResourceNotFoundException</span><span class="hljs-params">(String code, String message)</span> {        <span class="hljs-built_in">super</span>(message);        <span class="hljs-built_in">this</span>.code = code;    }    <span class="hljs-keyword">public</span> String <span class="hljs-title function_">getCode</span><span class="hljs-params">()</span> {        <span class="hljs-keyword">return</span> code;    }}`</div></div>---

#### 1.9.6. Quy tắc sử dụng Exception

Service layer phải throw exception domain:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-user-getuser%28"><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_">getUser</span><span class="hljs-params">(Long id)</span> {    <span class="hljs-keyword">return</span> repository.findById(id)        .orElseThrow(() -> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ResourceNotFoundException</span>(            <span class="hljs-string">"USER_NOT_FOUND"</span>,            <span class="hljs-string">"User not found"</span>        ));}`</div></div>Không được:

- throw new Exception()
- throw RuntimeException chung chung
- trả null thay vì lỗi
- trả Optional ra controller

---

#### 1.9.7. TraceId và logging

Mỗi request phải có traceId để truy vết lỗi:

- Sinh tại filter/interceptor
- Ghi vào MDC log
- Trả về response lỗi

Ví dụ:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-public-class-apierro"><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_">ApiError</span> {    <span class="hljs-keyword">private</span> String timestamp;    <span class="hljs-keyword">private</span> String code;    <span class="hljs-keyword">private</span> String message;    <span class="hljs-keyword">private</span> String traceId;    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ApiError <span class="hljs-title function_">of</span><span class="hljs-params">(String code, String message)</span> {        <span class="hljs-type">ApiError</span> <span class="hljs-variable">e</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">ApiError</span>();        e.timestamp = Instant.now().toString();        e.code = code;        e.message = message;        e.traceId = MDC.get(<span class="hljs-string">"traceId"</span>);        <span class="hljs-keyword">return</span> e;    }}`</div></div>---

#### 1.9.8. Nguyên tắc bắt buộc

Tất cả backend service phải tuân thủ:

- Mọi lỗi phải qua GlobalExceptionHandler
- Không trả exception raw ra client
- Không xử lý lỗi trong controller
- Exception phải có code
- Response lỗi phải đúng format chuẩn
- Mapping đúng HTTP status

Vi phạm quy tắc xử lý lỗi được xem là lỗi nghiêm trọng trong code review

# Chương 1: Setup môi trường

Chương này hướng dẫn chuẩn bị môi trường phát triển cho dự án, bao gồm cài đặt công cụ cần thiết, cấu hình biến môi trường, thiết lập IDE và kiểm tra kết nối đến các dịch vụ phụ trợ (DB, cache, message broker…). Sau khi hoàn thành, lập trình viên có thể chạy dự án ở môi trường local.

# Hướng dẫn setup môi trường và start project

## 1. Mục tiêu

Hướng dẫn developer:

- Cài đặt môi trường chạy project
- Cấu hình biến môi trường
- Khởi động project Spring Boot
- Kiểm tra project chạy thành công

---

## 2. Yêu cầu hệ thống

### 2.1. Phần mềm cần cài đặt

<div class="TyagGW_tableContainer" id="bkmrk-c%C3%B4ng-c%E1%BB%A5-phi%C3%AAn-b%E1%BA%A3n-kh"><table class="w-fit min-w-(--thread-content-width)" data-end="764" data-start="550"><thead data-end="585" data-start="550"><tr data-end="585" data-start="550"><th class="" data-col-size="sm" data-end="560" data-start="550">Công cụ</th><th class="" data-col-size="sm" data-end="585" data-start="560">Phiên bản khuyến nghị</th></tr></thead><tbody data-end="764" data-start="614"><tr data-end="644" data-start="614"><td data-col-size="sm" data-end="623" data-start="614">Java JDK</td><td data-col-size="sm" data-end="644" data-start="623">**17 (bắt buộc)**</td></tr><tr data-end="659" data-start="645"><td data-col-size="sm" data-end="651" data-start="645">Maven</td><td data-col-size="sm" data-end="659" data-start="651">3.8+</td></tr><tr data-end="676" data-start="660"><td data-col-size="sm" data-end="664" data-start="660">Git</td><td data-col-size="sm" data-end="676" data-start="664">Mới nhất</td></tr><tr data-end="704" data-start="677"><td data-col-size="sm" data-end="681" data-start="677">IDE</td><td data-col-size="sm" data-end="704" data-start="681">IntelliJ IDEA 2023+</td></tr><tr data-end="734" data-start="705"><td data-col-size="sm" data-end="716" data-start="705">PostgreSQL</td><td data-col-size="sm" data-end="734" data-start="716">**14 hoặc 15**</td></tr><tr data-end="764" data-start="735"><td data-col-size="sm" data-end="743" data-start="735">DB Tool</td><td data-col-size="sm" data-end="764" data-start="743">DBeaver / PgAdmin</td></tr></tbody></table>

</div>---

### 2.2. Kiểm tra cài đặt

Mở terminal:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-java--version"><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">`java -version`</div></div>Kết quả mong muốn:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-java-version-%2217.0.x"><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">java</span> version <span class="hljs-string">"17.0.x"</span>`</div></div>Kiểm tra Maven:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-mvn--v"><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">`mvn -v`</div></div>---

## 3. Clone source code

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-link-driver%3A%C2%A0"><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">`link driver: https://drive.google.com/file/d/1Ytzo-_uYqhhD8Pa4HUmCEjwPatoCnfPe/view?usp=sharing `</div></div>---

## 4. Cấu hình biến môi trường

Project sử dụng file:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-application.properti"><div class="overflow-y-auto p-4" dir="ltr">`application.properties`</div></div>---

## 4.1. Các biến môi trường cơ bản

Ví dụ file:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-application.properti-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">`application.properties`</div></div><div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-server%3A-port%3A-8080-s"><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-attr">server:</span>  <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span><span class="hljs-attr">spring:</span>  <span class="hljs-attr">datasource:</span>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://localhost:5432/lifetex</span>    <span class="hljs-attr">username:</span> <span class="hljs-string">lifetex</span>    <span class="hljs-attr">pass</span>`<div>  
</div></div></div>```
#spring.application.name=demo
#server.port=8080
#
## ===== Oracle datasource =====
#spring.datasource.url=jdbc:oracle:thin:@//192.168.0.111:1111/ORCLPDB1
#spring.datasource.username=test
#spring.datasource.password=test
#spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
#
## ===== JPA =====
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.database-platform=org.hibernate.dialect.OracleDialect
#
## ===== Hikari pool (optional) =====
#spring.datasource.hikari.maximum-pool-size=10
#spring.datasource.hikari.minimum-idle=2
#spring.datasource.hikari.connection-timeout=30000
#
#management.endpoints.web.exposure.include=health,info
```

---

## 4.2. Cách chỉnh sửa biến môi trường

### Cách 1: Sửa trực tiếp trong file `application.properties`

Ví dụ đổi DB:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-spring%3A-datasource%3A-"><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-attr">spring:</span>  <span class="hljs-attr">datasource:</span>    <span class="hljs-attr">url:</span> <span class="hljs-string">jdbc:postgresql://localhost:5432/test_db</span>    <span class="hljs-attr">username:</span> <span class="hljs-string">test</span>    <span class="hljs-attr">password:</span> <span class="hljs-string">test123</span>`</div></div>---

### Cách 2: Dùng biến môi trường hệ điều hành

Ví dụ trong `application.properties`:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-spring%3A-datasource%3A--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-attr">spring:</span>  <span class="hljs-attr">datasource:</span>    <span class="hljs-attr">url:</span> <span class="hljs-string">${DB_URL}</span>    <span class="hljs-attr">username:</span> <span class="hljs-string">${DB_USER}</span>    <span class="hljs-attr">password:</span> <span class="hljs-string">${DB_PASS}</span>`</div></div>---

#### Trên Windows (PowerShell)

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%24env%3Adb_url%3D%22jdbc%3Apo"><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-variable">$env:DB_URL</span>=<span class="hljs-string">"jdbc:postgresql://localhost:5432/lifetex"</span><span class="hljs-variable">$env:DB_USER</span>=<span class="hljs-string">"lifetex"</span><span class="hljs-variable">$env:DB_PASS</span>=<span class="hljs-string">"123456"</span>`</div></div>---

#### Trên Linux/Mac

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-export-db_url%3Djdbc%3Ap"><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-built_in">export</span> DB_URL=jdbc:postgresql://localhost:5432/lifetex<span class="hljs-built_in">export</span> DB_USER=lifetex<span class="hljs-built_in">export</span> DB_PASS=123456`</div></div>---

## 5. Chọn profile chạy

Spring Boot thường dùng profile:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-dev-staging-prod"><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">`devstaging<span class="hljs-built_in">prod</span>`</div></div>Ví dụ chạy với profile `dev`.

---

### Cách cấu hình profile

#### Cách 1: Trong biến môi trường

Windows:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%24env%3Aspring_profiles"><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-variable">$env:SPRING_PROFILES_ACTIVE</span>=<span class="hljs-string">"dev"</span>`</div></div>Linux/Mac:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-export-spring_profil"><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-built_in">export</span> SPRING_PROFILES_ACTIVE=dev`</div></div>---

#### Cách 2: Trong IDE

VM options:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk--dspring.profiles.ac"><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-attr">-Dspring.profiles.active</span>=dev`</div></div>---

## 6. Build project

Trong thư mục project:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-mvn-clean-install"><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">`mvn clean install`</div></div>Nếu build thành công sẽ thấy:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-build-success"><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">BUILD</span> SUCCESS`</div></div>---

## 7. Start project

### Chạy trong IDE

Trong IntelliJ:

1. Mở project
2. Mở file:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-demoapplication.java"><div class="sticky top-[calc(var(--sticky-padding-top)+9*var(--spacing))]">  
</div><div class="overflow-y-auto p-4" dir="ltr">`DemoApplication.java`</div></div>3. Nhấn nút **Run**

---

## 8. Kiểm tra project chạy thành công

Sau khi start, kiểm tra log:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-started-demoapplicat"><div class="overflow-y-auto p-4" dir="ltr">`<span class="hljs-attribute">Started</span> DemoApplication in <span class="hljs-number">5</span>.<span class="hljs-number">123</span> seconds`</div></div>Mở trình duyệt:

<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:<span class="hljs-comment">//localhost:8080</span>`</div></div>Hoặc gọi API:

<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/api/v1/health`</div></div>

# 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.

# Chương 3: Triển khai hệ thống (Deploy)

Chương này hướng dẫn quy trình build và triển khai ứng dụng Java (Spring Boot) lên các môi trường dev/staging/production. Bao gồm chuẩn bị hạ tầng, build artifact (JAR), cấu hình biến môi trường, chạy ứng dụng dạng service hoặc Docker container, cấu hình reverse proxy (Nginx), database migration và kiểm tra sau triển khai. Mục tiêu đảm bảo ứng dụng chạy ổn định, có thể giám sát và rollback khi cần.

# Hướng dẫn chi tiết

## 1. Yêu cầu hạ tầng

**Server tối thiểu**

- CPU: ≥ 2 core
- RAM: ≥ 4 GB (prod ≥ 8 GB)
- Disk: ≥ 20 GB
- OS: Linux (Ubuntu 20.04+ / CentOS 7+)
- Java: OpenJDK 17 (khuyến nghị LTS)

Cài Java:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-sudo-apt-update-sudo"><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-built_in">sudo</span> apt update<span class="hljs-built_in">sudo</span> apt install openjdk-17-jdk -yjava -version`</div></div>---

## 2. Build ứng dụng

### Build bằng Maven

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-mvn-clean-package--d"><div class="overflow-y-auto p-4" dir="ltr">`mvn clean package -DskipTests`</div></div>Artifact tạo ra:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-target%2Fapp.jar"><div class="overflow-y-auto p-4" dir="ltr">`target/app.jar`</div></div>##  

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk--1"><div class="overflow-y-auto p-4" dir="ltr"></div></div>

# Chương 4: Kiểm thử đơn vị (Unit Test)

Chương này hướng dẫn cách viết unit test cho API trong ứng dụng Java Spring Boot. Nội dung bao gồm cấu trúc test chuẩn, cách mock dependency, kiểm tra validate input, xác thực (authentication/authorization), ghi log, xử lý exception và kiểm tra logic service. Mục tiêu đảm bảo mỗi API hoạt động đúng, an toàn và ổn định trước khi tích hợp hoặc triển khai.

# Nội dung chi tiết

## 1. Nguyên tắc Unit Test API

Mỗi API cần test các nhóm case cơ bản:

1. Validate input
2. Auth / permission
3. Business logic
4. Repository interaction
5. Exception handling
6. Logging
7. HTTP status &amp; response

---

# 2. Ví dụ API mẫu

Controller:

<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("/users")</span><span class="hljs-meta">@RequiredArgsConstructor</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">final</span> UserService userService;    <span class="hljs-meta">@PostMapping</span>    <span class="hljs-keyword">public</span> ResponseEntity<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> CreateUserRequest req) {        <span class="hljs-keyword">return</span> ResponseEntity.ok(userService.create(req));    }}`</div></div>DTO:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40data-public-class-c"><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">@Data</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CreateUserRequest</span> {    <span class="hljs-meta">@NotBlank</span>    <span class="hljs-keyword">private</span> String username;    <span class="hljs-meta">@Email</span>    <span class="hljs-keyword">private</span> String email;}`</div></div>Service:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40service-%40requiredar"><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">@Service</span><span class="hljs-meta">@RequiredArgsConstructor</span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">UserService</span> {    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserRepository repo;    <span class="hljs-keyword">public</span> UserResponse <span class="hljs-title function_">create</span><span class="hljs-params">(CreateUserRequest req)</span> {        <span class="hljs-type">User</span> <span class="hljs-variable">u</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(req.getUsername(), req.getEmail());        repo.save(u);        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(u.getId(), u.getUsername());    }}`</div></div>---

# 3. Cấu trúc test chuẩn

Dependency:

<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.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span></span>>  <span class="hljs-tag"><<span class="hljs-name">artifactId</span></span>>spring-boot-starter-test<span class="hljs-tag"></<span class="hljs-name">artifactId</span></span>>  <span class="hljs-tag"><<span class="hljs-name">scope</span></span>>test<span class="hljs-tag"></<span class="hljs-name">scope</span></span>><span class="hljs-tag"></<span class="hljs-name">dependency</span></span>>`</div></div>Controller test dùng:

- @WebMvcTest
- MockMvc
- @MockBean

---

# 4. Test Validate Input

Case cần có:

- thiếu field bắt buộc
- email sai format
- request null

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40webmvctest%28usercont"><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">@WebMvcTest(UserController.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserControllerValidationTest</span> {    <span class="hljs-meta">@Autowired</span>    <span class="hljs-keyword">private</span> MockMvc mockMvc;    <span class="hljs-meta">@MockBean</span>    <span class="hljs-keyword">private</span> UserService userService;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldFail_whenUsernameBlank</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {        <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""            {"username":"","email":"a@test.com"}        """</span>;        mockMvc.perform(post(<span class="hljs-string">"/users"</span>)                .contentType(MediaType.APPLICATION_JSON)                .content(json))                .andExpect(status().isBadRequest());    }    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldFail_whenEmailInvalid</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {        <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""            {"username":"john","email":"invalid"}        """</span>;        mockMvc.perform(post(<span class="hljs-string">"/users"</span>)                .contentType(MediaType.APPLICATION_JSON)                .content(json))                .andExpect(status().isBadRequest());    }}`</div></div>---

# 5. Test Auth / Permission

Giả sử API cần login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40withmockuser-%40test-"><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">@WithMockUser</span><span class="hljs-meta">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldSuccess_whenAuthenticated</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-keyword">when</span>(userService.create(any()))        .thenReturn(<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(<span class="hljs-number">1L</span>,<span class="hljs-string">"john"</span>));    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isOk());}`</div></div>Test chưa login:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-void-create_sh"><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">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldUnauthorized_whenNoAuth</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isUnauthorized());}`</div></div>---

# 6. Test Business Logic (Service)

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40extendwith%28mockitoe"><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">@ExtendWith(MockitoExtension.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserServiceTest</span> {    <span class="hljs-meta">@Mock</span>    <span class="hljs-keyword">private</span> UserRepository repo;    <span class="hljs-meta">@InjectMocks</span>    <span class="hljs-keyword">private</span> UserService service;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldSaveUser</span><span class="hljs-params">()</span> {        <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();        req.setUsername(<span class="hljs-string">"john"</span>);        req.setEmail(<span class="hljs-string">"a@test.com"</span>);        <span class="hljs-keyword">when</span>(repo.save(any())).thenAnswer(i -> i.getArgument(<span class="hljs-number">0</span>));        <span class="hljs-type">UserResponse</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> service.create(req);        assertEquals(<span class="hljs-string">"john"</span>, res.getUsername());        verify(repo).save(any());    }}`</div></div>---

# 7. Test Exception Handling

Giả sử email trùng:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-when%28repo.save%28any%28%29"><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">when</span>(repo.save(any()))    .thenThrow(<span class="hljs-keyword">new</span> <span class="hljs-title class_">DataIntegrityViolationException</span>(<span class="hljs-string">"dup"</span>));`</div></div>Test:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-void-create_sh-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">@Test</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldThrow_whenDuplicateEmail</span><span class="hljs-params">()</span> {    <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();    req.setUsername(<span class="hljs-string">"john"</span>);    req.setEmail(<span class="hljs-string">"a@test.com"</span>);    <span class="hljs-keyword">when</span>(repo.save(any()))        .thenThrow(<span class="hljs-keyword">new</span> <span class="hljs-title class_">RuntimeException</span>(<span class="hljs-string">"duplicate"</span>));    assertThrows(RuntimeException.class,        () -> service.create(req));}`</div></div>---

# 8. Test Logging

Giả sử service có log:

<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>, req.getUsername());`</div></div>Test log:

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40extendwith%28mockitoe-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">@ExtendWith(MockitoExtension.class)</span><span class="hljs-keyword">class</span> <span class="hljs-title class_">UserServiceLogTest</span> {    <span class="hljs-meta">@Mock</span>    <span class="hljs-keyword">private</span> UserRepository repo;    <span class="hljs-meta">@InjectMocks</span>    <span class="hljs-keyword">private</span> UserService service;    <span class="hljs-meta">@Test</span>    <span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldLog</span><span class="hljs-params">()</span> {        <span class="hljs-type">CreateUserRequest</span> <span class="hljs-variable">req</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateUserRequest</span>();        req.setUsername(<span class="hljs-string">"john"</span>);        req.setEmail(<span class="hljs-string">"a@test.com"</span>);        <span class="hljs-keyword">when</span>(repo.save(any())).thenAnswer(i -> i.getArgument(<span class="hljs-number">0</span>));        service.create(req);        verify(repo).save(any());    }}`</div></div>(Thực tế có thể dùng LogCaptor)

---

# 9. Test HTTP Response

<div class="contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary" id="bkmrk-%40test-%40withmockuser-"><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">@Test</span><span class="hljs-meta">@WithMockUser</span><span class="hljs-keyword">void</span> <span class="hljs-title function_">create_shouldReturnBody</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {    <span class="hljs-keyword">when</span>(userService.create(any()))        .thenReturn(<span class="hljs-keyword">new</span> <span class="hljs-title class_">UserResponse</span>(<span class="hljs-number">1L</span>,<span class="hljs-string">"john"</span>));    <span class="hljs-type">String</span> <span class="hljs-variable">json</span> <span class="hljs-operator">=</span> <span class="hljs-string">"""        {"username":"john","email":"a@test.com"}    """</span>;    mockMvc.perform(post(<span class="hljs-string">"/users"</span>)            .contentType(MediaType.APPLICATION_JSON)            .content(json))            .andExpect(status().isOk())            .andExpect(jsonPath(<span class="hljs-string">"$.username"</span>).value(<span class="hljs-string">"john"</span>));}`</div></div>---

# 10. Danh sách Case chuẩn cho mỗi API

Mỗi API cần tối thiểu:

### Validate

- [ ]  [ ]  thiếu field
- [ ]  [ ]  format sai
- [ ]  [ ]  null body

### Auth

- [ ]  [ ]  chưa login
- [ ]  [ ]  sai role
- [ ]  [ ]  đúng role

### Business

- [ ]  [ ]  flow thành công
- [ ]  [ ]  dữ liệu biên
- [ ]  [ ]  điều kiện đặc biệt

### Repository

- [ ]  [ ]  save OK
- [ ]  [ ]  not found
- [ ]  [ ]  duplicate

### Exception

- [ ]  [ ]  DB lỗi
- [ ]  [ ]  logic lỗi
- [ ]  [ ]  external lỗi

### Response

- [ ]  [ ]  status code
- [ ]  [ ]  body
- [ ]  [ ]  schema

### Log

- [ ]  [ ]  log khi success
- [ ]  [ ]  log khi error

---

# 11. Checklist Unit Test API

- [ ]  [ ]  Test validate
- [ ]  [ ]  Test auth
- [ ]  [ ]  Test service
- [ ]  [ ]  Mock repo
- [ ]  [ ]  Test exception
- [ ]  [ ]  Test response
- [ ]  [ ]  Verify interaction
- [ ]  [ ]  No DB thật
- [ ]  [ ]  No network

# Chương 5: Đóng gói & phát hành (Packaging)

Chương này hướng dẫn cách đóng gói dự án thành artifact phát hành (jar, docker image, bundle web…), đặt version, ghi changelog, ký số (nếu có) và lưu trữ trên repository. Kết quả là gói cài đặt sẵn sàng để triển khai hoặc phân phối.

# Chương 6: Sử dụng AI agent skill

<span>chương này hướng dẫn cách hoạt động và sử dụng AI để thực hiện các tác vụ về tạo mới api </span>

# Nội dung chi tiết

### 1. Cài đặt ban đầu — Clone thư mục .agent

Trước khi sử dụng skill, bạn cần clone repository cấu hình về thư mục gốc của project bằng lệnh sau:

git clone -b master http://192.168.0.95/minhnd/chat-gpt-agent.git .agent

Lệnh trên sẽ tải branch master và đặt toàn bộ nội dung vào thư mục .agent ngay trong thư mục hiện tại của bạn.

Lưu ý: Chạy lệnh này tại thư mục gốc (root) của project, không phải bên trong thư mục con nào khác.

### 2. Hướng dẫn cài đặt Google Antigravity Truy cập link sau https://antigravity.google/download

Sau đó cài đặt như bình thường.

### 3. Mục tiêu của Skill

Skill này được thiết kế để:

• Chuẩn hóa cấu trúc dự án Spring Boot.

• Đảm bảo tính nhất quán (Consistency) giữa các module.

• Tự động hóa việc ra quyết định về kiến trúc (Layered Architecture).

• Tuân thủ các quy tắc bảo mật và lưu vết (Audit log).

### 4. Cách kích hoạt (Activation)

Skill này hoạt động hoàn toàn TỰ ĐỘNG khi project có thư mục cấu hình .agent.

Cơ chế: Khi bạn gửi yêu cầu (ví dụ: "Tạo API User"), hệ thống nhận diện .agent, kích hoạt @backend-specialist và tự động áp dụng các quy chuẩn trong skill api-processing-flow.

Lưu ý: Bạn không cần copy nội dung skill vào chatbot, chỉ cần đảm bảo thư mục .agent nằm trong project.

### 5. Các Quy tắc "Vàng" (Mandatory Rules)

Cần tuân thủ 5 quy tắc bắt buộc sau khi viết code:

1\. Kiến trúc 3 lớp: Luôn tuân theo luồng Controller -&gt; Service -&gt; Repository.

2\. Không Logic ở Controller: Controller chỉ nhận request, check validation cơ bản và gọi Service.

3\. ApiResponse Standard: Mọi API phải trả về object ApiResponse&lt;T&gt;. Không trả về trực tiếp DTO hay Entity.

4\. DTO vs Entity: Tuyệt đối không trả về Entity cho Client. Luôn dùng DTO cho Request và Response.

5\. In-memory Storage: Dùng ConcurrentHashMap hoặc List trong Repository nếu chưa có Database bên ngoài.

5b. Cấu trúc Module chuẩn

Mỗi module mới khi tạo ra phải có cấu trúc thư mục như sau:

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

+-- controller/ (Chua @RestController)

+-- service/ (Chua Business Logic &amp; Interface)

+-- repository/ (Chua truy van du lieu)

+-- entity/ (Chua mapping du lieu)

+-- dto/ (Chua Request/Response models)

### 6. Quy trình phát triển 5 bước (API Workflow)

Để tạo một tính năng mới, hãy đi theo trình tự:

6\. B1: Tạo DTO: Định nghĩa Request và Response model.

7\. B2: Tạo Entity: Định nghĩa cấu trúc dữ liệu mapping.

8\. B3: Tạo Repository: Định nghĩa các phương thức lưu trữ dữ liệu.

9\. B4: Tạo Service: Viết Business Logic (Xử lý dữ liệu, chuyển đổi Entity &lt;-&gt; DTO).

10\. B5: Tạo Controller: Khai báo Endpoint và gọi Service.

### 7. Coding Convention (Quy tắc đặt tên)

Bảng quy ước đặt tên các thành phần trong dự án:

Thành phần Quy tắc đặt tên Ví dụ

Controller &lt;n&gt;Controller UserController

Service &lt;n&gt;Service UserService

Repository &lt;n&gt;Repository UserRepository

Entity &lt;n&gt;Entity UserEntity

DTO Request &lt;n&gt;CreateRequest UserCreateRequest

DTO Response &lt;n&gt;Response UserResponse

### 8. Ví dụ một phương thức Service chuẩn

Dưới đây là cấu trúc chuẩn của một phương thức trong Service layer:

public UserResponse create(UserCreateRequest request) {

// 1. Validate (Kiem tra du lieu)

if (repo.existsByUsername(request.getUsername())) {

throw new BusinessException(ErrorCode.USER\_EXISTS);

}

// 2. Log (Luu vet hanh dong)

log.info("Creating user: {}", request.getUsername());

// 3. Business logic (Xu ly)

UserEntity entity = mapper.toEntity(request);

repo.save(entity);

// 4. Return (Tra ve ket qua)

return mapper.toResponse(entity);

}

### 10. Cơ chế hoạt động

Thư mục .agent (Antigravity Kit) đóng vai trò là "bộ não" và hệ thống điều hành của trợ lý AI trong dự án. Nó chứa các định nghĩa về vai trò, kỹ năng, quy trình làm việc và các kịch bản tự động hóa đảm bảo chất lượng code.

### 11. Cấu trúc thư mục .agent

Cấu trúc chuẩn của thư mục .agent:

.agent/

+-- rules/

| +-- GEMINI.md # Quy tắc tối cao(P0), định nghĩa hành vi cơ bản

+-- agents/ # Danh sách 20 Agent(Personas)

+-- skills/ # Các kỹ năng mo-dun hóa (Domain knowledge)

+-- workflows/ # Quy trình làm việc (/plan, /create, /debug...)

+-- scripts/ # Các kịch bản kiểm tra và tự động hóa master

+-- ARCHITECTURE.md # Bản đồ tổng thể hế thống Agent

+-- tasks/ # Lưu trữ tài liệu và các tác vụ({task-slug}.md)

### 12. Luồng xử lý — Từ yêu cầu đến code

Mỗi yêu cầu gõ vào chạy qua đúng 6 cổng theo thứ tự sau. Không cổng nào bị bỏ qua:

\[USER INPUT\]

|

v

\[1\] Request Classifier &lt;- rules/GEMINI.md doc vao dau tien

| QUESTION --&gt; trả lời thẳng,kết thúc.

| SIMPLE CODE --&gt; chuyển sang bước 2.

| COMPLEX --&gt; tạo tasks/{task-slug}.md rồi mới sang bước 2.

v

\[2\] Agent Routing &lt;- đọc agents/ chọn đúng agent

| ví dụ: viết API --&gt; @backend-specialist

| viết UI --&gt; @frontend-specialist

| kiểm tra sec --&gt; @security-auditor

v

\[3\] Modular Skill Loading &lt;- chỉ đọc skills/ cần thiết, không đọc hết

| ví dụ: @backend + API --&gt; skills/api-patterns + nodejs-best-practices

| mọi folder Skill gồm: SKILL.md (hướng dẫn) + scripts/ (công cụ)

v

\[4\] Rule Priority Check &lt;- áp dụng theo thứ tự P0 &gt; P1 &gt; P2

| P0: rules/GEMINI.md (Toàn cục -cao nhất, không thể override)

| P1: agents/{agent}.md (quy tắc riêng của Specialist)

| P2: skills/{skill}.md (kỹ thuật cụ thể cửa Skill được nạp)

v

\[5\] Socratic Gate &lt;- chỉ áp dụng với COMPLEX CODE

| AI hỏi &gt;= 3 câu trước khi viết bất kỳ dòng code nào:

| - Mục tiêu chính là gì?

| - Edge cases nào cần xử lý?

| - Ràng buộc kiến trúc/công nghệ?

v

\[6\] Implementation &amp; Validation &lt;- viết code + tự động kiểm tra

| Viết code: Clean Code + TDD

| scripts/checklist.py --&gt; Bảo mật, Lint, Schema, UX (nhanh)

| scripts/verify\_all.py --&gt; E2E, Performance, SEO (truoc deploy)

v

\[OUTPUT\] Code đạt chuẩn, đã quan kiểm tra, sẵn sàng review.

### 13. Các lệnh Slash Commands phổ biến

Hệ thống hỗ trợ các quy trình chuẩn hóa thông qua lệnh /. Gõ trực tiếp trong chat để kích hoạt:

/plan — Lập kế hoạch chi tiết, chia nhỏ tác vụ (không viết code).

/create — Bắt đầu tạo một tính năng hoặc ứng dụng mới.

/debug — Kích hoạt chế độ kiểm tra lỗi hệ thống 4 giai đoạn.

/orchestrate — Phối hợp nhiều chuyên gia để giải quyết vấn đề đa chiều.

/status — Kiểm tra tiến độ và trạng thái hiện tại của dự án.

Ghi chú: Mọi thay đổi đối với cấu trúc hệ thống Agent nên được thực hiện thông qua việc cập nhật các file trong .agent để đảm bảo AI luôn cập nhật được “tư duy” mới nhất.