遵循API优先方法,我们在开始编码之前先指定一个API。通过API描述语言,团队可以进行协作而无需执行任何操作。
使用OpenAPI,我们可以创建一个API规范,我们可以在团队之间共享以交流合同。OpenAPI Maven插件使我们可以根据这样的规范为Spring Boot生成样板代码,因此我们只需要自己实现业务逻辑即可。
这些描述语言指定了端点,安全性模式,对象模式等。而且,大多数时候我们也可以生成这样的规范代码。通常,API规范也成为该API的文档。
您可以在 GitHub上 浏览示例代码。
API优先的好处
要开始进行组件或系统之间的集成,团队需要签订合同。在我们的案例中,合同是API规范。API-first帮助团队之间相互通信,而无需实现任何事情。它还使团队可以并行工作。
API优先方法的亮点在于构建更好的API。仅关注需要提供的功能。简约的API意味着需要维护的代码更少。
使用Swagger编辑器创建API规范
让我们在YAML文档中创建自己的OpenAPI规范。为了更容易理解,我们将讨论分为正在创建的YAML文档的各个部分。如果您想了解有关OpenAPI规范的更多详细信息,可以访问 Github存储库 。
我们从文档顶部的一些有关API的常规信息开始:
openapi: 3.0.2 info: title: Reflectoring description: <font>"Tutorials on Spring Boot and Java."</font><font> termsOfService: http:</font><font><i>//swagger.io/terms/</i></font><font> contact: email: petros.stergioulas94@gmail.com license: name: Apache 2.0 url: http:</font><font><i>//www.apache.org/licenses/LICENSE-2.0.html</i></font><font> version: 0.0.1-SNAPSHOT externalDocs: description: Find out more about Reflectoring url: https:</font><font><i>//reflectoring.io/about/</i></font><font> servers: - url: https:</font><font><i>//reflectoring.swagger.io/v2</i></font><font> </font>
openapi 字段允许我们定义文档遵循的OpenAPI规范的版本。
在这一 info 部分中,我们添加了有关API的一些信息。这些字段应该是不言自明的。
最后,在本 servers 节中,我们提供了实现API的服务器列表。
然后是关于我们的API的一些其他元数据:
tags: - name: user description: Operations about user externalDocs: description: Find out more about our store url: http:<font><i>//swagger.io</i></font><font> </font>
tags 部分提供了其他元数据的字段,我们可以使用这些字段使我们的API更具可读性并易于遵循。我们可以添加多个标签,但是每个标签都应该是唯一的。
接下来,我们将描述一些路径。路径保存有关单个端点及其操作的信息:
paths: /user/{username}: get: tags: - user summary: Get user by user name operationId: getUserByName parameters: - name: username in: path description: 'The name that needs to be fetched. ' required: <b>true</b> schema: type: string responses: 200: description: successful operation content: application/json: schema: $ref: '#/components/schemas/User' 404: description: User not found content: {}
$ref 字段允许我们引用自定义模式中的对象。在这种情况下,我们指的是User架构对象(请参阅下一节有关 Components )。summary是该操作的简短说明。使用 operationId ,我们可以为操作定义唯一的标识符。我们可以将其视为我们的方法名称。最后, responses 对象允许我们定义操作的结果。我们必须为任何操作调用至少定义一个成功的响应代码。
组件
本components节中全部介绍了API的对象。除非我们从组件对象外部的属性中明确引用了它们,否则在组件对象中定义的对象将不会影响API,如上所述:
components: schemas: User: type: object properties: id: type: integer format: <b>int</b>64 username: type: string firstName: type: string ... more attributes userStatus: type: integer description: User Status format: <b>int</b>32 securitySchemes: reflectoring_auth: type: oauth2 flows: implicit: authorizationUrl: http:<font><i>//reflectoring.swagger.io/oauth/dialog</i></font><font> scopes: write:users: modify users read:users: read users api_key: type: apiKey name: api_key in: header </font>
schemas 部分允许我们定义要在API中使用的对象。在本 securitySchemes 节中,我们可以定义操作可以使用的安全性方案。有两种使用安全方案的可能方法。
首先,我们可以使用security字段将安全方案添加到特定操作:
paths: /user/{username}: get: tags: - user summary: Get user by user name security: - api_key: []
在上面的示例中,我们明确指定使用api_key我们上面定义的方案保护路径/ user / {username} 。
但是,如果我们要在整个项目中应用安全性,则只需将其指定为顶级字段即可:
paths: /user/{username}: get: tags: - user summary: Get user by user name security: - api_key: []
现在,我们的所有路径都应通过该api_key方案来保证。
从API规范生成代码
定义了API之后,我们现在将根据 上面 的 YAML文档 创建代码。
我们将研究两种不同的生成代码的方法:
1.从Swagger编辑器生成代码
尽管这是我不会采用的方法,但让我们讨论一下并讨论为什么我认为这是一个坏主意。
让我们转到Swagger Editor,然后将我们的YAML文件粘贴到其中。然后,我们从菜单中选择“ 生成服务器”,然后选择我们要生成哪种服务器(我使用“ Spring”)。
那么,为什么这是个坏主意呢?
首先,为我生成的代码是使用Java 7和Spring Boot 1.5.22,它们都已经过时了。
其次,如果我们对规范进行更改(并且更改始终在发生),我们将不得不复制并粘贴手动更改的文件。
2.使用OpenAPI Maven插件生成代码
更好的替代方法是使用OpenAPI Maven插件从Maven构建中生成代码。
让我们看一下文件夹结构。我选择使用一个多模块Maven项目,其中有两个项目:
文件夹结构如下所示:
spring-boot-openapi ├── app │ └── pom.xml │ └── src │ └── main │ └── java │ └── io.reflectoring │ └── OpenAPIConsumerApp.java ├── specification │ └── pom.xml │ └── src │ └── resources │ └── openapi.yml └── pom.xml
为了简单起见,我们省略了测试文件夹。
我们app是一个简单的Spring启动的项目,我们可以自动生成上 start.spring.io ,让我们着眼于pom.xml从specification模块,在那里我们配置的OpenAPI Maven插件:
<plugin> <groupId>org.openapitools</groupId> <artifactId>openapi-generator-maven-plugin</artifactId> <version>4.2.3</version> <executions> <execution> <goals> <goal>generate</goal> </goals> <configuration> <inputSpec> ${project.basedir}/src/main/resources/openapi.yml </inputSpec> <generatorName>spring</generatorName> <apiPackage>io.reflectoring.api</apiPackage> <modelPackage>io.reflectoring.model</modelPackage> <supportingFilesToGenerate> ApiUtil.java </supportingFilesToGenerate> <configOptions> <delegatePattern><b>true</b></delegatePattern> </configOptions> </configuration> </execution> </executions> </plugin>
您可以 在GitHub上 查看完整pom.xml文件。
在本教程中,我们使用spring生成器。
简单地运行命令./mvnw install将生成实现我们的OpenAPI规范的代码!
查看文件夹target/generated-sources/openapi/src/main/java/io/reflectoring/model,我们找到了User在YAML中定义的模型的代码:
@javax.annotation.Generated(...) <b>public</b> <b>class</b> User { @JsonProperty(<font>"id"</font><font>) <b>private</b> Long id; @JsonProperty(</font><font>"username"</font><font>) <b>private</b> String username; @JsonProperty(</font><font>"firstName"</font><font>) <b>private</b> String firstName; </font><font><i>// ... more properties</i></font><font> @JsonProperty(</font><font>"userStatus"</font><font>) <b>private</b> Integer userStatus; </font><font><i>// ... getters and setters</i></font><font> } </font>
生成器不仅生成模型,还生成端点。让我们快速看一下我们生成的内容:
<b>public</b> <b>interface</b> UserApiDelegate { <b>default</b> Optional<NativeWebRequest> getRequest() { <b>return</b> Optional.empty(); } <font><i>/** * POST /user : Create user * Create user functionality * * @param body Created user object (required) * @return successful operation (status code 200) * @see UserApi#createUser */</i></font><font> <b>default</b> ResponseEntity<Void> createUser(User body) { <b>return</b> <b>new</b> ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } </font><font><i>// ... omit deleteUser, getUserByName and updateUser</i></font><font> } </font>
当然,生成器无法为我们生成我们的业务逻辑,但是它确实会UserApiDelegate为我们实现上述接口。
它还创建一个UserApi接口,将调用委托给UserApiDelegate:
@Validated @Api(value = <font>"user"</font><font>, description = </font><font>"the user API"</font><font>) <b>public</b> <b>interface</b> UserApi { <b>default</b> UserApiDelegate getDelegate() { <b>return</b> <b>new</b> UserApiDelegate() {}; } </font><font><i>/** * POST /user : Create user * Create user functionality * * @param body Created user object (required) * @return successful operation (status code 200) */</i></font><font> @ApiOperation(value = </font><font>"Create user"</font><font>, nickname = </font><font>"createUser"</font><font>, notes = </font><font>"Create user functionality"</font><font>, tags={ </font><font>"user"</font><font>, }) @ApiResponses(value = { @ApiResponse(code = 200, message = </font><font>"successful operation"</font><font>) }) @RequestMapping(value = </font><font>"/user"</font><font>, method = RequestMethod.POST) <b>default</b> ResponseEntity<Void> createUser( @ApiParam(value = </font><font>"Created user object"</font><font> ,required=<b>true</b> ) @Valid @RequestBody User body) { <b>return</b> getDelegate().createUser(body); } </font><font><i>// ... other methods omitted</i></font><font> } </font>
生成器还为我们创建了一个Spring控制器,用于实现UserApi接口:
@javax.annotation.Generated(...) @Controller @RequestMapping(<font>"${openapi.reflectoring.base-path:/v2}"</font><font>) <b>public</b> <b>class</b> UserApiController implements UserApi { <b>private</b> <b>final</b> UserApiDelegate delegate; <b>public</b> UserApiController( @Autowired(required = false) UserApiDelegate delegate) { <b>this</b>.delegate = Optional.ofNullable(delegate) .orElse(<b>new</b> UserApiDelegate() {}); } @Override <b>public</b> UserApiDelegate getDelegate() { <b>return</b> delegate; } } </font>
如果Spring UserApiDelegate在应用程序上下文中找到我们的实现,它将注入到控制器的构造函数中。否则,将使用默认实现。
让我们启动应用程序并点击GET端点/v2/user/{username}。
curl -I http:<font><i>//localhost:8080/v2/user/Petros</i></font><font> HTTP/1.1 501 Content-Length: 0 </font>
但是为什么我们会收到501响应(未实现)?
因为我们没有实现UserApiDelegate接口,所以UserApiController使用了默认接口,该接口 返回HttpStatus.NOT_IMPLEMENTED。
现在让我们实现UserApiDelegate:
@Service <b>public</b> <b>class</b> UserApiDelegateImpl implements UserApiDelegate { @Override <b>public</b> ResponseEntity<User> getUserByName(String username) { User user = <b>new</b> User(); user.setId(123L); user.setFirstName(<font>"Petros"</font><font>); </font><font><i>// ... omit other initialization</i></font><font> <b>return</b> ResponseEntity.ok(user); } } </font>
在类中添加@Service或@Component批注很重要,以便Spring可以将其拾取并注入到中UserApiController。
如果现在curl http://localhost:8080/v2/user/Petros再次运行,将收到有效的JSON响应:
{ <font>"id"</font><font>: 123, </font><font>"firstName"</font><font>: </font><font>"Petros"</font><font>, </font><font><i>// ... omit other properties</i></font><font> } </font>
我认为,使用Maven插件而不是Swagger Editor生成OpenAPI规范是更好的选择。那是因为我们对我们的选择有更多的控制权。该插件提供了一些配置和使用Git作为版本控制工具,我们可以放心地跟踪在任的任何变化pom.xml和openapi.yml。
可以在 GitHub上 浏览示例代码。