Vert.x后端漫游指南(4)

前言

非常非常久没有更新 Vert.x后端漫游指南 了。主要是因为之前觉得Vert.x的Web后端实在是没有什么好写的,都是一些常规操作。再者,也有很长一段时间没有碰Vert.x的后端开发了。但是呢,因为打算做一个Vert.x全栈的服务端应用,所以又把Web后端的部分拿出来了。

API?

虽然Vertx-Web也支持模板引擎这类的东西,但是无论是从潮流还是从Vertx支持程度的角度来看,前后端分离的开发方式更加适合现在的Web开发。那么既然前后端分离,提供一套完整、合理的API就非常重要了。
回顾一下之前的 Vert.x后端漫游指南 ,我们使用了Router.route("/api/path")的方式来设置API。这种方式非常的简单粗暴,没有注解,没有配置文件,完完全全的硬编码。对于单人开发的简单服务来说,这样当然是没有问题的。但是当有了开发团队,需要前后端沟通的时候,这种硬编码的方式就变得很麻烦了。
一方面这意味着还得再单独写一份API文档给前端,

程序员最讨厌两种事情,一是没有文档,二是写文档。

另一方面是一旦要做什么修改,就必须要重新编译程序。

Swagger & OpenAPI

Swagger https://swagger.io/

OpenApi https://www.openapis.org/

OpenAPI和Swagger给API开发带来了不一样的体验。Swagger是一套非常完善的API开发工具,最新的版本已经提供了对OpenAPI 3.0 的支持。

yaml

Swagger使用yaml作为配置文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
openapi: 3.0.0
info:
title: Sample API
description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
version: 0.1.9
servers:
- url: /v1
description: Optional server description, e.g. Main (production) server
- url: http://staging-api.example.com
description: Optional server description, e.g. Internal staging server for testing
paths:
/api/transactions/{id}:
get:
operationId: getTransactionsList
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: from
in: query
required: false
schema:
type: string
- name: to
in: query
required: false
schema:
type: string
responses:
200:
content:
application/json:
schema:
type: object
put:
operationId: putTransaction
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
from:
type: string
format: email
to:
type: string
format: email
value:
type: number
format: double
additionalProperties: false
required:
- from
- to
- value
responses: ...

大致上就是这个样子,详细的关于swagger中yaml配置的编写可以看官方文档

https://swagger.io/docs/specification/basic-structure/

总体来说就是把原来的硬编码改成了文件配置的模式。通过pathsget,put,post,delete一起组成各种API,同时也通过parametersrequestBody规定了各个API所需的参数,以及最后的responses用于规定服务端返回的数据结构。

Swagger-Codegen

有了配置文件之后当然还是要能够生成对应的代码才行。Swagger提供了Swagger-Codegen来生成各种语言、框架的服务端和客户端代码。

https://github.com/swagger-api/swagger-codegen

如果要使用OpenAPI 3.0的话就必须要安装3.0.0版本以上的Swagger-Codegen。目前由于这个工具还在频繁的更新中,所以bug也是挺多的,特别是在客户端代码的部分,前几天就遇到生成的typescript-angular的代码里没有把body放到postput的参数里的问题。
如果开发的时候遇到了不可理喻的问题,就去issues里看看吧。

Vert.x的Web API Contract

在写完Swagger之后呢,就要来看看Vert.x的Web API Contract了。为什么明明有Swagger了,Vert.x的团队还要搞个Contract呢,因为Swagger不支持Vert.x( 笑 。Swagger虽然已经支持很多主流框架了,但是很明显Vert.x还不是那么的”主流”。

官方文档 https://vertx.io/docs/vertx-web-api-contract/java/

构建

Maven

1
2
3
4
5
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-api-contract</artifactId>
<version>3.6.2</version>
</dependency>

Gradle

1
2
3
dependencies {
compile 'io.vertx:vertx-web-api-contract:3.6.2'
}

创建一个工厂

1
2
3
4
5
6
7
8
OpenAPI3RouterFactory.create(vertx, "yaml的路径,支持本地文件或是http、https的地址", ar -> {
if (ar.succeeded()) {
//得到了一个路由工厂
OpenAPI3RouterFactory routerFactory = ar.result();
} else {
Throwable exception = ar.cause();
}
});

得到这样一个工厂之后,就可以添加各种处理器

1
2
3
4
5
6
7
8
9
routerFactory.addHandlerByOperationId("awesomeOperation", routingContext -> {
RequestParameters params = routingContext.get("parsedParameters");
RequestParameter body = params.body();
JsonObject jsonBody = body.getJsonObject();
// Do something with body
});
routerFactory.addFailureHandlerByOperationId("awesomeOperation", routingContext -> {
// Handle failure
});

这里的OperationId就对应了yaml文件中的OperationId,对于需要做权限控制的路由,还可以添加Security Handler

1
routerFactory.addSecurityHandler("security_scheme_name", securityHandler);

对于之前我们用到的SessionHandler、CookieHandler,可以通过调用addGlobalHandler的方式来添加。需要注意的是BodyHandler不能用addGlobalHandlerOpenAPI3RouterFactory为它提供了专门的方法。

在添加完处理器之后,就可以生产路由了

1
2
3
4
Router router = routerFactory.getRouter();

HttpServer server = vertx.createHttpServer(new HttpServerOptions().setPort(8080).setHost("localhost"));
server.requestHandler(router).listen();

这部分跟之前相差无几,唯一要注意的是router::accept已经被弃用了,现在直接向requestHandler传递router就可以了。

Vert.x的Web API Service

Contract之后再来个Service是要干什么。

各位肯定也都发现了,Web API Contract用起来是一点都不方便,先不说每个处理器都要通过operationId单独绑定,就说处理器得到的参数吧,一点都不智能,完全没有用到Yaml中配置的parameters,参数还是要像以前一样从RoutingContext里获取。
为了获得更好的开发体验,就必须要引入Web API Service了。

官方文档 https://vertx.io/docs/vertx-web-api-service/java/

构建

Maven

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-codegen</artifactId>
<version>3.6.2</version>
<classifier>processor</classifier>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-api-service</artifactId>
<version>3.6.2</version>
</dependency>

Gradle

1
2
3
4
dependencies {
compile 'io.vertx:vertx-codegen:3.6.2:processor'
compile 'io.vertx:vertx-web-api-service:3.6.2'
}

可以看到,除了vertx-web-api-service以外,还引入了vertx-codegen。在codegen面前,没有什么问题是解决不了的。

使用Interface

1
2
3
4
5
@WebApiServiceGen
interface TransactionService {
void getTransactionsList(String from, String to, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
void putTransaction(JsonObject body, OperationRequest context, Handler<AsyncResult<OperationResponse>> resultHandler);
}

Web API Service提供了另一种绑定handler的方式。接口中的方法名称需要和operationId相同,其中的参数也需要和parameters相同,需要注意的是如果使用requestBody,则对应JsonObject body。最后的OperationRequest contextHandler<AsyncResult<OperationResponse>> resultHandler是必须写的部分。分别代表了请求的额外信息和响应的对象。

  • OperationRequest
    • getHeaders 请求头信息
    • getParams 请求参数
    • getUser 如果使用Auth,User就在这里
    • getExtra 可以通过向RouterFactory添加setExtraOperationContextPayloadMapper额外设置extra,这个很重要,通过这种方式可以把session带到handler里来。
  • OperationResponses
    • 响应头
    • 响应状态
    • 响应主体

添加Service到RouterFactory

1
2
3
4
5
6
7
8
9
10
11
12
OpenAPI3RouterFactory.create(vertx,"yaml文件路径"){
val serviceImpl = ServiceImpl()
ServiceBinder(vertx)
.setAddress("my-service")
.register(Service::class.java,ServiceImpl)
it.result().mountServiceInterface(Service::class.java,"my-service")
//因为Web API Contract并不会处理yaml中的servers,所以需要自己设置sub-router
val router = Router.router(vertx).mountSubRouter("/api",it.result().router)
vertx.createHttpServer().requestHandler(router).listen(80){
logger.info("Listen at 80")
}
}

这样就可以非常方便的使用Vert.x的Web API Service了。

小结

在使用了OpenAPI之后,Vert.x的后端开发就算是比较完整了。这种开发方式也能给前端开发提供很多便利,比硬编码的模式要更加适合现在的开发形式。