遵循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上 浏览示例代码。