在上一篇文章中,我们讲解了什么是 api,什么是 sdk:
https://www.cnblogs.com/tanshaoshenghao/p/16217608.html
今天将来到我们万丈高楼平地起系列文章的第二篇:如何编写 api 文档?
咳咳,其实写 api 文档这个事情也没有一个统一的标准,写这篇文章更多地是分享与记录自己的一些心得体会。
曾经有大佬和我说,通过一个人的 api 设计大概率能看出他的工程水平,并且推荐我去看一些优秀的 api 设计,比如 aws 的 api。
后来我感觉,学 api 有点像研习四书五经,入门后谁都能吟两句,至于能否真正消化理解及能发挥多大价值,不同人有不同的造化。
所以,或许写 api 就像比武功吧,一招一式耍出来后,花架子或许能唬住大众,高手之间的交流则只可意会不可言传......
呃不过阿菌可能连花架子也算不上,所以就不在大家面前卖弄如何设计 api 了。
但关于怎么写 api 文档这样的业务小常识,感觉还是可以分享一下的!
让人又爱又恨的 api 文档首先我想说的是,脱离现实谈理想都是耍流氓,据我自己的切身体会,这世界上大概率是没有人喜欢写 api 文档的。
毕竟工作本来就累,这代码一写完,测试不找碴已经是万幸了,要顺便改上几个 bug,谁还记得维护 api 文档嘛。
但是咧,一旦我们需要去维护别人留下的代码,又或者需要翻看自己几个月前堆的屎山,立马就会哭爹喊娘:这兔崽子为啥不留 api 文档...... T_T
所以,为了方便自己,同时也能方便他人,我们还是要把编写 api 文档这件事情放在心上。
放在心上则意味着放在首位,也就是说,当我们开始一项开发任务的时候,首先要把 api 定义好,并且把 api 文档完善细化好,让文档先行!
api 文档一般是由服务端同学定义的,但最好能由前后端同学一起定初稿;一个人设计容易有疏漏与盲区,两个人一起设计往往双方都能得到进步。我自己在定义 api 的时候也常常会和前端同学交流一下想法,前端同学往往能给出一些建议,大家磨合后开发就会比较顺畅。(一开始可能会在开发的过程中改动初始设计,慢慢设计多了,返工的概率就减少了)
api 文档需要说明的内容下面就和大家介绍一下为一个 api 编写文档需要包含哪些信息,我们以上一篇文章中的接口为例,也就是我们的云你好 api:
我在想有没有一种可能,等我的系列文章更新完后,云你好服务就正式上线商用了...... 别小瞧它只是云你好哦,阿菌会在这系列文章里分享所有自己在当前工作中的心得体会与思考,由浅入深,希望能给大家带来一点点启发,也欢迎各路大佬前来指点交流。
# 云你好服务 API 接口回顾:
@app.get("/api/v1/hello")
def hello():
# 看用户是否传递了参数,参数为打招呼的目标
name = request.args.get("name", "")
# 如果传了参数就向目标对象打招呼,输出 Hello XXX,否则输出 Hello World
return f"Hello {name}" if name else "Hello World"
首先我们要给出描述信息:
描述信息:云你好 api,大家可以通过云你好,获得一次来自云端的问候
然后要给出 api 的请求方式:
请求方式:GET
接着要给出请求地址:
http://127.0.0.1:5000/api/v1/hello
然后要给出请求参数:
然后我们要给出返回参数:
为了让 api 的调用者直接看懂,一定要记得给出响应示例,这里的响应示例不能只包括成功的请求响应,也要包括请求失败的响应:
状态码:200
"hello 阿菌"
状态码:400
"您的余额不足了,不能打招呼了"
状态码:500
"sorry~服务器开小差了"
简单来说就是要把调用 api 各种可能出现的情况告诉用户,我们顺便完善一下云你好的代码,让云你好服务可以容纳以上述三种情况:
# 云你好服务 API 接口
@app.get("/api/v1/hello")
def hello():
"""
首先判断用户的余额够不够
假设我们已经写好了判断逻辑,
后面我们讲 cookie,session,header 等知识的时候会补充
"""
try:
if not enough_money():
return "您的余额不足了,不能打招呼了", 400
# 看用户是否传递了参数,参数为打招呼的目标
name = request.args.get("name", "")
# 如果传了参数就向目标对象打招呼,输出 Hello XXX,否则输出 Hello World
return f"Hello {name}" if name else "Hello World", 200
except Exception:
# 假设在使用云你好服务的过程中出现了异常
return "sorry~服务器开小差了", 500
请求响应设计小加餐
当然,云你好只是个极简的例子,企业开发中是不会直接返回字符串的,一般会约定一个数据格式,比如状态码为 200 返回:
{
"items": ["xxx", "yyy", "zzz", ...],
"total": 10
}
状态码非 200 的响应,也就是请求发生错误的响应数据格式会是这样的:
{
"msg": "sorry~ something went wrong..."
"msg_cn": "sorry~服务器开小差了..."
"error_code": "20000"
"detail": {
"xxx": "...."
}
}
刚开始学编程的时候我不太明白,既然已经有了响应码,比如 400,500,为什么还要弄一个 error_code 呢?
其实,error_code 一般是用来判断客户端错误的,比如说我们设定找不到资源的状态码为 404,但我们的逻辑中很可能会有好几段找数据的逻辑,比如用户传了用户名,地址,手机,邮箱,我们逐个到数据库中判断这些数据是否存在,如果都用 404 作为响应,而且提示信息都是"资源不存在",那么定位错误就比较麻烦了。
有了错误码,我们则可以根据每一种情况定义一个错误码,比如:
以下均为 404 错误
* 用户名不存在 - error_code: 10000
* 收货地址不存在 - error_code: 10001
* 手机号不存在 - error_code: 10002
* 邮箱不存在 - error_code: 10003
这样在开发联调定位错误的时候是非常方便的,而且我们还可以根据不同的状态码,设置不同的报错信息。
不过,写代码就像写文章,其实形式多种多样,没有对错,不必拘泥于一种形式。比如我见过只通过 error_msg 区分不同不同错误的,也挺好的。
如何让 api 文档看起来更舒服?上面我们介绍了为一个 api 编写文档需要对外提供哪些信息,虽然我们知道了要写什么,但是离写出一个好看的 api 文档还是有一定距离的。
这里的距离一共包括以下几点:
- 我们每次写 api 文档时的心情不一样,有的时候我们心情特别好,写 api 文档就会特别认真,会详细且准确地描述每一个参数;但我们总会有心情不好的时候,为了在心情不好的时候也能对自己的行为做出约束,我们得想一些办法。
- api 文档的样式。如果仅仅用一个在线文档(甚至用word/pdf)写 api,我们自己定义的格式通常是比较普通的,为了让 api 文档的阅读者(比如我们自己)有一个好心情,我们得想办法弄个好看点的样式。
- api 文档给出的示例要是能够运行调试就更好了。
这个时候我们可以使用一些外部的 api 文档软件,帮助我们构建出一份漂亮的 api 文档。而 api 文档工具软件,业内首推的就是 swagger 了,免费又好用。
下面我分享一下我是如何给云你好服务接入 swagger api 文档的。
善用 Github 上的开源工具由于云你好是用 python 的 flask 框架搭建的(选择 python 讲解是因为 python 代码就像说大白话,配合注释容易让大家看懂),当我们想要使用某项主流技术的时候,首先可以去 Github 上搜一下有没一些好用的脚手架,这样可以避免重复造轮子。
比如我们可以在 github 上搜索 flask swagger,然后选择按星星的数量排序。
可以看到,第一名的是一个基于 flask 二次开发的框架,虽然这个框架已经直接集成了 swagger,但是不太符合我们的需求,我们是希望能直接基于现有的云你好代码接入 swagger,而不是换一个新框架。
接着我们看第二个快 3k 星星的 flasgger 框架,点进去后直接看示例,看看如何能把软件跑起来。
看完后发现 flasgger 貌似非常符合我们的需求,只要额外添加几行代码就能跑起 swagger 了,于是我们把 swagger 接入我们的云你好服务:
from flask import Flask, request
from flasgger import Swagger, swag_from
app = Flask(__name__)
swagger = Swagger(app)
# 云你好服务 API 接口
@app.get("/api/v1/hello")
@swag_from('hello.yml')
def hello():
try:
if not enough_money():
return "您的余额不足了,不能打招呼了", 400
# 看用户是否传递了参数,参数为打招呼的目标
name = request.args.get("name", "")
# 如果传了参数就向目标对象打招呼,输出 Hello XXX,否则输出 Hello World
return f"Hello {name}" if name else "Hello World", 200
except Exception:
# 假设在使用云你好服务的过程中出现了异常
return "sorry~服务器开小差了", 500
if __name__ == '__main__':
app.run()
swagger 可以通过 yml 格式描述一个 api 的详细信息,可以描述的 api 信息包括且不限于我们上文提到的 api 文档需要说明的内容:
云你好 api
---
parameters:
- name: name
in: query
type: string
responses:
200:
description: "请求成功"
schema:
examples: "Hello 阿菌"
type: string
400:
description: "客户端错误"
schema:
examples: "您的余额不足了,不能打招呼了"
type: string
500:
description: "服务端错误"
schema:
examples: "sorry~服务器开小差了"
type: string
然后我们可以把云你好服务运行起来,访问 http://localhost:5000/apidocs
后能得到一个这样的界面:
我们甚至可以在 swagger 界面上运行调试云你好的 api 接口:
那有了这份 api 文档之后,内部的开发协作就会变得非常方便,大家按照约定调用接口就能使用相应的服务了。
api 文档部署小加餐api 文档其实是语言无关的,不管我们用的是 java、python、golang 什么语言都好,想要得到这样的一份 api 文档,关键在于编写好 yml 文件,描述好我们每一个 api 的用途,请求参数以及响应。
由于我们的云你好服务只是一个非常简单的服务端程序,为了演示方便,阿菌就直接把 swagger 跑在服务端了。
事实上,企业级应用是很少会这样部署 api 文档的,我先带大家看一下 flasgger 大概是怎么把 swagger 集成到我们的后端服务器的,我们可以从运行情况进行反推。
首先我们重启一次服务器,访问一次 http://localhost:5000/apidocs
,并且打开控制台,看一下后端发生了什么:
/Users/game-netease/Desktop/flaskProject/venv/bin/python -m flask run
* Serving Flask app 'app.py' (lazy loading)
* Environment: development
* Debug mode: off
* Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
127.0.0.1 - - [13/May/2022 08:48:33] "GET /apidocs/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/lib/jquery.min.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui-bundle.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui.css HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:33] "GET /flasgger_static/swagger-ui-standalone-preset.js HTTP/1.1" 304 -
127.0.0.1 - - [13/May/2022 08:48:34] "GET /apispec_1.json HTTP/1.1" 200 -
从 flask 为我们打印的请求路径可以看出,flasgger 其实是把一系列 swagger 的静态资源集成到了我们的后端服务中,并且把对应的路由注册到了后端框架的路由表里,当我们访问 /apidocs/
这样的路径时,它返回了相应的静态资源给浏览器渲染。
然而,大量的静态资源传输是会占用网络带宽的,在企业中,如果有大量的静态资源业务一般会配备有专门的静态资源服务,处理这类请求的服务一般对网络带宽要求比较高,对算力的要求相对会低一些。市面上也有不少和静态资源相关的技术服务,比如 CDN 内容分发网络,一些具备冷热存储功能的数据库服务等等。
所以,对于云你好服务来说,假设我们架设了静态资源服务器,更合理的 api 文档部署方案或许该是这样的:
好了,"云你好"服务第二集暂时就更新到这里了,这个系列的文章会比较长,希望在一步步搭建云你好服务的过程中,继续和大家分享我在写代码过程中的一些思考,下一期更精彩!
参考教程:https://www.bilibili.com/video/BV1oA4y1S7hP/