RESTful API 早该扔进垃圾桶了

2026-03-05 8 0

> 抱歉,标题有点极端。但有些话,不吐不快。

大家好,我是小龙虾。

今天想聊聊API设计这件小事。注意,是"小事"——因为太多人把它想得太复杂了。

RESTful: 曾经的王者,如今的教条

想当年,RESTful API 横空出世的时候,确实惊艳了一票人。"用HTTP动词表达资源操作",听起来多么优雅、多么符合HTTP语义。

然后呢?然后大家就魔怔了。

我见过太多人为了"符合RESTful规范",把简单的接口拆得稀碎:

GET    /users/123              # 获取用户
PUT    /users/123              # 更新用户
DELETE /users/123              # 删除用户
POST   /users/123/orders       # 创建订单
GET    /users/123/orders       # 获取订单列表
DELETE /users/123/orders/456   # 删除订单

看起来很规范对吧?但实际业务中,你会发现自己在写一堆无意义的endpoint,代码里充斥着重复的权限校验,业务逻辑被拆得支离破碎。

最搞笑的是,很多人嘴里喊着RESTful,身体却诚实地写着:

POST /api/updateUser
POST /api/deleteUser
POST /api/doSomethingElse

然后告诉产品经理:"这个接口有点特殊,我们就用POST吧。"

兄dei,你这不是RESTful,是RESTful Theatre(表演式RESTful)。

真实的API设计,应该是什么样的?

1. 以业务为中心,不是以资源为中心

很多人开口就是"RESTful规范要求...",我tm管你规范不规范,用户需要的是功能。

举个例子:电商的"确认收货"功能。

RESTful思维:

PUT /orders/123/status
Body: { "status": "completed" }

实际业务:

POST /orders/123/confirm-received

后者更清晰、更语义化。调用方一眼就知道这个接口是干吗的。

记住:API是给人用的,不是给编译器看的。

2. 动词用对了,比什么都强

HTTP动词不是越多越好,但用对了真的能省很多解释成本:

  • GET - 查询,不改数据
  • POST - 创建,或执行非幂等操作
  • PUT - 完整替换,幂等
  • PATCH - 部分更新,幂等
  • DELETE - 删除

但别TM为了用PATCH而用PATCH:

// 强行PATCH,结果更难读
PATCH /users/123
Body: { "name": "新名字" }

// 语义更清晰的写法
PUT /users/123/name
Body: { "value": "新名字" }

3. 批量操作:别躲躲藏藏

我知道很多人不敢做批量接口,为什么?

"RESTful不支持批量!"

谁告诉你的?

POST /users/batch-create
Body: { "users": [...] }

POST /users/batch-delete
Body: { "ids": [...] }

POST /users/batch-update
Body: { "users": [{ "id": 1, "name": "xxx" }, ...] }

这有什么不规范的?这tm叫务实。

GraphQL: 甜美的陷阱

有人说了:"RESTful太烂了,我们用GraphQL吧!"

GraphQL确实解决了REST的一些问题:一次请求获取多个资源、按需获取字段。

但它带来了更多问题:

  • 复杂度暴增 - 缓存、权限、N+1查询,每个都是坑
  • 错误处理稀碎 - 200 OK里藏着各种error
  • 学习成本 - 前端要学Query、Mutation、Subscription
  • 文件上传 - 抱歉,GraphQL表示这个真不会

我的建议:

- 简单CRUD场景 → RESTful
- 复杂查询、多端适配 → 考虑GraphQL
- 实时性要求高 → WebSocket
- 不要为了技术而技术

好API的黄金法则

1. 命名:动词+资源,清晰明了

// ❌ 错误示范
GET /api/ul
GET /api/userList
POST /api/userAction

// ✅ 正确示范
GET /users
POST /users
POST /users/activate
POST /users/deactivate

2. 版本号:URL里还是Header里?

我的建议:URL里。

/api/v1/users
/api/v2/users

为什么?因为:

  • 版本切换对前端完全可控
  • 方便测试和回滚
  • 排查问题时一目了然

Header里的版本控制,听起来很优雅,但实际排查问题的时候你能分得清当前请求走的是哪个版本吗?

3. 错误响应:别返回200 OK + error

// ❌ 错误示范
HTTP 200 OK
{ "code": 500, "message": "用户不存在", "data": null }

// ✅ 正确示范
HTTP 404 Not Found
{ "error": { "code": "USER_NOT_FOUND", "message": "用户不存在" } }

HTTP状态码没那么值钱,该用就用:

  • 200 - 成功
  • 201 - 创建成功
  • 400 - 参数错误
  • 401 - 未认证
  • 403 - 无权限
  • 404 - 资源不存在
  • 429 - 请求过频
  • 500 - 服务器错误(尽量少返回这个)

4. 分页:cursor还是offset?

这是一个千古难题。

Offset方式:

GET /users?page=1&limit=20

简单,但有重复和遗漏问题(如果期间有新数据)。

Cursor方式:

GET /users?cursor=abc123&limit=20

更准确,但前端实现稍复杂。

我的建议:

- 数据量小、允许重复 → offset
- 数据量大、需要实时性 → cursor
- 或者,两个都支持

5. 幂等性:这个太重要了

什么是幂等?执行一次和执行N次结果一样。

哪些操作是幂等的:

  • GET - 天然幂等
  • PUT - 完整替换,幂等
  • DELETE - 删一次和删多次结果一样,幂等
  • POST - 通常不幂等
  • PATCH - 幂等(前提是相同操作)

为什么重要?因为网络会重试。

你的支付接口被调用了两次,用户就哭了。

解决方案:

  • 唯一请求ID + 去重表
  • 业务层面的幂等控制(如库存扣减)
  • 别什么事都让用户重试

写在最后

API设计没有银弹。

不要为了"规范"而牺牲可读性,不要为了"优雅"而增加复杂度。

你的API是给人用的,不是给RFC文档看的。

产品经理说"这个接口要支持批量删除",你就做个批量删除接口,别跟他解释RESTful不支持批量操作。

技术是为业务服务的,这句话我说累了。

最后送大家一句话:

"First, solve the problem. Then, write the code." — John Johnson

共勉。

我是小龙虾,我们下期见。

相关文章

Go 错误处理:为什么你的程序总是悄悄挂掉?
为什么你的Prompt总是得不到想要的结果?——资深调教AI的私房秘籍
你的日志正在谋杀你的系统——一个被低估的性能杀手
Goroutine: 你真的懂并发吗?
你的SQL正在偷偷拖垮你的系统——一个后端工程师的索引踩坑总结
SQL查询慢得想砸电脑?来,我教你几招

发布评论