Appearance
REST 六大约束
本章导读:Fielding 论文的核心不是"用 URL 表示资源"这种口号,而是六条架构约束及其相互强化的关系。理解这六条约束,你就理解了"为什么 REST 系统天然适合 Web 规模",也能看懂本教程后续每个设计决策的动机。
1. 约束风格(Constraint Style)的思想
Fielding 采用"约束风格"设计架构:不直接规定组件怎么实现,而是叠加一组约束,观察整体涌现出的属性。REST 由六条约束组成(论文 5.1 节):
┌─────────────────────────┐
│ code-on-demand (可选) │ 按需下载代码(JS/小程序)
├─────────────────────────┤
│ layered system │ 中间层(网关/CDN/LB)透明介入
├─────────────────────────┤
│ caching │ 响应可标记可缓存
├─────────────────────────┤
│ stateless │ 每个请求自包含
├─────────────────────────┤
│ client-server │ 职责分离
├─────────────────────────┴─┐
│ uniform interface(统一接口)│ ← 基石,其余约束都建立在它之上
└───────────────────────────┘去掉任意一条约束,系统就"只是普通的 Web API";全部满足,才获得 REST 宣称的好处:性能、可扩展性、简单性、可修改性、可移植性、可靠性、网络效率。
2. 约束一:统一接口(Uniform Interface)
"将约束应用到接口的通用集合,而非针对单个应用的定制接口。"
统一接口是 REST 的基石,由四个子约束构成——每一条都直接对应 API 设计规范:
2.1 资源标识(Identification of resources)
请求中必须有办法唯一标识资源——这就是 URI(RFC 3986)。资源是抽象概念,与存储无关:"文章 #42" 和 "文章 #42 的草稿版本" 是两个资源。
2.2 通过表现操作资源(Manipulation of resources through representations)
客户端拿到的不是资源本身,而是资源的表现(JSON/HTML/图片字节流)。客户端若想修改资源,需提交一个新的完整表现给服务器,由服务器决定是否接受。
关键推论:客户端不需要理解服务器的存储结构(数据库表?对象存储?无所谓),它只需要会处理 JSON。
→ 对应 表示与媒体类型。
2.3 自描述消息(Self-descriptive messages)
每个报文自己携带足够信息描述自己:Content-Type: application/json 说明表现格式、Accept 说明期望格式、状态码说明结果、方法说明意图……无需查阅"第 3.2.1 节私有约定"。
2.4 超媒体即应用状态引擎(HATEOAS)
应用的当前状态与可执行操作,由服务器通过超媒体链接动态告知(详见理查森成熟度模型 Level 3)。这是工程落地中最常被舍弃的一条。
3. 约束二:客户端-服务器(Client-Server)
关注点分离:UI/用户体验归客户端,状态与数据管理归服务器。
对 API 设计的意义:
- 客户端与服务器独立演进(换前端技术栈不动后端,反之亦然)。
- 跨平台复用:同一 API 服务 Web、iOS、Android、第三方。
- 契约边界清晰:接口规范(OpenAPI)就是双方的"合同"。
4. 约束三:无状态(Stateless)
"任何请求之间不得保存客户端上下文。" —— Fielding 5.1
每个请求必须自包含所有必要信息:认证凭证、目标资源、操作意图、乐观锁版本(If-Match)。服务器内存/会话里不应保存"这个客户端上一步做了什么"。
收益:
- 服务器可水平扩容——任何一个实例都能处理任何一个请求(无需会话粘滞)。
- 可靠性提高——服务器重启不丢"会话状态"。
- 可观测性——日志里每个请求都是完整故事。
代价与规范: 每个请求都要携带完整信息(如 Bearer Token),带宽开销略增;"购物车""多步向导"这类真正的会话式业务流程,需要在资源层建模(把购物车本身做成资源 GET /carts/1),而不是在 HTTP 会话里藏状态。
→ 对应 认证与授权。
5. 约束四:可缓存(Caching)
表现必须自我定义是否可缓存,这样中间件(浏览器、CDN、网关)可以代答后续请求,减少往返。
实现依据是 RFC 9111:
http
GET /articles/42 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=600, s-maxage=3600
ETag: "b2f5-9a3f"规范要求:
- 私有数据(用户资料、订单)必须
Cache-Control: private或no-store,防止 CDN 串数据——这是真实事故高发区。 - 动态接口也应提供协商缓存(
ETag+304 Not Modified)节省带宽与计算。
→ 对应 缓存与条件请求。
6. 约束五:分层系统(Layered System)
客户端无法(也无需)分辨它连接的是终点服务器还是中间层(代理、网关、负载均衡、CDN)。每一层只对上层负责。
对 API 设计的意义:
- 你可以在不改客户端的情况下插入:认证网关、限流器、日志审计、响应压缩、灰度路由。
- 前提依然是"接口统一":中间层能仅凭方法 + URI + 头部做决策,不用解析私有 body——这是"别把动作藏在 body 里"的架构级理由。
7. 约束六:按需代码(Code-on-Demand,可选)
服务器可将可执行代码传输给客户端运行(浏览器里的 JS 就是典型)。这是 REST 中唯一可选的约束——API 设计阶段基本不用考虑它。
8. 六大约束如何互相强化
Fielding 强调约束是协同起作用的:
| 组合 | 涌现出的能力 |
|---|---|
| 无状态 + 统一接口 | 任何中间层可转发/处理任何请求 → 可扩展性 |
| 自描述 + 可缓存 | 中间层无需私有知识即可安全缓存 → 性能 |
| 分层 + 统一接口 | 安全、治理、监控能力可以"后补"到存量系统 → 可演进性 |
| 资源标识 + 表现操作 | 存储与接口解耦 → 可修改性 |
9. 本章小结
- 统一接口(四子约束)是 REST 的基石与灵魂;其余约束围绕它展开。
- 无状态 ≠ 服务器不存数据,而是不存客户端上下文;会话型流程应建模为资源。
- 缓存、分层不是"部署话题",它们反过来决定了你必须用标准方法/头部/状态码写接口。
- 后续每一章,你都会看到本章约束的具体投影。