跳转到内容

Fielding 2000 — 用约束推导法把 Web 的成功讲成了一门方法

是什么

Roy Fielding 的 2000 年博士论文,是 REST(Representational State Transfer)这个名字第一次被完整定义的地方。但论文真正的贡献不是六条约束本身,而是它怎么推出这六条——一种叫做「约束推导」的方法。

日常类比:好比研究为什么一座桥能撑住,他不是先看建好的桥再倒推。他从「什么都不约束的空中楼阁」出发,一条一条加规矩,每加一条就观察「桥变结实了哪里、变笨重了哪里」。最后六条加完,刚好得到 Web 的样子。

这个方法叫 null style + incremental constraints——从空风格起,逐条施加。Fielding 在第 5 章用这个方法推出了 REST。

注:与本仓库另一篇 rest-fielding-2000 互补,那篇正面讲六条约束本身;本篇聚焦他是怎么推出来的这套方法论。

为什么重要

不抓住「方法」只记「结论」,会犯三个常见错:

  • 把 REST 当成 HTTP+JSON+四个动词,丢掉它能扩展性、可缓存、独立演化的根
  • 看到新协议(gRPC / GraphQL)只会喊「这是不是 REST」,不会问它放弃了哪条约束、换了什么能力
  • 自己设计 API 时,只会模仿现成的 RESTful 命名,不会基于场景反推该不该加某条约束

Fielding 留给后人的工具其实是「架构风格 = 一组约束的有序选择」。学会这个,才能解释为什么 Web 赢了,也才能在下一个 Web 出现时认得出来。

核心要点

三个抽象层(论文第 5.1 节)

REST 之前要先定义讨论的对象。Fielding 给出一套通用词表

  • data elements:资源(resource)、表示(representation)、标识符(identifier)、元数据
  • connectors:客户端、服务器、缓存、解析器、隧道——通信的中介本身被当一等公民
  • components:源服务器、网关、代理、用户代理——能被部署的实体

这三层把分布式系统拆成可以独立约束的部分。这是后面推导能进行的前提。

约束推导:从空风格到 REST(论文第 5.2 节)

Fielding 一条一条加:

  1. client-server:把 UI 从数据切开 → 客户端独立演化
  2. + stateless:服务器不记会话 → 任意机器都能接任意请求(水平扩展立刻成立)
  3. + cache:响应自己声明可缓存 → 同样的请求不必每次穿透
  4. + uniform interface:所有资源同一套语义(URI + GET/POST/PUT/DELETE)→ 一份客户端能接千万种服务
  5. + layered system:客户端不知道自己在和源站还是网关说话 → 中间能塞 CDN、鉴权、负载均衡
  6. + code-on-demand(可选):服务器可以下发可执行代码(JavaScript 就是这一条的产物)

每加一条,他都列得到了什么、付出了什么。比如「无状态」换来扩展性,但损失了「服务器记住你」的便利;统一接口换来通用客户端,但牺牲了为某场景特化的性能。

这种「约束有代价」的视角,是论文方法论上最值钱的一块。

Uniform Interface 再拆四小条

第 4 条还不够细。Fielding 把它进一步拆成:资源标识 / 通过表示操纵 / 自描述消息 / HATEOAS(超媒体作为应用状态引擎)。

HATEOAS 这条最常被忽略:响应里要附带「下一步能去哪」的链接,让客户端不必硬编码 URL 规则。这是 95% 自称 RESTful 的 API 实际上没做到的部分。

实践案例

案例 1:用约束推导法看 gRPC

gRPC 大致选择:保留 client-server、stateless、layered,但不要 uniform interface(用 protobuf 自定义方法名)、不要 cache(默认)不要 HATEOAS。代价:失去了通用客户端、CDN 友好、可独立演化;换来:强类型 + 高性能 + 双向流

这就是 Fielding 方法的用法——不是判 RESTful 真假,而是看它选了哪条、放了哪条

案例 2:HATEOAS 在真实世界

{
"id": 42, "status": "pending",
"_links": {
"approve": { "href": "/orders/42/approve" },
"cancel": { "href": "/orders/42/cancel" }
}
}

GitHub API、PayPal API 真的这样做。多数公司的内部 API 不做,这是个清醒的取舍——内部系统客户端少、变动少,硬编码 URL 成本可接受。

案例 3:边缘计算继承的是哪几条

CDN / 边缘网关能存在的根,是 REST 三条约束的叠加涌现:stateless(任何节点都能接)+ cache(响应自描述可缓存性)+ layered(客户端不感知中间层)。少一条,整个边缘生态都立不住。

举个具体的:当你访问 https://example.com/foo.png,请求可能根本没到源站,而是被 Cloudflare 边缘节点的本地缓存命中。这件事能成立的前提是——客户端不知道也不需要知道自己在跟谁说话(layered),响应自己写明了能缓存多久(cache),且服务器不依赖之前的会话(stateless)。三条任一缺失,CDN 都得改协议。

案例 4:判断一个 API 是不是「真 RESTful」

POST /api/getUser?id=42
POST /api/deleteUser

这是 RPC 套着 HTTP 皮。资源标识没了(动作放进了 URI),统一接口没了(动词全是 POST)。

GET /users/42
DELETE /users/42

这才是按 REST 设计:URI 是名词、动作放方法里、HTTP 语义被尊重。HATEOAS 还在更上面一层——响应里附带「下一步链接」才算完整。

踩过的坑

  1. 背六条约束 vs 理解推导:背下来没用,能在新场景**一条一条问「这条加不加」**才算学会。

  2. 以为统一接口=四个动词:原文从未说必须 GET/POST/PUT/DELETE,那只是 HTTP 的实现。统一接口是用同一套语义操纵不同资源,本质是资源 + 表示 + 自描述消息 + HATEOAS 四件套。

  3. 看摘要就觉得读完了:方法论藏在第 5 章推导步骤里,第 6 章用 HTTP/URI 验证。摘要只列结论。

  4. 把架构风格和架构混了:Fielding 在第 1 章明确分开——风格是约束集架构是某个具体实例。Web 是 REST 的一个实例,但不等于 REST。

  5. 以为「有状态就是错」:Fielding 没说不准记状态,他说的是「应用状态留在客户端」。资源状态(数据库里的订单)当然在服务器。混淆这两种状态是新手最常见的误读。

适用 vs 不适用场景

适用

  • 公开、长期演化、客户端类型多样的 web API
  • 需要 CDN / 网关 / 缓存层介入的高扇出系统
  • 想从原理推导自家 API 设计的团队

不适用

  • 强一致分布式事务 → 用专用协议(2PC / Saga)
  • 实时双向通信 → WebSocket / gRPC streaming
  • 内部低延迟同质 RPC → gRPC / Thrift 通常更合适
  • 查询形状高度可变 → GraphQL 解决得更直接

历史小故事(可跳过)

  • 1994:Fielding 加入 W3C 早期工作,参与 HTTP 设计
  • 1996:在 IETF 起草 HTTP/1.1 期间首次用 REST 一词描述背后哲学
  • 1999:HTTP/1.1(RFC 2616)发布,Fielding 是主作者之一
  • 2000:博士论文出版,REST 第一次完整出现在公开文献
  • 2008:Fielding 写博客 REST APIs must be hypertext-driven,公开抗议「你们这不是 REST」

REST 名字火了,方法论被遗忘——这是论文最大的讽刺。

学到什么

  1. 架构风格 = 一组约束的有序选择,不是某个框架,不是某种格式
  2. null style + 逐条加约束 是设计分布式系统的可推导方法,可以迁移到任何新协议设计
  3. 每条约束都有代价,没有「免费的最佳实践」,只有「针对场景的取舍」
  4. 论文层级:理论 → 标准 → 工程,与 hindley-milner 走的是同一条路径——先有方法论,再有标准,再有工业铺开

延伸阅读

关联

  • rest-fielding-2000 —— 互补视角:那篇讲六条约束本身,本篇讲怎么推出来
  • hindley-milner —— 都是「先做出能跑的东西,再回头方法化」的代表作
  • turing-1936 —— 计算可行性的祖师,REST 是分布式可行性的近代版
  • lambda-calculus —— 都用极少核心规则推导丰富后果