OpenAPI và springdoc: Tự Động Sinh Tài Liệu REST API
Tìm hiểu chuẩn OpenAPI 3.1, cơ chế springdoc-openapi tự động quét Controller sinh Swagger UI và kỹ thuật bổ sung metadata với @Operation, @Schema.
TL;DR: OpenAPI Specification 3.1 (OAS) là chuẩn định dạng máy đọc được (JSON/YAML) mô tả toàn bộ hợp đồng tương tác của REST API. Thư viện springdoc-openapi tự động quét các annotation của @RestController lúc khởi động ứng dụng và dựng đối tượng OpenAPI ngay trong bộ nhớ RAM, phục vụ qua /v3/api-docs và giao diện tương tác Swagger UI tại /swagger-ui.html. Cách tiếp cận code-first này biến mã nguồn thành nguồn sự thật duy nhất (Single Source of Truth), đảm bảo tài liệu không bao giờ bị lệch pha so với code thực tế. Để hoàn thiện tài liệu, ta bổ sung ngữ nghĩa nghiệp vụ thông qua @Operation, @Parameter, @Schema và @ApiResponse.
1. Tại sao OpenAPI Specification 3.1 là chuẩn mực mô tả API hiện đại?
Viết tài liệu API thủ công (trên Confluence hoặc Google Docs) luôn gặp vấn đề tồn tại hai nguồn sự thật: mã nguồn và tài liệu. Khi lập trình viên sửa đổi kiểu dữ liệu mà quên cập nhật tài liệu, đội ngũ Frontend hoặc đối tác tích hợp sẽ gọi sai và mất nhiều giờ gỡ lỗi.
OpenAPI Specification 3.1 (trước đây là Swagger) giải quyết triệt để vấn đề này bằng định dạng máy đọc được:
openapi: 3.1.0
info:
title: TaskFlow Order API
version: 1.0.0
paths:
/api/v1/orders/{id}:
get:
summary: Get order details by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Order details
content:
application/json:
schema:
$ref: '#/components/schemas/OrderDto'
'404':
description: Order not found
Tệp đặc tả này đóng vai trò là nguồn dữ liệu đầu vào cho toàn bộ hệ sinh thái công cụ:
- Swagger UI: Tạo trang web tương tác cho phép chạy thử API trực tiếp trên trình duyệt.
- OpenAPI Generator: Tự động sinh mã nguồn client SDK cho hơn 50 ngôn ngữ (TypeScript, Kotlin, Python).
- Postman / Bruno: Tự động nhập spec thành các bộ sưu tập API kiểm thử.
2. springdoc-openapi quét annotation và dựng spec trong bộ nhớ ra sao?
Để tích hợp, bạn chỉ cần thêm một dependency duy nhất vào pom.xml:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
Cơ chế hoạt động bên dưới:
- Khi
ApplicationContextphát ra sự kiệnContextRefreshedEvent(toàn bộ Bean đã nạp xong),springdoc-openapibắt đầu quét các class có gắn@RestController. - Sử dụng Java Reflection để đọc metadata từ các annotation Spring Web và Bean Validation.
- Xây dựng một đối tượng Java
OpenAPIhoàn chỉnh trong bộ nhớ RAM.

Điểm then chốt: Không có file tĩnh nào được lưu trên đĩa. Mỗi lần ứng dụng khởi động lại, đặc tả OpenAPI được tái tạo từ chính mã nguồn đang chạy, đảm bảo độ chính xác tuyệt đối 100%.
3. Những thông tin nào springdoc tự động phát hiện và những gì cần khai báo bổ sung?
80% thông tin được tự động nhận diện:
| Annotation sẵn có trong Spring MVC | Thành phần tương ứng trong OpenAPI |
|---|---|
@RestController + @RequestMapping | Đường dẫn paths trong tài liệu |
@GetMapping, @PostMapping... | HTTP method của endpoint |
@PathVariable Long id | Parameter in: path, required: true, kiểu integer |
@RequestParam(required = false) | Parameter in: query, required: false |
@RequestBody OrderRequest req | Đối tượng requestBody với liên kết schema |
@ResponseStatus(HttpStatus.CREATED) | Mã phản hồi 201 Created |
@NotNull, @Size(min = 2, max = 50) | Thuộc tính schema required, minLength, maxLength |
Return type ResponseEntity<OrderDto> | Cấu trúc dữ liệu JSON của response 200 |
20% thông tin cần bổ sung thủ công:
Hệ thống kiểu dữ liệu Java không thể tự suy luận được:
- Mục đích nghiệp vụ chi tiết của endpoint.
- Ví dụ thực tế (
example) cho từng tham số và trường dữ liệu. - Các mã phản hồi lỗi nghiệp vụ đặc thù (409 Conflict, 422 Unprocessable Entity).
4. Làm thế nào để tùy biến tài liệu với các annotation OpenAPI?
Sử dụng các annotation trong gói io.swagger.v3.oas.annotations:
@Operation: Mô tả phương thức API
@Operation(
summary = "Lay thong tin don hang theo ID",
description = "Tra ve chi tiet don hang kem danh sach san pham. Tra ve 404 neu khong tim thay.",
tags = {"Orders"}
)
@GetMapping("/orders/{id}")
public OrderDto getOrderById(
@Parameter(description = "ID dinh danh don hang", example = "1024")
@PathVariable Long id
) {
return orderService.findById(id);
}
@Schema: Mô tả thuộc tính DTO và dữ liệu mẫu
@Schema(description = "Yeu cau tao moi don hang")
public record OrderRequest(
@Schema(description = "Ten khach hang", example = "Nguyen Van A", minLength = 2)
@NotBlank
String customer,
@Schema(description = "Tong gia tri don hang (VND)", example = "250000", minimum = "1000")
@NotNull @Positive
BigDecimal total,
@Schema(description = "Ngay du kien giao hang (ISO 8601)", example = "2026-07-20")
@NotNull @Future
LocalDate deliveryDate
) {}
Nếu thiếu example, Swagger UI sẽ hiển thị giá trị mặc định vô nghĩa như "customer": "string", "total": 0. Khi có ví dụ rõ ràng, tính năng "Try it out" sẽ tự động điền sẵn dữ liệu mẫu thực tế, giúp lập trình viên Frontend kiểm thử ngay lập tức mà không phải đoán định dạng.
@ApiResponse: Khai báo tường minh các kịch bản phản hồi
@Operation(summary = "Tao moi don hang")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "Tao don hang thanh cong"),
@ApiResponse(responseCode = "400", description = "Du lieu dau vao khong hop le (RFC 9457)"),
@ApiResponse(responseCode = "409", description = "Trung ma don hang trong he thong")
})
@PostMapping("/orders")
@ResponseStatus(HttpStatus.CREATED)
public OrderDto createOrder(@Valid @RequestBody OrderRequest req) {
return orderService.create(req);
}
5. Cấu hình thông tin toàn cục (Global Metadata) qua @Bean OpenAPI như thế nào?
Cấu hình tiêu đề, phiên bản, thông tin liên hệ và danh sách máy chủ:
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("TaskFlow Core API")
.version("v1.0.0")
.description("He thong quan ly du an va quy trinh nghiep vu")
.contact(new Contact().name("OLHub Platform Team").email("[email protected]"))
.license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0")))
.servers(List.of(
new Server().url("http://localhost:8080").description("Moi truong Local"),
new Server().url("https://api-staging.olhub.org").description("Moi truong Staging"),
new Server().url("https://api.olhub.org").description("Moi truong Production")
));
}
}
Giới hạn phạm vi quét trong application.yml
Tránh để lộ các API nội bộ hoặc endpoint Actuator ra ngoài tài liệu:
springdoc:
api-docs:
path: /v3/api-docs
enabled: true
swagger-ui:
path: /swagger-ui.html
operationsSorter: method
tagsSorter: alpha
tryItOutEnabled: true
packages-to-scan: com.olhub.taskflow.api # Chi quet cac controller public
paths-to-match: /api/v1/** # Chi hien thi endpoint bat dau bang /api/v1
show-actuator: false # An toan: An toan bo actuator endpoints
6. Những cạm bẫy kỹ thuật thường gặp khi sử dụng springdoc-openapi là gì?
- Bật Swagger UI trên Production công khai: Để lộ toàn bộ tài liệu và nút bấm "Try it out" có thể tạo điều kiện cho hacker tấn công dò quét. Khuyến nghị tắt trên production thông qua
springdoc.swagger-ui.enabled: falsehoặc bảo vệ bằng Spring Security. - Quên định dạng cho trường Date/Time: Khi không có
@Schema(example = "2026-07-20"), người dùng không biết cần gửi kiểu chuỗi hay timestamp, dẫn đến lỗi 400 không đáng có. - Trùng lặp Tag dẫn đến giao diện lộn xộn: Không phân nhóm
tagskhiến toàn bộ các endpoint dồn chung vào một danh mục theo tên controller class.
7. OpenAPI kết nối với các bài học khác trong hệ thống ra sao?
- RestController & RequestMapping: springdoc dựa trên các annotation định tuyến này để dựng nên cây tài liệu API.
- Jakarta Bean Validation: Tự động chuyển đổi các ràng buộc
@Size,@Pattern,@NotNullthành các thuộc tính kiểm soát tương ứng trên schema JSON. - OpenAPI Nâng Cao: Mở rộng cấu hình xác thực JWT Bearer token và tư duy thiết kế API-first.
8. Những điểm then chốt cần ghi nhớ là gì?
- OpenAPI 3.1 là tiêu chuẩn vàng mô tả hợp đồng REST API, thay thế hoàn toàn tài liệu viết tay.
springdoc-openapitự động hóa việc sinh tài liệu từ mã nguồn bằng Reflection trong pha khởi động.- Mã nguồn là nguồn sự thật duy nhất (Single Source of Truth), loại bỏ nguy cơ tài liệu bị lỗi thời.
- Tận dụng
@Operation,@Parameter,@Schemavà@ApiResponseđể làm giàu ngữ nghĩa và cung cấp ví dụ trực quan. - Giới hạn phạm vi quét qua
packages-to-scanvàpaths-to-matchđể bảo vệ các endpoint nội bộ.
9. Bài tập tự kiểm tra mức độ hiểu sâu
- Q1Tại sao tài liệu API tự động sinh bởi springdoc-openapi lại vượt trội hơn tài liệu viết tay trên Confluence?
- Q2springdoc-openapi thực hiện quét Controller và sinh spec OpenAPI vào thời điểm nào trong vòng đời ứng dụng?
- Q3Annotation nào được sử dụng để cung cấp giá trị dữ liệu mẫu (example) cho một thuộc tính trong DTO trên giao diện Swagger UI?
- Q4Cấu hình nào trong application.yml giúp ngăn chặn các Controller nội bộ (/internal/**) bị hiển thị trên Swagger UI công khai?
- Q5Annotation nào giúp gộp các endpoint từ nhiều Controller khác nhau vào chung một nhóm hiển thị trên Swagger UI?
Bài tiếp theo: Security scheme, groups & API-first
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Validation Nâng Cao: Groups, Custom Constraint và i18n
Bài tiếpOpenAPI Nâng Cao: Security, Grouping và Triết Lý API-First
Lộ trình Junior Java Backend Developer
Chặng 15/21 · REST API với Spring MVC — bài 18/21 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này