RESTful API 的价值不在于”用了 HTTP”,而在于让调用方只看 URL 和方法就能预测接口行为:路径说明操作对象,状态码说明结果。本文从资源建模出发,梳理方法语义、状态码、版本与分页约定,并盘点常见反模式。

核心概念:资源建模是核心

REST(Representational State Transfer,表现层状态转移)把系统能力抽象为一组资源。每个资源有唯一 URI,客户端与服务器之间交换资源的表现形式(通常是 JSON),通过标准 HTTP 方法完成状态变更。设计 API 时首先要回答的不是”有哪些动作”,而是”有哪些名词”:订单、用户、支付单这些稳定的业务实体才是 URI 的主角,动作交给 HTTP 方法表达。

建模有两条经验:一是 URI 使用名词复数,如 /orders、/users,单个资源通过 /orders/{id} 定位;二是用路径层级表达从属关系,如 /orders/{id}/items 表示某订单下的条目。层级不宜过深,超过三层通常意味着该拆成独立资源加查询参数,否则中间任何一环变动都会震动整条路径。

实践要点

用 HTTP 方法表达操作语义

方法 语义 幂等性
GET 读取资源,无副作用 幂等且安全
POST 创建子资源或触发非幂等操作 不幂等
PUT 整体替换指定资源 幂等
PATCH 局部修改 取决于实现,一般不保证
DELETE 删除资源 幂等

幂等(Idempotent)指同样的请求执行一次与执行多次效果相同,它直接决定调用方能否安全重试:GET/PUT/DELETE 失败后可以放心重发,POST 重发则可能创建重复订单,需要额外的幂等键(Idempotency-Key)配合去重。

状态码精确表达结果

  • 200:读取或更新成功;201:创建成功,并用 Location 头指向新资源;202:已受理、异步处理中。
  • 400:参数校验失败;401:未认证;403:已认证但无权限;404:资源不存在;409:状态冲突(如重复提交);429:触发限流。
  • 5xx 只留给服务器自身故障,业务校验失败绝不返回 500。

把所有失败都包装成 200 加 {"code": 500} 是典型反例——网关、监控、重试组件会因此全部失灵。

版本策略:URL 与 Header

不兼容变更必须开新版本。路径版本(/v1/orders)直观、易路由、浏览器可直接访问,是对外开放 API 的常见选择;请求头版本(Accept: application/vnd.example.v2+json)更贴近内容协商理念、URI 保持稳定,但调试与文档成本更高,多见于内部服务。无论选哪种,旧版本都要给出明确的下线窗口,避免版本无限堆积。

分页、过滤与排序约定

GET /orders?status=PAID&page=2&size=20&sort=-createdAt

过滤条件放查询参数;分页用 page/size,数据量大且实时性强的场景改用以游标(cursor)翻页更稳定;排序用 sort 加方向前缀。响应体里带上总数或 hasMore,方便客户端渲染与终止翻页。

常见误区

  • URI 里塞动词:/getOrderList、/deleteUser,把 RPC 习惯搬进 REST,方法语义形同虚设。
  • 把数据库表原样暴露成资源,表结构一变,API 跟着破窗。
  • 层级过深:/tenants/1/apps/2/users/3/orders/4,脆弱且难记。
  • 忽视幂等:支付类 POST 不做幂等键,一次网络重试就可能重复扣款。
  • 用 GET 做删除:GET /orders/1/delete,一个链接预取就能清光数据。

小结

  • 资源建模先于接口设计:先找名词、定层级,再让 HTTP 方法表达动作。
  • 幂等性是重试策略的地基,POST 需要幂等键补齐。
  • 状态码、版本、分页都是契约的一部分,全局统一约定比事后写文档更有效。