← 返回文章列表

REST 设计与资源建模入门

API入门

资源而不是动作

REST 的核心是把「事物」当资源,用 URI 标识,用 HTTP 方法(GET/POST/PUT/PATCH/DELETE)表示操作。反例是把动作写进路径,如 /getUser、/createOrder,这把 RPC 风格混进了 REST。

常用约定

方法含义幂等
GET读取是
POST新建否
PUT整体替换是
PATCH局部更新否
DELETE删除是

三个实践要点

  1. 复数资源名:用 /users 而非 /user,集合与单项用 /users/{id} 区分;
  2. 用状态码表达结果:201 新建成功、400 参数错误、401 未认证、404 不存在、500 服务端错误;
  3. 分页与过滤:列表用 ?page=2&size=20,避免一次返回全量拖垮接口。

两个反模式

  • 把动词当资源:与其用 POST /users/{id}/ban,不如用一个状态字段做 PATCH;
  • 忽略幂等:重试 POST 可能重复创建,关键写操作要配合幂等键。

动手试试

校验接口返回的 JSON:JSON 格式化。

实战案例:三个让调用方难受的设计

  1. 动词塞进路径:POST /getUserList 无法利用 HTTP 语义与缓存;应改为 GET /users,动作交给方法表达。
  2. 状态码一律 200:无论成败都返回 200、靠 body 里的业务码判断,网关与重试逻辑无法区分。失败应使用 4xx / 5xx。
  3. 分页只有 offset:数据持续写入时 offset 分页会漏项或重复。高频变更的列表应提供游标分页。

常见问题(FAQ)

REST 必须用名词复数吗?这是常见约定而非强制,关键是同一资源在全站使用同一种写法。PATCH 与 PUT 怎么选?PUT 表示整体替换,PATCH 表示局部更新;只改一个字段用 PATCH 更贴合语义。错误响应该包含什么?稳定的错误码、可读信息与字段级原因,别把内部堆栈暴露给调用方。一定要写 OpenAPI 文档吗?对外接口建议有,它是契约、自动化测试与 SDK 生成的基础。

幂等、并发与缓存:三个必须提前定的契约

  1. 幂等键:写接口(下单、支付、发起任务)应支持客户端传入 Idempotency-Key,服务端按该键去重并在有效期内返回同一结果,避免重试造成重复扣款;
  2. 并发控制:更新接口提供 ETag + If-Match(乐观锁),冲突时返回 412,比盲目覆盖安全得多;
  3. 缓存语义:明确哪些响应可缓存、缓存多久,用 Cache-Control 表达;列表类接口默认 no-store,静态资源才用长缓存加哈希。

分页的两种形态

  • 游标分页:?cursor=…&limit=50,适合不断有新数据写入的列表,结果稳定且深翻页不退化;
  • 偏移分页:?offset=…&limit=50,实现简单,但深翻页时数据库开销随 offset 线性增长,且插入新数据会导致漏项或重复。
  • 无论哪种,响应里都应给出显式的“是否还有下一页”字段,而不是让调用方靠“返回条数小于 limit”猜测。

容易被忽略的细节

  • 时间格式:统一使用 UTC 的 ISO 8601(带 Z),不要把本地时间字符串塞进接口;
  • 空值与缺省:明确 null 与字段缺失的语义差异,并保持稳定;
  • 大整数:超过 2^53 的整数(如订单号)以字符串传输,避免 JSON 解析丢精度;
  • 错误码稳定:错误码一旦发布就不应改义,新增语义请新增错误码。

健康检查与运维端点

  • 区分存活与就绪:存活探针只判断进程是否需要重启,就绪探针判断是否可以接流量,二者混用会导致滚动更新时短暂全量不可用;
  • 探针要轻:健康检查不应触发数据库全表扫描或下游调用,否则检查本身成了故障源;
  • 不要暴露内部信息:健康端点只返回状态与版本,不要附带依赖地址、环境变量等敏感内容;
  • 元数据端点:/version 之类端点便于排查“线上跑的是哪个版本”,请带上构建哈希而非仅版本号。

文档即契约

把接口定义(OpenAPI 等)纳入版本控制,并让它成为唯一事实来源:类型、文档与测试都从它生成。手工维护的文档一定会过期,而过期的文档比没有文档更危险——调用方会按错误的理解去实现。