遵循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: "Tutorials on Spring Boot and Java." termsOfService: http://swagger.io/terms/ contact: email: petros.stergioulas94@gmail.com license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1-SNAPSHOT externalDocs: description: Find out more about Reflectoring url: https://reflectoring.io/about/ servers: - url: https://reflectoring.swagger.io/v2
|
openapi字段允许我们定义文档遵循的OpenAPI规范的版本。
在这一info部分中,我们添加了有关API的一些信息。这些字段应该是不言自明的。
最后,在本servers节中,我们提供了实现API的服务器列表。
然后是关于我们的API的一些其他元数据:
tags: - name: user description: Operations about user externalDocs: description: Find out more about our store url: http://swagger.io
|
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: true 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: int64 username: type: string firstName: type: string ... more attributes userStatus: type: integer description: User Status format: int32 securitySchemes: reflectoring_auth: type: oauth2 flows: implicit: authorizationUrl: http://reflectoring.swagger.io/oauth/dialog scopes: write:users: modify users read:users: read users api_key: type: apiKey name: api_key in: header
|
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项目,其中有两个项目:
- app,它是根据我们的规范实现API的应用程序。
- specification,其唯一的工作就是为我们的应用提供API规范。
文件夹结构如下所示:
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>true</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(...) public class User { @JsonProperty("id") private Long id;
@JsonProperty("username") private String username;
@JsonProperty("firstName") private String firstName; // ... more properties
@JsonProperty("userStatus") private Integer userStatus;
// ... getters and setters
}
|
生成器不仅生成模型,还生成端点。让我们快速看一下我们生成的内容:
public interface UserApiDelegate {
default Optional<NativeWebRequest> getRequest() { return Optional.empty(); }
/** * POST /user : Create user * Create user functionality * * @param body Created user object (required) * @return successful operation (status code 200) * @see UserApicreateUser */ default ResponseEntity<Void> createUser(User body) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
} // ... omit deleteUser, getUserByName and updateUser }
|
当然,生成器无法为我们生成我们的业务逻辑,但是它确实会UserApiDelegate为我们实现上述接口。
它还创建一个UserApi接口,将调用委托给UserApiDelegate:
@Validated @Api(value = "user", description = "the user API") public interface UserApi {
default UserApiDelegate getDelegate() { return new UserApiDelegate() {}; }
/** * POST /user : Create user * Create user functionality * * @param body Created user object (required) * @return successful operation (status code 200) */ @ApiOperation(value = "Create user", nickname = "createUser", notes = "Create user functionality", tags={ "user", }) @ApiResponses(value = { @ApiResponse(code = 200, message = "successful operation") }) @RequestMapping(value = "/user", method = RequestMethod.POST) default ResponseEntity<Void> createUser( @ApiParam(value = "Created user object" ,required=true ) @Valid @RequestBody User body) { return getDelegate().createUser(body); } // ... other methods omitted }
|
生成器还为我们创建了一个Spring控制器,用于实现UserApi接口:
@javax.annotation.Generated(...) @Controller @RequestMapping("${openapi.reflectoring.base-path:/v2}") public class UserApiController implements UserApi {
private final UserApiDelegate delegate;
public UserApiController( @Autowired(required = false) UserApiDelegate delegate) { this.delegate = Optional.ofNullable(delegate) .orElse(new UserApiDelegate() {}); }
@Override public UserApiDelegate getDelegate() { return delegate; } }
|
如果Spring UserApiDelegate在应用程序上下文中找到我们的实现,它将注入到控制器的构造函数中。否则,将使用默认实现。
让我们启动应用程序并点击GET端点/v2/user/{username}。curl -I http://localhost:8080/v2/user/Petros HTTP/1.1 501 Content-Length: 0
|
但是为什么我们会收到501响应(未实现)?
因为我们没有实现UserApiDelegate接口,所以UserApiController使用了默认接口,该接口 返回HttpStatus.NOT_IMPLEMENTED。
现在让我们实现UserApiDelegate:@Service public class UserApiDelegateImpl implements UserApiDelegate {
@Override public ResponseEntity<User> getUserByName(String username) { User user = new User(); user.setId(123L); user.setFirstName("Petros"); // ... omit other initialization
return ResponseEntity.ok(user); } }
|
在类中添加@Service或@Component批注很重要,以便Spring可以将其拾取并注入到中UserApiController。
如果现在curl http://localhost:8080/v2/user/Petros再次运行,将收到有效的JSON响应:
{ "id": 123, "firstName": "Petros", // ... omit other properties }
|
我认为,使用Maven插件而不是Swagger Editor生成OpenAPI规范是更好的选择。那是因为我们对我们的选择有更多的控制权。该插件提供了一些配置和使用Git作为版本控制工具,我们可以放心地跟踪在任的任何变化pom.xml和openapi.yml。
可以在GitHub上浏览示例代码。