随着微服务的盛行和服务粒度的细化,对我服务的 API 接口也越来越多。如果技术管理不到位,技术债的累积会导致服务接口数量爆炸,最后变成业务开发的沉重包袱。据说有的公司,微服务个数不超 300 但 API 接口成功超越5万,这数字估计任何人听到都会头大。
本文集合流行的API文档功能 swagger,国内开源的集中式文档管理系统 YApi 和个人之前的经验分享下微服务文档的管理和控制。
Swagger
Swagger 是一套基于 OpenAPI 规范构建的开源工具,可以帮助我们设计、构建、记录以及使用 Rest API。Swagger 主要包含了以下三个部分:
- Swagger Editor:基于浏览器的编辑器,我们可以使用它编写我们 OpenAPI 规范。
- Swagger UI:它会将我们编写的 OpenAPI 规范呈现为交互式的 API 文档,后文我将使用浏览器来查看并且操作我们的 Rest API。
- Swagger Codegen:它可以通过为 OpenAPI(以前称为 Swagger)规范定义的任何 API 生成服务器存根和客户端 SDK 来简化构建过程。
目前 springfox 已整合了 swagger 开源功能,下面用基于 spring boot 的 bookinfo-service 的例子来带大家体验下 swagger ui 的强大。
maven 依赖
这里只列出 swagger 的依赖包,springboot 的省去,不会的可以在 https://start.spring.io/ 自动生成一个开箱即用的 spring boot 项目。
代码语言:javascript复制<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
swagger 配置
API 文档信息和自动扫描的 basepackage 配置如下:
代码语言:javascript复制@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket buildDocket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(buildApiInf())
.select()
.apis(RequestHandlerSelectors.basePackage("demo.tke.sc.bookinfo.service"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo buildApiInf(){
return new ApiInfoBuilder()
.title("Bookinfo Service APIs")
.description("图书服务的 API 文档,本页面采用 swagger 自动生成,可以查看图书服务对外提供的所有 API 及其对应的方法和参数说明,同时提供了在线测试功能。关于 swagger 的详细功能,请访问其官网:https://swagger.io")
.termsOfServiceUrl("https://cloud.tencent.com")
.contact(new Contact("森林木", "https://cloud.tencent.com", "yuanlinbao@tencent.com"))
.build();
}
}
API 接口文档注解
bookinfo-service 例子中提供了两个 controller,管理用的 AdminController 和用户访问用的 UserController。
- @Api 注解用于标注服务接口类
- @ApiOperation 注解用于标注方法
- @ApiParam 注解用于标注方法参数
@Api(description="Admin APIs",tags={"图书管理接口"})
@RestController
@RequestMapping("/admin")
public class AdminController {
@ApiOperation("添加图书")
@PostMapping(value = "/addBook")
public @ResponseBody
Response<String> addBook(@ApiParam(name="bookInfoDto",value="Json 格式的图书信息",required=true) @RequestBody BookInfoDto bookInfoDto) {
Response<String> resp = new Response<String>();
resp.setCode(200);
resp.setMsg("【添加图书】" bookInfoDto.getTitle() " 成功!");
return resp;
}
@ApiOperation("删除图书")
@DeleteMapping(value = "/removeBook/{isbn}")
public @ResponseBody Response<String> removeteBook(@ApiParam(name="isbn",value="国际标准书号ISBN,长整型",required=true) @PathVariable Long isbn) {
Response<String> resp = new Response<String>();
resp.setCode(200);
resp.setMsg("【删除图书】 ISBN " isbn " 成功!");
return resp;
}
@ApiOperation("修改图书信息")
@PostMapping(value = "/modifyBookInfo")
public @ResponseBody Response<String> modifyBookInfo(@ApiParam(name="bookInfoDto",value="Json 格式的图书信息",required=true) @RequestBody BookInfoDto bookInfoDto) {
Response<String> resp = new Response<String>();
resp.setCode(200);
resp.setMsg("【修改图书】" bookInfoDto.getTitle() " 成功!");
return resp;
}
}
代码语言:javascript复制@Api(tags={"用户接口"}, description = "User APIs")
@RestController
@RequestMapping("/bookinfo")
public class UserController {
@ApiOperation("查询图书信息")
@GetMapping(value = "/{isbn}")
public @ResponseBody
Response<BookInfoDto> getBookInfo(@ApiParam(name="isbn",value="国际标准书号ISBN,长整型",required=true) @PathVariable Long isbn) {
BookInfoDto bookInfo = new BookInfoDto();
bookInfo.setIsbn(isbn);
bookInfo.setTitle("Dubbo Mesh");
bookInfo.setAuthor("jack");
bookInfo.setPrice(100.00f);
return new Response<BookInfoDto>(200, bookInfo);
}
@ApiOperation("图书列表")
@PostMapping(value = "/bookList")
public @ResponseBody Response<List<BookInfoDto>> listBook() {
BookInfoDto bookInfo1 = new BookInfoDto();
bookInfo1.setIsbn(9787111653240L);
bookInfo1.setTitle("云原生:运用容器、函数计算和数据构建下一代应用");
bookInfo1.setAuthor("[美] Boris,Scholl,[美] Trent,Swanson 著,季奔牛 译");
bookInfo1.setPrice(75.1f);
BookInfoDto bookInfo2 = new BookInfoDto();
bookInfo2.setIsbn(9787111644682L);
bookInfo2.setTitle("Istio服务网格技术解析与实践");
bookInfo2.setAuthor("王夕宁");
bookInfo2.setPrice(94.1f);
List<BookInfoDto> bookInfos = new ArrayList<BookInfoDto>(2);
bookInfos.add(bookInfo1);
bookInfos.add(bookInfo2);
return new Response<List<BookInfoDto>>(200, bookInfos);
}
}
POJO 也可以添加文档说明,主要有 @ApiModel 用于标注 pojo class; @ApiModelProperty用于标注字段, 其中 required 可以用来标注一个字段是否必须。
代码语言:javascript复制@Data // 不想写 get set 方法的话,保留此注解同时添加 lomok 依赖
@ApiModel(description="图书信息类" )
public class BookInfoDto {
@ApiModelProperty(value = "国际标准书号 ISBN ", required = true)
private Long isbn;
@ApiModelProperty(value = "书名", required = true)
private String title;
@ApiModelProperty(value = "作者", required = true)
private String author;
@ApiModelProperty(value = "价格", required=false)
private Float price;
}
至此,代码和配置都已完成,本地编译运行后 http://localhost:8080/swagger-ui.html (若设置了 server.port 请将 8080 改成对应的端口号)即可看到 swagger 的文档界面。
整个界面很清爽,所有的 API 一目了然。点击一个具体的接口方法,会展示参数说明等详细信息。Try it out! 按钮可以在发起线测试,同时执行结果也会详细展示。具体见下面的图片说明。
在 Model Schema 下的输入框点击鼠标,会在 Value 下的输入框中填上默认数据,简单修改下,点击 Try it out! 按钮就会发起线上真实调用,并下面展示出服务端的响应,具体见下面的图标说明:
在小步快跑的互联网行业,加上微服务的开发模式,维持一份及时更新且完整的API 文档将会极大的提高我们的工作效率。传统意义上的手工编写文档,很难落地而且也难保证文档的及时性,这种文档久了也就会失去参考意义,反而会加大我们的沟通成本。采用 Swagger 自动化维护 API 文档的方式具有如下优点:
- 文档随代码及时变化。只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,很好的保证了文档的时效性。
- Swagger UI 是一个交互式的 API 文档,我们可以直接在文档页面尝试 API 的调用,省去了准备复杂的调用参数的过程。
YApi
Swagger 虽然很直观,单个服务而言很易用,但服务数量一旦多起来就不够便利。总不能没查看一个服务的接口文档就要找一遍对应url,因而还需要一个集中式的文档管理服务。下面介绍一款国产开源的文档服务工具 YApi。
虽然 YApi 官网 有详细的安装说明,但在具体安装过程中,还是遇到了些坑,下面介绍下在 CVM 上具体安装过程。
安装 YApi
YApi 依赖 nodejs, mongodb 和 git,其中 nodejs 需要 7.6 版本,mongodb 需要 2.6 版本。
- 安装 Node.js
# 执行以下命令,下载 Node.js Linux 64位二进制安装包。
wget https://nodejs.org/dist/v10.16.3/node-v10.16.3-linux-x64.tar.xz
# 执行以下命令,解压安装包。
tar xvf node-v10.16.3-linux-x64.tar.xz
# 依次执行以下命令,创建软链接。
ln -s /root/node-v10.16.3-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v10.16.3-linux-x64/bin/npm /usr/local/bin/npm
# 依次执行以下命令,查看 Node.js 及 npm 版本信息。
node -v
npm -v
# 执行以下命令,安装 git。
yum install -y git
- 手工安装 YApi
mkdir yapi
cd yapi
git clone https://github.com/YMFE/yapi.git vendors
cp vendors/config_example.json ./config.json # 复制完成后请修改相关配置
cd vendors
npm install --production --registry https://registry.npm.taobao.org
# 安装程序会初始化数据库索引和管理员账号,管理员账号名可在 config.json 配置
npm run install-server
# 启动服务
node server/app.js
关于 YApi 的具体配置,可参考 官方文档 。
使用 demo
下面以一个简单的图书管理系统 BMS,来展示下 YApi 的使用。BMS 由4个服务组成:
- BMS-Web:图书管理系统的 web 服务
- user-service:用户管理服务
- store-service:图书信息管理服务
- loan-service:图书借阅服务
BMS 下的服务使用 swagger 生成了文档信息,见下图:
通过 swagger-ui 界面里的文档元数据连接可以看到下面的 API 文档 JSON。
接下来在 YApi 中建好分组和项目,让后将上面的 json 导入系统中。可以将上图中的 json 保存成本地文件,拖入数据管理导入窗口中即可。YApi 还提供了 cli 的数据导入方式,可点击数据导入窗口中的 通过命令行导入接口数据 查看具体操作方式。
数据导入成功后,就可以在接口 tab 页看到所有的接口:
点击具体接口后,可以看到详细的请求和响应参数和说明。
下面是借书接口的请求和响应数据字段的详细说明。还可以在编辑 tab 对接口的信息进行维护。
对于实际业务系统,微服务会很多,可以在 YApi 下通过分组和项目进行分类管理。分组可以对应一个领域或平台,项目可以对应领域或平台下的微服务,比如下图 Demo 中的图书管理系统下面有 4 个微服务,每个微服务又提供了很多对外的 API,那么可以通过项目分组进行管理。通过这种树桩分类管理可以很清晰的进行文档管理和查看,为应用开发之间的对接带来便利。
写在最后
上面介绍的两款工具都是开源的,swagger 和 YApi 可以相互辅助,利用 swagger 生成API文档元数据,再通过 YApi 进行集中管理。如果觉得不够方便,还可以自己打造一个中央文档系统,笔者的前东家唯品会就是这么做的,开发使用一条命令就可以生成API元数据并上传到中央文档系统中,使用更便捷。