你以为你在设计API,其实你在制造灾难
入行这么多年,我见过最恐怖的事情不是线上数据库被删,而是某天产品经理兴奋地跑过来跟你说:「那个API再加三个字段吧,很快很简单。」然后你发现那个接口已经被87个客户端在用了。
今天咱们好好聊聊API设计——不是那种背书的玩意儿,是真的实战踩出来的血泪经验。读完了你可能会拍大腿:「卧槽,原来我一直在踩坑。」
坑一:REST不是你想怎么用就怎么用
很多人嘴里说「RESTful」,实际上做的是「RESWhatever」——把 GET 当 POST 用,把 POST 当 PUT 用,返回状态码永远只有200和500,中间地带?不存在的。
举个例子,我见过最离谱的一个接口是这样设计的:
POST /getUserInfo
Content-Type: application/json
{"userId": 12345}
兄弟,这不是REST,这是裸奔。你都 POST 了,body 里还带 userId 干嘛?不嫌别扭吗?
GET /users/12345
这才是正道。资源是名词,动作靠HTTP方法,后面的数字是路径参数,干干净净,清清楚楚。
坑二:状态码是门艺术,不是玄学
我见过太多项目所有接口返回值长这样:
{"code": 0, "message": "success", "data": {...}}
或者更离谱的:
{"status": "ok"}
哥们儿,HTTP协议给你提供了几十个状态码,你不用,非得自己发明一套?这是什么,重复造轮子俱乐部?
状态码是用来表达语义的:
- 200 OK:成功了,数据在body里
- 201 Created:资源刚被创建,地址在Header的Location里
- 204 No Content:成功了但没内容,DELETE的时候常用
- 400 Bad Request:客户端的参数有问题,别动不动500
- 401 Unauthorized:没登录,别跟403混用
- 403 Forbidden:登录了但没权限
- 404 Not Found:资源不存在,不是「用户不存在」是404,但「用户存在但订单不存在」也该404
- 422 Unprocessable Entity:格式对了但语义不对,比如邮箱格式正确但已被注册
- 429 Too Many Requests:你被限流了,给个 Retry-After 头
- 500 Internal Server Error:服务器炸了,这个真的只有服务器炸了才用
记住一个原则:能用标准状态码就绝不自己发明。你发明的那套code体系,新人来了得先看三天文档才能干活。
坑三:分页是刚需,但你可能做错了
最简单的分页是什么?
GET /users?page=1&size=20
这有什么问题?问题大了。你翻到第5页的时候,第3页的数据被删了——你刷新一下,用户从第3页掉到了第4页,数据错位了。这种问题在生产环境里巨难追查。
正确做法是用游标分页:
GET /users?cursor=eyJpZCI6MTIzfQ&size=20
或者基于创建时间的游标:
GET /orders?after_id=12345&limit=20
返回的时候带上下一页的cursor:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTYzfQ==",
"has_more": true
}
}
这样无论数据怎么变化,分页结果都是稳定的。用户在第5页刷新的时候,看到的还是那些数据,不会乱跳。
坑四:版本管理是个大学问
API是要版本化的,这点大家都有共识。但怎么做版本化?
常见的三种方式:
第一种:URL版本
/api/v1/users
/api/v2/users
这是最直观的方式,Netflix和Stripe都在用。好处是调试方便,坏处是多个版本要同时维护。
第二种:Header版本
GET /users HTTP/1.1
Accept: application/vnd.mycompany.v2+json
这很「标准」,但说实话,不直观。调试的时候你得记着这个Header,翻日志的时候眼睛要瞎了。
第三种:Query参数版本
/users?version=2
求你了,别这样。这玩意儿很容易跟业务参数混在一起,时间长了谁分得清哪个是版本哪个是业务参数?
我个人的建议是:URL版本优先,简单直观,利于API发现和管理。维护多个版本不丢人,丢人的是版本之间打架。
坑五:错误信息要厚道
很多API的错误返回是这样的:
{"error": "Invalid parameter"}
这个错误信息扔给前端,前端同学能骂你八辈祖宗。「什么参数?参数在哪儿?为什么Invalid?」
好的错误格式应该是这样的:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数验证失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "age",
"message": "年龄必须大于0"
}
]
}
}
这样的错误信息,前端可以直接渲染到对应的输入框旁边,用户体验直接拉满。搞成这样,你的同事会感谢你的。
坑六:安全这事别糊弄
CORS这东西很多人糊弄过去了:
Access-Control-Allow-Origin: *
如果你在做一个公开API,这样写情有可原。但如果你的API有认证信息,还敢这样写,等着被人用JSONP抽走用户数据吧。
认证方面,Bearer Token 是标准做法:
Authorization: Bearer
别把Token放在URL里,URL会被服务器日志、浏览器历史记录、Referer头一路传到天涯海角。Token晒出去了,比密码泄露还麻烦,因为用户往往会用一个密码打天下。
坑七:文档是API的门面
最后说文档。很多人API写完了,Postman测一下,能跑,就交付了。然后别人问他:「这个接口参数是什么?」「不知道,你跑一下试试就知道了。」
这叫什么?这叫「薛定谔的API」——你不测,永远不知道它能不能用。
建议用 OpenAPI Specification (以前叫Swagger) 来写文档。好处是:
- 可以自动生成客户端SDK
- 可以自动渲染出漂亮的交互式文档
- 团队新人来了能快速上手,不需要你亲自讲解每个接口
- 契约测试——你的实现必须跟文档对上,不对上CI就失败
你要是懒得写文档,那迟早你的同事也会懒得看你写的代码。出来混,都是要还的。
写到最后
API设计这件事,看起来是技术活,实际上是产品思维+技术能力的结合。你设计的每一个字段、每一种状态码、每一版返回格式,都在定义使用你API的人的体验。
好的API是沉默的仆人——调用者不需要说明书就能用明白,用的过程中遇到问题看一眼错误信息就知道怎么改,用久了会觉得这API还挺贴心。
差的API是暴躁的老板——你得猜它想要什么,它给错的时候你不知道是你错了还是它错了,它报错的时候你恨不得把它重写一遍。
愿你我都能多做前者,少做后者。毕竟,写代码已经够累了,API就别再给人添堵了。
共勉。