3. 接口设计原则回顾:RESTful API 设计规范,单一职责原则在接口中的应用,接口版本管理策略
说实话,我见过太多团队在接口设计上栽跟头了。有的接口参数混乱,有的一个接口干三件事,还有的版本管理全靠口头约定。今天咱们就把这些坑一个个填上。
3.1 RESTful API 设计规范
RESTful 不是什么高深理论,说白了就是一套约定俗成的接口设计规矩。我刚开始写接口时也觉得这玩意儿麻烦,直到接手一个全是自定义动词的项目……嗯,那酸爽,至今难忘。
3.1.1 资源命名与 HTTP 动词
核心思想就一句话:用名词表示资源,用动词表示操作。来看个对比:
| 错误做法 | 正确做法 | 说明 |
|---|---|---|
GET /getUser |
GET /users/{id} |
动词交给 HTTP 方法 |
POST /createOrder |
POST /orders |
创建用 POST |
GET /deleteProduct?id=1 |
DELETE /products/1 |
删除用 DELETE |
POST /updateUserStatus |
PATCH /users/{id}/status |
部分更新用 PATCH |
关键原则:URL 中只出现名词复数形式,操作语义由 HTTP 方法表达。我见过一个项目,所有接口都用 POST,结果排查问题时根本分不清哪个是查询哪个是修改。
3.1.2 状态码的正确使用
很多新手喜欢所有成功都返回 200,所有失败都返回 500。这其实是在给自己挖坑。我个人习惯这样分配:
- 200:GET 成功、PUT 成功
- 201:POST 创建成功
- 204:DELETE 成功(无返回体)
- 400:参数校验失败
- 401:未认证
- 403:无权限
- 404:资源不存在
- 409:资源冲突(比如重复创建)
- 422:业务逻辑校验失败
- 429:限流触发
- 500:服务端内部错误
小技巧:我曾经在项目中统一封装了响应体,把业务错误码放在 body 里,HTTP 状态码只反映传输层状态。这样前端可以根据业务码做精细化处理,而不是看到 500 就弹「系统异常」。
3.2 单一职责原则在接口中的应用
单一职责原则(SRP)不只是类设计的事,接口同样适用。一个接口只做一件事,并且做好。你想想看,如果一个接口既查询用户信息又更新用户积分,那它出了问题你都不知道是查挂了还是改挂了。
3.2.1 接口粒度的把控
我遇到过最夸张的一个接口,参数有 20 多个,返回字段 50 多个,里面同时做了订单查询、库存扣减、积分累计、消息推送……这哪是接口,分明是个定时炸弹。
正确的做法是:
- 查询接口:只做查询,不做修改
- 命令接口:只做修改,不做查询
- 批量接口:明确语义,比如
POST /orders/batch - 状态变更接口:用 PATCH 只更新变更字段
// ❌ 错误:一个接口干三件事
POST /api/order/process
{
"orderId": 123,
"action": "pay_and_ship_and_notify"
}
// ✅ 正确:拆分为三个独立接口
POST /api/orders/123/pay
POST /api/orders/123/ship
POST /api/orders/123/notify
避坑指南:我曾经把一个「创建订单并发送通知」的接口写在一起,结果通知服务挂了,订单也创建失败。后来拆成两步:先创建订单(返回订单 ID),再异步发送通知。这样即使通知失败,订单数据也不会丢失。
3.2.2 参数与返回值的单一职责
接口的参数和返回值也要遵循单一职责。一个接口的参数应该只包含它需要的数据,不要为了「方便」把整个对象传进去。
// ❌ 错误:传了整个用户对象,但只需要 userId
POST /api/orders
{
"user": {
"id": 1,
"name": "张三",
"email": "zhang@example.com",
"phone": "13800138000"
},
"productId": 100
}
// ✅ 正确:只传需要的字段
POST /api/orders
{
"userId": 1,
"productId": 100
}
3.3 接口版本管理策略
接口版本管理是个老生常谈的话题,但真正做好的团队不多。我见过最野的路子是在接口文档里写「此接口已废弃,请使用新接口」,然后就没有然后了。
3.3.1 三种主流版本策略
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径版本 | /api/v1/users |
直观、易路由 | URL 冗余 |
| 请求头版本 | Accept: application/vnd.myapp.v2+json |
URL 干净 | 调试不方便 |
| 查询参数版本 | /api/users?version=2 |
实现简单 | 容易被缓存污染 |
我个人习惯用 URL 路径版本。为什么?因为直观。你打开浏览器就能看到版本号,不用翻请求头。而且网关做路由分发也方便。
3.3.2 版本的生命周期管理
版本不是发布了就完事了,得有退出机制。我一般这样管理:
- v1:当前稳定版,全量使用
- v2:新功能开发中,灰度发布
- v1-deprecated:标记废弃,保留 3 个月
- v1-removed:彻底下线,返回 410 Gone
实践经验:我在上一个项目中,每次发布新版本都会在响应头加一个 X-API-Version: v2 和 X-API-Deprecated: true。客户端看到废弃标记后,有足够时间迁移。3 个月后,我们统计调用量,确认没有流量了再下线。
3.3.3 向后兼容的底线
版本升级时,有些东西绝对不能改:
- 不能删字段:老客户端可能还在用
- 不能改字段类型:比如把 int 改成 string,解析会崩
- 不能改枚举值:新增可以,删除不行
- 不能改接口语义:GET 不能突然变成写操作
血的教训:我曾经把一个接口的返回字段 userName 改成了 name,觉得「反正前端也会改」。结果有 3 个老版本客户端没更新,线上直接白屏。从那以后,我所有接口都加了字段映射层,新旧字段同时返回,等确认旧字段没人用了再删。
3.4 本章知识体系
下面这张图把本章的核心知识点串起来了,你可以对照着回顾:
这三块内容看似独立,其实环环相扣。RESTful 规范给你定骨架,单一职责帮你定粒度,版本管理给你留退路。少了任何一个,接口设计都会出问题。