为什么你的REST API让人想砸键盘?一位后端开发者的血泪吐槽

2026-07-19 5 0

为什么你的REST API让人想砸键盘?一位后端开发者的血泪吐槽

干过后端开发的基本上都踩过这种坑:对接一个第三方API,文档写得跟天书似的,返回数据结构嵌套八层,关键字段还是个动态key,文档三年没更新,接口报500连个错误信息都没有。打电话给技术支持,对方说"这个接口一直是这样子的呢"。

我干这行这么多年,见过太多烂API了。今天不吐不快,顺便聊聊什么叫真正好用的API设计。


REST不是什么了不起的东西,别把它当圣经

很多人一聊API设计就"RESTful",仿佛不加个ful就不够专业。但现实是,HTTP协议那几条动词(GET/POST/PUT/DELETE)根本装不下业务逻辑的复杂度。

举几个常见的坑:

  • 一个搜索接口,GET请求带几十个过滤参数,URL长得能绕地球三圈,Nginx直接报414
  • "更新"语义用POST,"创建"也用POST,"部分更新"还是用POST,HTTP动词?不存在的
  • 一个业务操作涉及三个资源的原子更新,REST设计下你要调三次API,中间任何一次失败都不知道怎么回滚

我的观点:REST是风格,不是教条。你的业务逻辑决定你的API长什么样,而不是REST规范决定你的业务逻辑该怎么做。


好API的第一个标准:连傻子都能看懂

API是给人类用的,不是给机器用的。机器只管执行,人类得理解、记忆、调试、排查。

什么叫好的URL设计:

# 差的:嵌套地狱
GET /api/v1/users/12345/orders/6789/items/111

# 好的:扁平化,可读性强
GET /api/v1/orders/6789/items
GET /api/v1/users/12345/orders

什么叫好的命名:

# 差的:缩写狂魔,看不懂
GET /api/usr/ord/lst?tp=1&st=pend

# 好的:完整单词,语义清晰
GET /api/users?status=pending
GET /api/orders?type=shipping

URL里的名词是复数还是单数其实不重要,重要的是整站统一。最烦的就是一套接口里有的用单数有的用复数,也不知道谁先谁后,反正就是乱。


错误处理:别让调用方猜谜

这是最容易暴露后端开发者情商的地方。想象一下这个场景:

你调一个接口,返回{"code": 500, "message": "Internal Server Error"}

你打电话给厂家,厂家说"这个是网络波动导致的呢"。你问"那我重试有用吗",对方说"可能有用呢"。

这种错误处理等于没做。

好的错误响应应该长这样:

{
  "code": 10401,
  "message": "用户积分不足,无法兑换此商品",
  "detail": {
    "userPoints": 50,
    "requiredPoints": 100,
    "shortage": 50
  },
  "action": "充值积分或选择其他商品",
  "traceId": "abc123def456"
}

错误码要分类清晰:10xxx是用户侧错误(参数错误、权限不足),20xxx是业务逻辑错误(余额不足、配额超限),50xxx是服务侧错误(数据库挂了、依赖服务超时)。调用方一看码就知道该自己排查还是该打电话骂你们。

还有,traceId必须要有,出了生产问题调用方报过来你能直接搜日志,没有traceId的错误响应就是在耍流氓


版本管理:向前兼容是基本礼貌

接口要版本号,这是常识。但很多人做版本号就是挂个v1/v2,然后"这次我们改了大返回结构",原有调用方全部炸裂。

真正的版本管理原则是:新增字段可以,删除字段不行,改字段名不行,改字段类型不行。如果必须要改,请走新的版本号,并且给旧版本至少三个月的过渡期。

# URL版本是最直观的方式
GET /api/v1/users/123
GET /api/v2/users/123

# Header版本适合复杂场景
GET /api/users/123
Accept: application/vnd.myapi.v2+json

我自己倾向URL版本,因为调试的时候一眼就能看出在测哪个版本,不用去翻Header。


性能优化:别把数据库裸奔暴露给前端

N+1查询问题,懂的都懂,不懂的迟早会懂。

# 差的:一次列表请求,N次详情查询
GET /api/v1/orders
# 返回
[
  {"id": 1, "userId": 101, "productName": null},
  {"id": 2, "userId": 102, "productName": null}
]
# 然后前端逐个调
GET /api/v1/users/101
GET /api/v1/users/102
# 页面加载:1 + N 次请求,数据库:N次额外查询
# 好的:一次请求,返回完整数据
GET /api/v1/orders?include=user,items
# 返回
[
  {
    "id": 1,
    "user": {"id": 101, "name": "张三"},
    "items": [{"id": 1, "name": "小龙虾"}]
  }
]

include参数或者fields参数是标配,让调用方决定要拿什么数据。既减少网络传输量,又减轻数据库压力。不做这个的前端API,要么是早期草台班子,要么是后端懒得写。


幂等性:重试不炸是基本素养

网络不稳定的时候,前端重试是常态。如果你的接口不具备幂等性,重试一次就多扣一次钱、多创建一条订单、多发一封邮件,那这个API就是定时炸弹。

哪些操作天然幂等:

  • GET:只读操作,天然幂等
  • PUT:用固定数据覆盖,幂等
  • DELETE:删除一次和删除一百次结果一样,幂等

哪些操作需要特殊处理:

  • POST创建订单:不幂等,需要通过业务ID或唯一索引防止重复
  • 扣款/扣积分:不幂等,需要幂等键(idempotency key)
# 幂等键实现示例
POST /api/v1/orders
Idempotency-Key: client-generated-uuid-12345
# 服务端根据key缓存响应,重复请求直接返回缓存结果

所有涉及金钱的操作,幂等键是必选项,不是可选项。


文档:没有文档的API等于没有API

最后这条可能最重要,也可能最容易被忽视。文档不是写给自己看的,是写给调用方看的。

好文档三要素:

  1. 每个字段都要有说明:类型、含义、可能的值、是否必填
  2. 要有完整示例:请求示例和响应示例,能直接复制粘贴调试的那种
  3. 要有错误码对照表:每个错误码代表什么,调用方该怎么处理

我见过最离谱的文档是一张截图,上面写着"详见接口文档",然后接口文档就是个Word文档在另一个服务器上,链接已经404了。

工具推荐:Swagger/OpenAPI是标配,写代码的时候顺手就生成了,别偷懒。手写文档最大的问题不是初始工作量大,是后续维护不起来,最后文档和接口变成两个世界。


写在最后

API设计这事,说难听点,就是"你行你上"和"我上我也行"之间的差距。看起来谁都能设计,但设计出来好不好用、稳不稳定、调用方骂不骂你,那就是另一回事了。

核心原则就三条:

  • 调用方是傻子:你的API要让傻子也能调通
  • 错误信息是信任:错误处理做得好,调用方才愿意重试而不是直接换供应商
  • 文档是合同:文档写了什么,接口就得是什么,改了接口必须同步改文档

做到这三条,不敢说你的API能封神,但至少不会让人想砸键盘。

至于那些文档三年不更新、错误码永远是{"code": 0, "msg": "success"},连个traceId都没有的API——

建议转行送外卖,真的。

相关文章

你的数据库事务,正在悄悄毁掉你的系统
🦞 当小龙虾遇上AI:新闻速递与那些让人上头的奇技淫巧
写了三年API,我踩过的那些坑和血泪经验
为什么我从不让 PostgreSQL 写日志:一次危险的认识转变
写API这事儿,90%的人都在假装很懂
你的接口为什么总是返500?大多数人都搞错了异常处理的本质

发布评论