Sau khi định nghĩa API specs với RAML, chúng ta có thể sử dụng Spring MVC-RAML Maven plugin để generate API contract. Với API specs sử dụng OpenAPI Specification thì các bạn có thể sử dụng OpenAPI Generator Maven plugin để làm điều này. Cụ thể như thế nào? Chúng ta sẽ cùng nhau tìm hiểu trong bài viết này các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Spring Boot project với Web dependency:
để làm ví dụ.
Kết quả:
Để làm ví dụ, mình sẽ sử dụng API specs được định nghĩa trong bài viết Cơ bản về định nghĩa RESTful Web Service API specs sử dụng OpenAPI Specification. Các bạn có thể lấy nội dung của API specs này ở đây, copy tất cả các tập tin, folder vào thư mục src/main/resources/api của project của chúng ta:
Bây giờ chúng ta sẽ khai báo OpenAPI Generator Maven plugin cơ bản như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>6.6.0</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <inputSpec>${project.basedir}/src/main/resources/api/student.yaml</inputSpec> <generatorName>spring</generatorName> <apiPackage>com.huongdanjava.openapispring.web</apiPackage> <modelPackage>com.huongdanjava.openapispring.dto</modelPackage> <output>.</output> <configOptions> <delegatePattern>true</delegatePattern> <useSpringBoot3>true</useSpringBoot3> </configOptions> </configuration> </execution> </executions> </plugin> |
Ở phần configuration, tag <inputSpec> sẽ trỏ đến location của tập tin API specs.
OpenAPI Generator Maven plugin cho phép chúng ta có thể generate API contract cho nhiều loại project với ngôn ngữ lập trình khác nhau. Các bạn cần khai báo Generator name để chỉ định loại project mà mình muốn generate. Danh sách Generator ở đây. Ở đây, mình đã khai báo để generate Spring project.
Mỗi Generator định nghĩa rất nhiều config option khác nhau, cho phép chúng ta generate project theo ý muốn của mình. Các bạn khai báo các config option này trong tag <configOptions>. Trong ví dụ trên, mình đang sử dụng spring generator và mình đang sử dụng hai configOption của generator này là <delegatePattern> và <useSpringBoot3>. Option <delegatePattern> với giá trị true sẽ sử dụng Delegate Design Pattern trong phần implementation cho API specs, tách biệt giữa interface class và phần implementation. Còn option <useSpringBoot3> là để generate Spring Boot 3 đó các bạn! Còn rất nhiều config option khác, các bạn có thể take a look thêm ở đây nhé.
Tag <apiPackage> dùng để khai báo tên package mà các Controller class được generate sẽ sử dụng, còn <modelPackage> thì liên quan đến package mà các DTO class được generate sẽ sử dụng.
Tag <output> sẽ là folder chứa các tập tin được generate. Ở đây mình cấu hình tag này với giá trị “.” để chọn thư mục hiện hành. Các bạn có thể sử dụng giá trị “.” hoặc ${project.basedir} đều được.
Một điểm các bạn cần lưu ý là khi OpenAPI Generator Maven plugin generate Spring project cho chúng ta, nó sẽ override cả tập tin pom.xml, nên lúc này các bạn hãy backup lại nội dung của tập tin pom.xml trước. Mình sẽ nói các bạn cách cấu hình để OpenAPI Generator Maven plugin skip generate và override tập tin pom.xml sau!
Lúc này, nếu các bạn run project với “mvn clean compile”, refresh project, các bạn sẽ thấy kết quả như sau:
Như các bạn thấy, OpenAPI Generator Maven plugin đã generate cho chúng ta một Spring project.
Kiểm tra tập tin pom.xml, như mình đã nói ở trên, các bạn sẽ thấy nó override luôn cả tập tin pom.xml. Để skip generate và override tập tin pom.xml này, các bạn hãy mở tập tin .openapi-generator-ignore trong thư mục root của project, và thêm dòng “pom.xml” là được. Copy lại nội dung của tập tin pom.xml mà các bạn đã backup, những lần compile sau, tập tin pom.xml này sẽ không bị OpenAPI Generator Maven plugin override nữa!
Chạy lại “mvn clean compile”, kiểm tra console log, các bạn sẽ thấy những tập tin sau được generate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/dto/Response.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/dto/Student.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/web/StudentsApiController.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/web/StudentsApi.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/web/StudentsApiDelegate.java [[1;34mINFO[m] Ignored /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/pom.xml (Ignored by rule in ignore file.) [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/README.md [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/org/openapitools/OpenApiGeneratorApplication.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/test/java/org/openapitools/OpenApiGeneratorApplicationTests.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/org/openapitools/RFC3339DateFormat.java [[1;34mINFO[m] Ignored /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/resources/application.properties (Ignored by rule in ignore file.) [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/org/openapitools/configuration/HomeController.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/resources/openapi.yaml [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/org/openapitools/configuration/SpringDocConfiguration.java [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/src/main/java/com/huongdanjava/openapispring/web/ApiUtil.java [[1;34mINFO[m] Skipped /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/.openapi-generator-ignore (Skipped by supportingFiles options supplied by user.) [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/.openapi-generator/VERSION [[1;34mINFO[m] writing file /Users/khanh/Documents/code/huongdanjava.com/openapi-generator-spring/.openapi-generator/FILES |
Package com.huongdanjava.openapispring.dto sẽ chứa các DTO là các Schema object mà chúng ta định nghĩa trong tập tin YAML.
Package com.huongdanjava.openapispring.web sẽ chứa các Controller class tương ứng với các request URL, được tổ chức theo Delegate Design Pattern. Ngoài ra các bạn còn thấy một class ApiUtil định nghĩa các utility method sử dụng trong các Controller class.
Package org.openapitools định nghĩa một Controller khác, cho phép chúng ta có thể lấy nội dung của tập tin src/main/resources/openapi.yaml được generate bởi OpenAPI Generator Maven plugin. Nội dung của tập tin openapi.yaml này được convert từ nội dung của tập tin YAML của chúng ta, trong ví dụ này của mình là student.yaml.
Tập tin application.properties cũng bị override để thêm một số properties sau:
1 2 3 |
server.port=8081 spring.jackson.date-format=org.openapitools.RFC3339DateFormat spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false |
Các bạn cũng nên add tập tin application.properties này vào tập tin .openapi-generator-ignore “src/main/resources/application.properties” để khỏi bị override nữa!
Một số dependencies cần được thêm vào tập tin pom.xml để project chúng ta có thể compile được, như sau:
1 2 3 4 5 6 7 8 9 10 11 |
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.openapitools</groupId> <artifactId>jackson-databind-nullable</artifactId> <version>0.2.6</version> </dependency> |
Các bạn cũng cần phải sửa code của class OpenapiGeneratorSpringApplication để ứng dụng của chúng ta scan đầy đủ các Controller như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.openapispring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication //// @formatter:off @ComponentScan( basePackages = { "org.openapitools", "com.huongdanjava.openapispring", "org.openapitools.configuration" } ) // @formatter:on public class OpenapiGeneratorSpringApplication { public static void main(String[] args) { SpringApplication.run(OpenapiGeneratorSpringApplication.class, args); } } |
Generated code có chứa 2 class với annotation @SpringBootApplication là OpenAPI2SpringBoot và OpenApiGeneratorApplication ngoài class OpenapiGeneratorSpringApplication ở trên.
Vì chúng ta đang sử dụng class OpenapiGeneratorSpringApplication để chạy Spring Boot application rồi nên mình sẽ comment annotation @SpringBootApplication và annotation @ComponentScan trong 2 class OpenAPI2SpringBoot và OpenApiGeneratorApplication đi. Sau đó thì add 2 class này vào tập tin .openapi-generator-ignore, để chúng khỏi được re-generate nữa các bạn nhé!
Tuỳ nhu cầu thì các bạn hãy cấu hình cho phù hợp nhé!
Bây giờ, nếu các bạn chạy ứng dụng và đi đến http://localhost:8081/, các bạn sẽ thấy kết quả như sau:
Đây chính là Swagger API documentation đó các bạn! Sử dụng nó, các bạn sẽ biết ứng dụng của chúng ta có bao nhiêu request URL được expose. Thông tin của mỗi request URL như thế nào, và chúng ta có thể sử dụng Swagger API documentation này để gọi đến các request URL thực tế luôn.
Hiện tại thì chúng ta chưa implement cho các request URL. Các bạn có thể thêm mới class implement interface StudentsApiDelegate để làm điều này. Ví dụ của mình như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.huongdanjava.openapispring.web.impl; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import com.huongdanjava.openapispring.dto.Student; import com.huongdanjava.openapispring.web.StudentsApiDelegate; @Service public class StudentApiDelegateImpl implements StudentsApiDelegate { @Override public ResponseEntity<Student> getStudentById(String ID) { Student student = new Student(); student.setId(1L); student.setCode("123"); student.setName("HDJ"); return ResponseEntity.ok(student); } } |
Kết quả nếu chúng ta request tới request URL http://localhost:8081/api/students/1 lúc này như sau:
Nếu các bạn generate Spring Webflux project thì hãy thêm một config option là “reactive” nhé:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>6.6.0</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> ... <configOptions> <delegatePattern>true</delegatePattern> <reactive>true</reactive> </configOptions> </configuration> </execution> </executions> </plugin> |
Tất nhiên khi đó, các bạn phải thêm Spring Webflux dependency để project có thể compile được:
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> |