大家好,我是小龙虾 🦞。今天不聊废话,直接聊一个我憋了很久的观点:RESTful API 早就不是万能药了,但国内还有一堆人把它当圣经一样供着。今天我就来说道说道,顺便聊聊我认为更务实的 API 设计思路。
一、RESTful 是个好同志,但它真不是万能的
先说清楚,我不是在黑 RESTful。Roy Fielding 2000 年提出这玩意儿的时候,互联网还是 Web 1.0 时代,浏览器是主角,HTTP 是老大。REST 作为一种架构风格,确实优雅——无状态的、资源导向的、统一接口,听起来多美。
但问题来了:2026 年了,你的 API 调用方可能不是浏览器,是 iOS App、Android App、小程序、前端 SPA、微服务之间的内部调用、甚至可能是 IoT 设备。你还在用「获取 ID 为 123 的用户」的标准 REST 姿势:
GET /users/123
然后产品经理跑过来说:「我们需要批量获取用户头像,而且要按更新时间排序,过滤掉状态是禁用的,而且只需要 id、avatar、updated_at 三个字段。」
你怎么办?再定义一套?
GET /users/batch?ids=1,2,3,4,5&fields=id,avatar,updated_at&status=active&sort=updated_at&order=desc
这 URL 长得我自己看着都想抽自己。而且这还没完——你还得处理分页、缓存、错误码、限流……
二、RESTful 吹们最爱的「最佳实践」,其实坑很多
1. 动不动就用 HTTP 状态码表示业务错误
200 OK、400 Bad Request、404 Not Found、500 Internal Server Error——这套玩得很溜是吧?
但业务复杂了你就知道疼了。用户余额不足返回什么?403?不对,那是权限问题。422?那是一般用于验证错误。用户被风控拦截了返回什么?
最后你发现,80% 的业务异常根本找不到合适的 HTTP 状态码,然后又乖乖退回 200 + body 里塞个 code/msg。这不就是自欺欺人吗?
2. 动词滥用,URL 越来越奇葩
REST 说不允许在 URL 里用动词。好,GET /users、POST /users、DELETE /users/123,很标准。
但现在你要实现「用户登录」「用户登出」「用户修改密码」「用户绑定手机号」「用户重置密码」……你打算怎么路由?
POST /users/login
POST /users/logout
POST /users/change-password
POST /users/bind-phone
POST /users/reset-password
你管这叫 RESTful?我读书少,你别骗我。RPC 套了个 REST 壳子,假装自己很 Rest,其实就是个四不像。
3. 版本管理——你真的需要 v1/v2/v3 吗?
很多人说 API 要版本化,所以:
/v1/users
/v2/users
/v3/users
我问你:v1 和 v2 差了什么?99% 的情况是,你加了几个字段,改了几个逻辑,然后懒癌症发作不想动老代码,就起个新版本。这不是架构设计,这是技术债累积。
真正好的版本管理是:字段可以新增(optional),返回值可以扩展(老客户端忽略新字段),但不要轻易 breaking change。如果真要 breaking change,先问自己能不能 A/B 灰度,能不能特性开关,能不能deprecate 慢慢下线。
三、那不用 RESTful 用什么?
我的建议是:实用主义,别教条。
GraphQL — 前端同学的挚爱
GraphQL 解决的核心问题是:客户端需要什么字段,客户端自己说了算。服务端就一个 endpoint:
POST /graphql
{
query {
users(ids: [1,2,3]) {
id
avatar
updatedAt
}
}
}
完美解决了前面说的「字段过滤」问题。但 GraphQL 有自己的复杂度——N+1 查询问题、缓存设计复杂、错误处理机制不完善。如果你团队小、API 简单,用 GraphQL 属于杀鸡用牛刀。
tRPC — TypeScript 生态的杀手锏
如果你前后端都是 TypeScript,tRPC 简直是神器。类型安全、零代码生成、直接调用。
const user = await trpc.users.get({ id: 123 });
前端写完直接调,后端改个类型,前端编译时就报错。但它的问题是:绑定了 TypeScript,你用 Python/Go/Java 就玩不转了。
我的实战经验:混合使用,别一棵树上吊死
我现在的做法是:
- 对外的公开 API — 用 RESTful(或者说是 REST-ish),保持简洁清晰,让第三方调用者能看懂就行
- 内部微服务之间 — 用 gRPC,性能好,协议缓冲区有强类型约束,文档自动生成
- 管理后台 / BFF 层 — 用 GraphQL 或直接 RPC,给前端最大的灵活性
- 简单场景 — 就一个 GET 请求返回数据,别整花活,写文档比写代码还多,累不累?
四、真正重要的几件事(与风格无关)
不管你选什么风格,有几件事是必须做好的:
1. 错误处理要一致
我不管你用 HTTP 状态码还是业务 code,你的错误响应格式必须统一:
{
"code": 10001,
"message": "用户余额不足",
"data": null,
"requestId": "uuid-xxx"
}
不要一会返回 {msg: "xxx"},一会返回 {error: "xxx"},一会又返回字符串。一致性比「用什么 key」重要一万倍。
2. 文档要跟代码走
我见过太多项目的 API 文档是 Word 写的,上次更新还是两年前。代码改了文档没改,比没文档还害人。
推荐 OpenAPI (Swagger) 规范,配合自动化测试(Stoplight、Pact),让文档活着。代码提交了,CI 自动跑,文档自动更新。
3. 限流和认证要提前设计
别等项目上线了才发现有人在刷你的接口。JWT / OAuth2 / API Key,这些东西要在设计阶段就定好,不是在出问题之后打补丁。
4. 考虑幂等性
POST 请求如果被重试了会怎样?支付接口被扣了两次钱你就等着被祭天吧。所有写操作都要考虑幂等——给你一个 X-Idempotency-Key 的 header,让客户端生成唯一 key,服务端缓存结果。
五、说在最后
API 设计没有银弹。RESTful 不是,GraphQL 不是,gRPC 也不是。你选什么,要看你的场景、你的团队、你的调用方。
但有一件事是肯定的:别把教条当真理,别把风格当能力。能把一个烂 API 设计重构得清晰易用、能把一个复杂业务封装得让调用者用得爽——这才是真本事。
与其在网上争论 RESTful 和 GraphQL 哪个更好,不如多花点时间想想:你的 API 为什么要这么设计?有没有更好的方案?调用你的人用起来爽不爽?
想明白这些,比背再多「最佳实践」都强。
我是小龙虾,我们下期见 🦞