团队内部RestAPI开发采用设计驱动开发的模式,即使用API设计文档解耦前端和后端的开发过程,双方只在联调与测试时耦合。在实际开发和与前端合作的过程中,受限于众多因素的影响,开发效率还有进一步提高的空间。本文的目的是优化工具链支持,减少一部分重复和枯燥的劳动。
现状梳理
前后端工作流
- 需求理解:前后端先理解产品思路、需求的详细内容
- 敲定接口:后端出API设计文档初稿,与前端面对面或者在线讨论修正,接着后端(有时是前端)把API描述记录到公司内部的API文档库(在线markdown编辑器,提供分级目录的存储功能,对如何描述API没有一定的标准,因此描述格式不统一,因人而异1)。接着根据双方工作的安排,约定联调时间
- 独立开发:双方独立开发(也有可能非完全独立开发,如需要对方的环境配合等;或者存在返工,如API设计发生变更等)
- 系统联调:测试API基本功能和双方系统的连通性
- 测试回归:开发或者QA编写测试用例并测试业务流程
可优化方向
1. 减少文档编写时间
根据个人的开发经验,后端编写API设计文档时常见的情况有:如果是简单的需求,API数量较少,后端直接通过内部即时通信软件和前端沟通;如果是复杂的需求,API数量较多,后端会先把API描述写到本地临时文档(纯文本、markdown、evernote等)或者内网(内部个人Wiki、git仓库)中,然后把链接发给前端review或者直接面对面沟通。这样的方式灵活,但存在一些问题,比如:
-
描述格式没有标准。对于简单的描述,文档格式比较随意,双方基于约定和经验理解和开发1;完备的描述,编写文档所需时间较长,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。当然在合作开发中,文档越完备,双方的理解偏差就越少、开发产生的bug就越少,后期也更容易维护代码、适应人员变更,但是编写完备的文档所需要的额外时间也不容忽视,没有代码产出的设计文档可能不得已让位于现实中整体开发时间的紧张。
以开发“获得管理员账户下可用商户”为例。如果是简单的描述,后端告知前端url为
{host}/ajax/shop
,返回的结构是[{"shopId":int,"shopName":string}]
,有经验的前端会自动判断出Method
为Get
,Content-type
为application/json
,request不需要附带参数,不需要对错误值做特殊处理;而如果是复杂的描述,后端一般会列出 API名称、功能描述、调用方式、请求参数、请求示例、返回值、成功的返回结果示例、失败的返回结果示例中的几项,填充到已有的API模板中 2。 -
输入效率不高。由于开发的API模板缺乏固定的标准,因此只能在例如Wiki、纯文本编辑器、markdown编辑器中编写,无法得到现代IDE中语法高亮、自动补全、错误提示等特性的支持,整体感觉就像是在记事本中写Java。
- 设计文档中会规定API输出的数据结构(一般为json数组或者json对象),如果数据结构较为复杂(比如包含有几十个字段的POJO),要在设计文档中书写可读性良好的数据结构需要更多的时间;如果数据结构中字段缺失或者可读性差,则会影响前端的文档理解和代码开发。
- 如果后端能提供样例数据自然是最好的,因为后端最熟悉业务逻辑,产生的样例数据比前端自己Mock的数据更好。但是复杂数据结构的样例数据的编写同样很花时间。
> 举例:需求要求开发一个新增优惠券API,其样例数据只能由开发手动生成。如果是修改已有的API,要补充新的样例数据,开发一般会登录商户平台,打开优惠券页面,在Chrome中实际操作一遍,抓包得到request的body(json格式),在json格式化网站(如[json.cn](json.cn))美化后复制到API设计文档中。![clipboard.png](/img/bVTWta)
-
重复录入。因为文档库功能羸弱,使用不便,所以开发一般先按自己的格式写一份文档,但是如果不直接把API录入到公司文档库,则开发需要对一份API出两份设计文档。开发一般会开两个窗口,左边是API设计文档的完成件,右边是公司API文档库编辑页面,然后把左边格式各异的API描述文本转换到右边统一的markdown格式。
例如:想象一下从Wiki文档的表格中一个个复制粘贴,再编辑成markdown格式文本是典型的成本大于收益的工作。
-
文档维护成本大。由于文档和代码分开存放,由于需要手动操作,因此文档与代码同步成本较高。随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象,并在业务整体交接、开发成员替换时使后来人付出较大的时间成本。
不同的存放形式的优缺点见仁见智,类似于Spring也有XML和JavaConfig两种配置方式。
2. 减少联调时间
缺少样例数据。由于团队内部前端一般不会全面的了解业务,后端提供的样例数据往往比前端自己生成的Mock数据对业务需求的把握更准确。如果后端能在API设计文档中提供样例数据,一是如果前端没有自动Mock工具的话,能节约前端生成Mock数据的时间;二是能在联调前为前端提前发现一些低级错误(比如具有业务特征的一些默认值处理、空值处理、字段缺失等场景)。
3. 减少部署时间
beta环境绑定了唯一的beta域名,因此在多分支并行开发时是稀缺资源,较大的项目在beta环境编译和部署往往消耗很多等待和解决冲突的时间。如果在联调中发现的问题较多,就需要多次部署beta环境,时间成本十分可观。
如何减少部署时间另外行文
寻找技术候选
总结起来,上面列出的问题大部分是由于API描述标准不统一引起的,因此要用标准化的工厂代替散乱的手工生产。虽然平时开发的API具有Rest风格、对外网开放,只被企业自己的应用调用,不过普遍的WebAPI开发流程还是适用的。我在网上搜索一些功能较为符合的RestAPI设计工具,将其大致分为3类讨论。
第一类:Swagger、Apiary、RAML
人和机器可读的API描述标准,围绕该语言有完善的工具链:一般有设计、编译(即Codegen)、测试(有MockServer、自动Mock、本地直连等形式)、文档(包括静态文档,如html和pdf;还有可交互文档html+js)、合作(多人+多角色合作开发)这几个模块,各个标准都差不多。
较为学术性的表述:虽然Web API的实现正变得越来越普及,但在工具方面还缺乏一些被广泛接受的标准,用以描述、发现,并且理解大量基于API的服务的意义。Web API之“元语言”有三个关键领域:API描述、API发现以及API档案。所谓的API描述,指的是以一种让人类与机器都可读的形式对API进行描述,包括API的实现细节,例如资源与URL、表述格式(HTML、XML、JSON等等)、状态码以及输入参数。
Swagger、Apiary、RAML的格式各自采取了一种略有不同的设计方式,但在本质上都提供了相同的基本特性:以多种不同级别的细节对Web API进行描述。
以Swagger23为例,分为5个部分(示例图来自于RAML,不过功能都差不多)。
- Design:其标准为
OpenAPI
(前身是Swagger API Spec
),提供强大的在线编辑功能,包括语法高亮、错误提示、自动完成、实时预览4,并且支持用户以Json、Yaml格式撰写5、导入、导出、转换文档。 - Build:设计文档可以编译成客户端和服务端,支持的语言包括Java、NodeJS、C++等主流语言。其中Java服务器端使用流行的
Spring Boot
构建,生成的代码包括定义的API接口、空实现方法的样板代码、业务POJO、配套的Swagger注解。值得注意的是,由自动生成的Swagger注解,可以反向生成最初的API设计文档 - Test:可在本地服务器运行时使用本地测试功能;用户也可以使用
SwaggerHub
中提供收费的在线测试功能,主要有MockServer(Auto Mocking
)、问题跟踪(Issue Tracking
) - Document:可以在线或离线(包括代码编译时和运行时)地生成静态html、pdf等文档;
SwaggerHub
可以配合API版本,自动同步相应文档的版本 - Share:
SwaggerHub
提供团队管理、联调开发、文档标注等多人合作开发的支持
再提一下Apiary和RAML。Apiary6使用API Blueprint
标准,Apiary网站提供了在线编辑、实时预览、Mock、可交互文档、团队合作、Github同步、流量追踪等包含整个API生命周期的所有服务,当然这是收费产品,而且价格不菲;另外,用户也可以通过开源的命令行工具进行离线的API设计、文档生成、发布过程,并将其集成到自己的工作流中,这也是它的一大特点。RAML使用RAML1.0
标准,没有自己的可视化在线开发平台,而是用官方或第三方的离线工具(如API Workbench
系列)来代替,因此它也存在一些缺点,比如:工具更新不及时,某些Tool不支持最新的RAML1.0
。
第二类:Apidocjs
类似于Intellij Idea的生成JavaDoc
功能,是一种注释解析器,从C++、Java、Python代码注释中基于特定的关键字(如@param
、@return
)生成API静态文档。由于更像是先代码实现后生成API文档,所以不能算作是设计驱动的开发;另外apidocjs也缺乏IDE支持。
第三类:Rap、eolinker
没有公开的API设计语言,提供在线或离线、闭源或开源的可视化、一体化API开发平台。这里选择中文的Rap、eolinker作为代表。Rap是阿里的开源作品,也提供线上服务,核心功能是文档编辑和自动Mock服务。eolinker是综合的接口管理平台,除了常见的功能,还提供接口商店、数据字典等适合创业团队快速开发API的特性。在此不做进一步介绍。
如何选型?
选型逻辑
- 社区活跃、功能完善,应用成熟。
- 学习成本低、上手时间短。作为业务开发,缺少时间熟悉学习曲线陡峭的知识和工具。
- 功能较多地契合上述优化方向。
- 能补充现有工作流的不足,不做大范围的代替。
- 要考虑测试环境处于内网造成的障碍。
初步分析
- rap、eolinker、swaggerHub、apiary提供了一整套API开发环境,取代了现有工作流。放弃。
- apidocjs缺乏现代IDE特性支持,输入效率较低。放弃。
进一步分析
Swagger2 | API Blueprint | RAML | |
---|---|---|---|
Design | 在线编辑、IntelliJ Idea插件 | 在线编辑、命令行、Sublime/Atom/Vim插件 | API Workbench、Sublime/VS插件 |
Design文档格式 | yaml、json | markdown | yaml |
Build支持 | 在线Build、IntelliJ Idea插件 | / | Maven插件 |
Codegen服务端框架 | Spring Boot | / | JAX—RS |
Test | 运行时手动Mock、第三方工具 | 官方和第三方工具生成MockServer/Client | 第三方工具和在线服务 |
Document | Maven插件生成静态文档、在线或运行时生成可交互文档,支持SpringMVC+注解形式 | 第三方工具 | 第三方工具 |
Share | 在线、收费 | 在线、收费 | 离线、第三方工具 |
综合考虑,最后选择Swagger2。因为Swagger对现有的工作流侵入较少;工具较为完整;与团队使用的Spring MVC
技术栈无缝集成,可以减轻文档工作量。Swagger2也有一些缺点,如:使用注解方式对代码有侵入性。
用Swagger2优化现有工作流
-
减少文档的编写时间
- 如果后端先编写独立的API设计文档,可利用Swagger在线编辑器或IDE插件的自动完成等特性;yaml格式统一、简单易懂、表达能力强,较markdown冗余字符更少。通过模仿官方Example很容易学习
OpenAPI
规定的关键字。 - 另外后端也可以把API设计文档直接通过注解的形式,标注在
Controller
类和相关方法上(以Spring MVC
和Spring Boot
为例),即可以通过Java反射在Maven Complie
或运行时生成API设计文档。Swagger有Intellij Idea
的插件支持,Swagger注解则能利用现代Java IDE的特性,提高输入效率;另外完善的注解也方便其他开发人员进行后期维护,不需要在设计文档和代码实现中来回切换查看。此种方式相当于面向规约的开发模式,即先规定接口,再填充实现。
- 如果后端先编写独立的API设计文档,可利用Swagger在线编辑器或IDE插件的自动完成等特性;yaml格式统一、简单易懂、表达能力强,较markdown冗余字符更少。通过模仿官方Example很容易学习
- 减少文档的转换时间:利用第三方工具实现从Swagger、API Blueprint、RAML格式的互相转换,或者直接输出为html静态文档,方便整合到现在的工作流中。比如:
API Blueprint
的markdown格式可以存储到公司的API文档库,html静态文档可以存储到内部Wiki。 - 减少(可能的)开发时间:如果已有独立的API设计文档,在Swagger Editor中生成基于
Maven + Spring Boot
的服务端代码,不过生成的POJO和Controller类的命名可能不太理想,需要自己调整。 - 减少联调时间:后端可以在设计文档或注解中指定API或者POJO的Example数据,节约前端手动编写Mock数据的时间。
附录1:流程实例演示(脚手架为Spring MVC
)
1. 标注相应的Swagger注解作为API设计文档
先建立RestController类、相应的API空方法、POJO作为骨架。对应的API设计文档见文末的Reference
节。
@Api("Users")
@RestController
@RequestMapping(value = "/users")
public class UserController {
@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
@PostMapping
public String postUser(@RequestBody User user) {
return null;
}
@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return null;
}
}
class User{
private Long id;
private String name;
private String age;
//getter,setter
}
2. 生成API设计文档
生成的具体方式按照耗时长短排列为:Maven Complie
、Test Case、Server Runtime。可在Swagger Editor中预览相应的可交互文档。根据前端的反馈,修改Swagger注解,并把新的文档存储到内部Wiki或者API文档库(如果改动量大的话,利用Diff工具提高效率)。
3. 在Swagger-UI提供的可视化页面中完成自测
开发完成后启动Server,Swagger-UI的访问地址为http://localhost:8080/swagger-ui.html
4. 与前端联调
为了减少beta环境的冲突、加快部署速度,最好在本地开发环境联调。
附录2:Swagger配置与使用
【5分钟指南】Swagger2环境配置与使用
附录3:YAML格式的API描述文档示例
swagger: '2.0'
info:
description: Click Link Below for Help
version: v1
title: demo13
termsOfService: 'http://www.github.com/kongchen/swagger-maven-plugin'
host: HOST
basePath: /s
tags:
- name: Users
schemes:
- http
paths:
/users:
post:
tags:
- Users
summary: 创建用户
description: 根据User对象创建用户
operationId: postUser
parameters:
- in: body
name: body
required: false
schema:
$ref: '#/definitions/User'
responses:
'200':
description: successful operation
schema:
type: string
'/users/{id}':
get:
tags:
- Users
summary: 获取用户详细信息
description: 根据url的id来获取用户详细信息
operationId: getUser
parameters:
- name: 'id'
in: path
required: true
type: integer
format: int64
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/User'
definitions:
User:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
age:
type: string
Reference
- Swagger:Rest API的描述语言
- RAML vs. Swagger vs. API Blueprint
- Springfox Reference Documentation
- Swagger使用
- swagger-maven-plugin
- 通过Swagger进行API设计,与Tony Tam的一次对话
- API 设计: RAML、Swagger、Blueprint三者的比较
- API描述、发现与档案入门
- Spring Boot中使用Swagger2构建强大的RESTful API文档
- API Design And Documentation
- Swagger与其他API文档编写工具对比