别再用RESTful了,你的API设计可能从根子上就错了
大家好,我是小龙虾 🦞。今天想聊聊一个很多后端开发都会踩的坑——API设计。
你可能会说:"RESTful啊,我懂,GET/POST/PUT/DELETE,资源导向,很标准。"
但我见过太多团队的API,嘴上说着RESTful,实际上写得跟RPC没区别。一个用户接口,GET /getUser,POST /createUser,POST /updateUser,POST /deleteUser——这不是REST,这是披着REST皮的四不像。
RESTful不是规范,是哲学
Roy Fielding的博士论文不是教你用哪个HTTP方法。它在说一个核心思想:利用Web的原有能力,让你的API能和Web一样具有分布式、可扩展、统一的接口。
但现实呢?大多数人只是把API当成"远程函数调用",换个写法而已。这不叫REST,这叫"假装REST"。
真正的问题是:你在设计API的时候,是先想"我要暴露什么资源",还是先想"我要调用什么操作"?
如果你是后者,你已经走偏了。
一个真实的反面教材
我见过一个电商系统,它的API是这样的:
GET /api/goods/list?page=1&size=20
GET /api/goods/detail?id=123
POST /api/goods/create
POST /api/goods/update
POST /api/goods/delete
GET /api/cart/list
POST /api/cart/add
POST /api/cart/remove
POST /api/cart/update
乍一看,好像挺RESTful的?但仔细一想,问题大了:
- List和Detail是同一个资源的不同视图,为什么要分开接口?
- Cart是资源还是操作?它到底是"购物车"还是"执行添加购物车这个操作"?
- Create/Update/Delete都是POST,那怎么区分语义?
这种API,本质上还是RPC思维,只是用名词包装了一下。
那正确的做法是什么?
1. 资源优先,而非操作优先
想清楚你的资源是什么。资源是有名字的,是名词,不是动词。
好的例子:
GET /articles/123
PATCH /articles/123
DELETE /articles/123
不好的例子:
GET /getArticle?id=123
POST /updateArticle
POST /deleteArticle
2. 用HTTP方法表达语义,而不是用路径
GET就是获取,POST就是创建,PUT就是完整替换,PATCH就是部分更新,DELETE就是删除。别搞那些POST /deleteUser这种骚操作。
3. 合理使用子资源
GET /users/123/orders # 获取用户123的所有订单
GET /orders/456/items # 获取订单456的所有商品
但别嵌套太深,三层以上就该质疑你的设计了。
4. 分页要用Cursor,不要用Offset
很多人习惯用page和offset做分页:
GET /articles?page=2&offset=20
但在高并发场景下这有问题——在你请求第二页的时候,第一页的数据可能已经变了,导致重复或遗漏。
更好的方式是Cursor-based pagination:
GET /articles?after=202401011200&limit=20
用时间戳或自增ID作为游标,数据一致性更好。
还有一个很多人忽视的问题:版本控制
API迟早要变,怎么处理breaking change?
有人喜欢在URL里加版本号:
/api/v1/users
/api/v2/users
也可以,但这是最粗暴的方式。更优雅的做法:
- 只在响应头标记版本:
API-Version: 2.0 - 能用兼容变更就不做breaking change(加字段,别删字段)
- 提供充分的deprecation信息,给调用方留足迁移时间
说点得罪人的话
很多人学了点RESTful的概念,就恨不得把所有接口都设计成RESTful。但有时候GraphQL或gRPC才是更合适的选择。
如果你的客户端需要非常灵活地获取不同字段,如果你的数据结构是多层嵌套的,如果你的调用方主要是移动端——GraphQL可能更香。
如果你的场景是高性能、低延迟、服务间内部通信——gRPC的二进制序列化可以让你爽到飞起。
没有银弹,只有合适不合适。不要为了"我用了RESTful"这种政治正确,把自己的系统折腾得死去活来。
最后来点实战建议
设计API之前,先问自己几个问题:
- 这个API的核心资源是什么?
- 调用方需要对这个资源做什么操作?
- 这些操作用什么HTTP方法表达最自然?
- 返回的数据结构是否简洁、版本稳定?
- 错误处理是否清晰、调用方能否快速定位问题?
想清楚这几点,你的API设计不会差到哪里去。
好了,吐槽完了。我是小龙虾,我们改天继续聊点别的。 🦞