RESTful API 设计最佳实践:资源建模、状态码与版本化
REST(Representational State Transfer)是目前 Web API 最主流的架构风格。微软在 Azure 架构中心发布的《Web API 设计最佳实践》与 Google 的 AIP(API Improvement Proposals)设计规范,共同构成了业界公认的设计依据。无论你用的是 Node.js Express、Python FastAPI 还是 Go Gin,遵循这套约定都能让接口更易理解、更易维护。
一、资源建模:用名词而不是动词
RESTful API 围绕"资源"组织,每个资源由唯一的 URI 标识。设计 URI 时要记住三条核心约定:
- 用复数名词表示集合:
/orders是订单集合,/orders/5是单个订单。 - 不要在 URI 里用动词:HTTP 方法本身已经表达了动作,
POST /orders是创建订单,而不是/create-order。 - 关系保持简单:推荐
collection/item/collection这种层级,例如/customers/1/orders,避免过深的嵌套如/customers/1/orders/99/products。
此外,微软明确建议不要让 API 直接镜像数据库表结构。REST 建模的是业务实体和操作,而不是把每张表都暴露成资源——这既增加了攻击面,也可能造成数据泄露。必要时在数据库与 API 之间加一层映射。
二、HTTP 方法与状态码:语义要对齐
每种 HTTP 方法都有明确语义,状态码要能准确反映结果:
| 方法 | 语义 | 常见状态码 |
|---|---|---|
| GET | 读取资源 | 200、204、404 |
| POST | 创建资源 | 201(Location 头带新资源 URI)、400、405 |
| PUT | 整体更新,必须幂等 | 200、201、204、409 |
| PATCH | 部分更新 | 200、400、409、415 |
| DELETE | 删除资源 | 204、404 |
PATCH 推荐使用 JSON Patch(RFC 6902)或 JSON Merge Patch(RFC 7396);PUT 必须是幂等的,重复提交结果一致,而 POST 和 PATCH 不保证幂等。对耗时操作(如导出报表),可以返回 202 Accepted 并提供状态查询端点,客户端通过轮询获取进度。
三、分页、过滤与排序
大数据集不要一次全量返回。使用查询参数:
GET /orders?limit=25&offset=0
GET /orders?status=shipped&sort=price
- limit / offset:控制返回条数与起始位置,并设置上限防止 DoS。
- 过滤:通过查询字符串传条件,如
?status=shipped。 - 排序:用
sort=price指定排序字段。 - 字段选择:用
fields=id,name让客户端只取需要的字段。
注意:排序会影响缓存命中率,因为查询字符串参与缓存键。
一个规范接口的完整例子
把前面的规则拼起来,一个规范的订单查询接口大概长这样:
GET /v1/orders?status=paid&limit=10&offset=0
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"items": [
{ "id": "ord_8f2a", "customer_id": "cus_31", "total": 1250, "status": "paid" }
],
"pagination": { "limit": 10, "offset": 0, "total": 42 }
}
注意几个细节:资源用复数名词 orders;版本放在 URI 前缀 /v1;分页参数显式出现在查询串里;响应统一包一层 pagination,而不是让客户端自己去猜总条数。
错误响应的统一格式
错误处理是最容易各写各的部分。建议全站统一一种错误体,客户端只需解析一次:
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order ord_9x1b was not found",
"detail": { "resource": "orders", "id": "ord_9x1b" }
}
}
code 用机器可读的枚举,方便客户端做分支判断;message 面向人类;detail 放可选的上下文信息。配合 404、409、422 等状态码,前端和监控都能快速定位问题。
幂等性的实际考量
PUT 要求幂等,但"整体更新"并不总能覆盖所有业务需求。更稳妥的做法是给写接口加一个 Idempotency-Key 请求头:客户端为每次"逻辑上只应执行一次"的操作生成一个随机键,服务端按键去重。支付、下单这类接口尤其值得加上——网络超时后客户端重试,也不会重复扣款或重复下单。
四、版本化:兼容旧客户端
API 不会一成不变。微软列出了四种版本化方案:
- URI 版本化:
/v2/customers/3,简单直观,但版本多了难维护。 - 查询字符串版本化:
/customers/3?version=2,同一资源同一 URI。 - Header 版本化:自定义头如
api-version: 2,不污染 URI。 - 媒体类型版本化:
Accept: application/vnd.contoso.v2+json,最 RESTful 也最复杂。
对大多数中小型项目,URI 版本化最简单、对缓存最友好,是常见首选。
版本化不是发布当天的动作,而是一套提前规划的契约。一个常见场景:你的 API 已经有第三方客户端在调用,却要把 /customers/3 的响应从扁平结构改成嵌套结构。如果直接改,老客户端会立刻解析失败。更稳妥的做法是发布 /v2 新版本,保留 /v1 运行一个过渡期(例如 6 个月),在文档里标注弃用时间,并通过 Deprecation 响应头告知客户端;等旧版本调用量明显下降后再下线 /v1。这个节奏比"一次大改、全员迁移"稳得多,也更容易争取到客户的配合窗口。
五、OpenAPI:契约优先
微软推荐采用 OpenAPI(OAS)做契约优先设计:先定义接口契约,再实现代码。Swagger/OpenAPI 工具可以从契约自动生成文档和客户端库。Google 的 AIP 还提供了 API Linter(linter.aip.dev)来自动检查设计规范。
如果对 REST 和 GraphQL 的取舍还不确定,可以先读 REST vs GraphQL 对比与选型指南,再结合 网站 API 集成基础指南 理解调用方视角。
参考:微软官方《Web API 设计最佳实践》 https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
参考:Google AIP 设计规范 https://google.aip.dev/
16IDC 观察
对独立开发者和小团队,API 设计规范是"一次投入、长期受益"的事:接口命名统一后,前端对接、自动化测试和后续迭代都会顺畅很多。上线前记得规划好 API 错误处理与重试策略,并把接口文档纳入 CI。更多后端工程实践见 后端对接 分类。
原文来源:https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design