做了这么多年后端,我有一个特别深刻的体会:写API的时候觉得自己是个艺术家,接口设计行云流水,路径命名优雅至极。等后人接手维护的时候才发现——这哪是艺术,这是行为艺术。
一、URL路径:我看不懂你的浪漫
先说个真实案例。我曾经接手一个项目,看到这么一个接口:
GET /api/v2/internal/user/123/profile/address/primary
我数了一下,7层嵌套。当时我的反应是:写这个接口的人,是不是觉得自己在写诗?而且这首诗还押韵了。
RESTful API的核心理念是什么?资源导向。名词复数形式,路径描述资源层级关系。但是很多人理解偏了,觉得嵌套越深越"REST"。其实嵌套超过2层,你就该考虑是不是设计有问题了。
正确的做法是什么?
GET /users/123/addresses?primary=true
GET /addresses?user_id=123&primary=true
简洁、清晰、一眼看懂。少一点嵌套,多一点真诚。
二、HTTP方法乱用:POST和GET傻傻分不清
这个问题在国内项目里极其普遍。我见过太多人用POST干所有的事情:查列表用POST、分页用POST、搜索用POST。问就是"POST安全"。
安全你个头啊!HTTP方法不是随便选的,它们有语义!
- GET:读取资源,安全、幂等、可缓存
- POST:创建资源,不安全、不幂等
- PUT:完整替换资源,幂等
- PATCH:部分更新,不一定幂等
- DELETE:删除资源,幂等
有人会说:"我用POST查数据是因为GET有长度限制,参数太长发不进去。"兄弟,那是你的参数设计有问题。正常查个列表、搜个关键词,需要传几十KB数据吗?
正确的打开方式:
# 查询 - GET
GET /articles?status=published&page=1&per_page=20&category=backend
# 搜索 - GET(带复杂查询条件)
GET /articles/search?q=RESTful&tags=api,design&sort=created_at:desc
# 创建 - POST
POST /articles
{"title": "...", "content": "..."}
# 更新 - PUT/PATCH
PUT /articles/123
{"title": "新标题"}
# 删除 - DELETE
DELETE /articles/123
三、状态码:200 OK走天下
这是我最受不了的。有人写API,所有响应都返回200 OK,然后自己在body里加个code字段表示业务状态。400、401、403、404、500,统统不存在的。
# 这是什么鬼?
{
"code": 1001,
"message": "用户不存在",
"data": null
}
# 正确的是这样的:
HTTP 404 Not Found
{
"error": "user_not_found",
"message": "用户不存在",
"details": {
"user_id": 123
}
}
你用HTTP状态码,是把职责还给协议层。前端可以根据状态码统一做错误处理:4xx是客户端错误要提示用户,5xx是服务端错误要上报监控。你搞个code=1001,前端怎么判断这是网络问题还是业务问题?
四、版本管理:v1、v2、v3,版本即人生
接口版本怎么管理?这也是个重灾区。常见的三种方式:
# 1. URL路径(最推荐)
/api/v1/users
/api/v2/users
# 2. Query参数(不推荐)
/api/users?version=1
# 3. Header(过度设计)
Accept: application/vnd.api+json; version=1
URL路径是最直观的方式,调试方便,缓存友好。很多人吐槽说URL包含版本不优雅,但实际上这就是最实用的方案。别为了"优雅"把简单的事情搞复杂。
版本切换的时候要注意什么?
- 旧版本要有明确的废弃时间和公告
- 新旧版本并行期间,旧的也要同步修复bug
- 不要在v1里加新功能然后让v2变成摆设
五、分页:你的limit offset还活着吗?
数据库数据量大了之后,OFFSET分页就是性能杀手。你以为OFFSET 1000000是跳过了100万条?数据库说:"不好意思,我得先数100万零十条,然后扔掉前100万条。"
正确的方式是游标分页(Cursor Pagination):
# 游标分页
GET /articles?limit=20&cursor=eyJpZCI6MTIzfQ
# 响应
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTAzfQ",
"has_more": true
}
}
游标分页的好处是什么?无论翻到第几页,性能都是稳定的。不像OFFSET,越往后越慢。当然,如果你的产品经理坚持要用"跳到第XX页"功能,那你只能祈祷数据量别太大了。
六、幂等性:你确定你的接口可以重试?
网络是不稳定的,重试是必然的。如果你的接口不支持幂等,重试就会出问题。最典型的例子就是支付接口:
# 没有幂等的扣款接口
POST /orders/123/pay
{"amount": 100}
# 重试一次 = 扣200块
# 用户:???
# 幂等的扣款接口
POST /orders/123/pay
{"amount": 100, "idempotency_key": "order_123_pay_unique_key"}
# 服务端根据idempotency_key做幂等判断
# 重复请求直接返回之前的响应
对于创建类的POST请求,一定要支持幂等key。查询和删除天生幂等,GET和DELETE可以放心重试。PUT和PATCH要确保业务逻辑支持幂等。
七、写在最后
好的API设计不是什么高深莫测的东西,就是把简单的事情做对:
- 路径清晰,层级不超过2层
- 方法语义正确,不要用POST干所有事
- 状态码用对,别200 OK走天下
- 版本管理实用为主,别为了优雅过度设计
- 分页要考虑性能,游标优于OFFSET
- 幂等性是救命稻草,支持重试是基本素养
你要是问我为什么知道这么多坑,那都是血和泪的教训。不过话说回来,每次踩坑都是成长的机会。等你踩完了这些坑,你就是团队里最懂API设计的那个人了。
祝你的API好维护,好维护,真的好维护。