Spring 5 giới thiệu một bước tiến quan trọng với WebFlux và mô hình lập trình hàm (functional programming) thông qua RouterFunction và HandlerFunction. Không chỉ giới hạn ở WebFlux, Spring cũng mang phong cách này vào WebMVC truyền thống, cho phép lập trình viên định nghĩa các endpoint mà không cần sử dụng @Controller hay @RequestMapping truyền thống.
Trong khi các công cụ như Swagger có thể tự động quét các Controller thông thường, việc tạo tài liệu cho các Functional Interface yêu cầu một cách tiếp cận khác. Thư viện springdoc-openapi là giải pháp tiêu chuẩn để giải quyết vấn đề này.
1. Cấu hình Dependency
Để tích hợp OpenAPI 3 vào dự án Spring MVC sử dụng Functional Router, bạn cần thêm các dependency sau vào tệp pom.xml:
<!-- Giao diện UI cho Swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.15</version>
</dependency>
<!-- Hỗ trợ core cho Spring MVC -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webmvc-core</artifactId>
<version>1.6.15</version>
</dependency>
2. Sử dụng @RouterOperation để mô tả API
Trong mô hình Functional, chúng ta không có các phương pháp được đánh dấu @GetMapping để Swagger quét thông tin. Thay vào đó, chúng ta sử dụng annotation @RouterOperation (hoặc @RouterOperations cho nhiều route) trực tiếp trên Bean định nghĩa Router.
Ví dụ về cách cấu hình cho một tập hợp các route quản lý sản phẩm:
@Configuration
public class ProductRouterConfig {
@Bean
@RouterOperations({
@RouterOperation(
path = "/v1/products/{id}",
beanClass = ProductHandler.class,
beanMethod = "getProductById",
method = RequestMethod.GET,
operation = @Operation(
operationId = "getProductById",
summary = "Lấy thông tin sản phẩm theo ID",
parameters = {
@Parameter(name = "id", in = ParameterIn.PATH, description = "Mã sản phẩm", required = true)
}
)
),
@RouterOperation(
path = "/v1/products",
beanClass = ProductHandler.class,
beanMethod = "listAllProducts",
method = RequestMethod.GET
)
})
public RouterFunction<ServerResponse> productRoutes(ProductHandler handler) {
return RouterFunctions.nest(path("/v1/products"),
RouterFunctions.route(GET("/{id}"), handler::getProductById)
.andRoute(GET(""), handler::listAllProducts)
);
}
}
Lưu ý quan trọng: Để Swagger hiển thị đúng, bạn cần cấu hình quét package trong tệp application.properties. Package được quét phải bao gồm cả nơi định nghĩa RouterFunction và HandlerFunction.
springdoc.packagesToScan=com.example.api.handler,com.example.api.router
springdoc.pathsToMatch=/v1/**
3. Khai báo Request Body cho phương thức POST
Đối với các request có chứa dữ liệu gửi lên (như POST hoặc PUT), chúng ta cần mô tả Schema của Request Body để Swagger UI có thể tạo mẫu dữ liệu chuẩn.
Định nghĩa route cho việc tạo mới sản phẩm:
@RouterOperation(
path = "/v1/products",
beanClass = ProductHandler.class,
beanMethod = "createNewProduct",
method = RequestMethod.POST,
operation = @Operation(
operationId = "createNewProduct",
requestBody = @RequestBody(
required = true,
content = @Content(schema = @Schema(implementation = ProductDTO.class))
)
)
)
Trong lớp xử lý (Handler), bạn thực hiện lấy dữ liệu từ request như sau:
public ServerResponse createNewProduct(ServerRequest request) {
try {
ProductDTO productDto = request.body(ProductDTO.class);
// Logic xử lý nghiệp vụ
return ServerResponse.ok().body("Product created successfully");
} catch (Exception e) {
return ServerResponse.status(HttpStatus.BAD_REQUEST).build();
}
}
4. Tùy chỉnh thông tin OpenAPI Metadata
Bạn có thể cấu hình thông tin chung của tài liệu API (như Title, Version, License) bằng cách khai báo một Bean OpenAPI:
@Configuration
public class SwaggerOpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Hệ thống Quản lý Sản phẩm API")
.version("1.0.0")
.description("Tài liệu hướng dẫn sử dụng Functional API trong Spring 5")
.termsOfService("http://example.com/terms")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
}
Việc chuyển đổi từ Swagger 2 (Springfox) sang Springdoc-openapi đòi hỏi thay đổi các annotation từ thư viện io.swagger.v3.oas.annotations. Mặc dù việc cấu hình cho Functional WebMVC có phần chi tiết hơn so với Controller truyền thống, nhưng nó mang lại khả năng kiểm soát chặt chẽ và tách biệt rõ ràng giữa logic định tuyến và logic xử lý nghiệp vụ.