# REST 的四个成熟度等级:为什么你不需要 Level 3

面试造火箭(HATEOAS),入职拧螺丝(GET /users + POST /users)。这是大多数后端开发对 REST 的真实体验。

引言

REST 这个词被滥用得太厉害了。很多人觉得"URL 里用名词、返回 JSON"就是 RESTful------但这只能算 Level 2。真正的 REST(Fielding 博士论文里的那个)要求 HATEOAS,而它几乎从未在工业界大规模落地过。

这篇文章从 Richardson 成熟度模型出发,把 REST 的四个等级讲清楚,然后回到实际:你的项目到底需要做到哪一级?哪些场景 REST 根本不适用?


Richardson 成熟度模型:REST 的四级台阶

Leonard Richardson 在 2008 年提出了一个衡量 API RESTful 程度的模型,Martin Fowler 后来在博客中推广了它。

Level 0:单一 URI + 单一方法

http 复制代码
POST /api/service
Content-Type: application/json

{
  "action": "getUser",
  "params": { "id": 1 }
}

所有操作通过一个 URI 和一个 HTTP 方法(通常是 POST)完成。请求体里携带操作名和参数。这是 SOAP 时代的遗产------HTTP 被当成传输层,而不是应用层协议

Level 1:资源 URI

http 复制代码
GET /users/1
GET /users/1/orders

不同的资源有了不同的 URI,但还没有充分利用 HTTP 方法------可能增删改查全是 GET 或 POST。进步是有了资源概念,缺点是操作语义仍靠自定义参数传递。

Level 2:HTTP 动词(绝大多数项目的终点)

http 复制代码
GET    /users/1      # 查询
POST   /users         # 创建
PUT    /users/1      # 全量更新
PATCH  /users/1      # 部分更新
DELETE /users/1      # 删除

每个 HTTP 动词有明确的语义:GET 是安全且幂等的,PUT 是幂等的,POST 非幂等,DELETE 是幂等的。同时合理使用 HTTP 状态码:

状态码 语义 使用场景
200 OK GET/PUT/PATCH 成功
201 Created POST 创建资源成功
204 No Content DELETE 成功
400 Bad Request 参数校验失败
401 Unauthorized 未认证
403 Forbidden 无权限
404 Not Found 资源不存在
409 Conflict 资源冲突(如重复创建)
422 Unprocessable 语义错误
500 Internal Error 服务端异常

Level 2 是现代 Web API 的实际标准。Swagger/OpenAPI 对这个级别的支持最好,工具链也最成熟。

Level 3:HATEOAS(超媒体即应用状态引擎)

http 复制代码
GET /users/1
200 OK

{
  "id": 1,
  "name": "张三",
  "email": "zhangsan@example.com",
  "_links": {
    "self": { "href": "/users/1" },
    "orders": { "href": "/users/1/orders" },
    "deactivate": { "href": "/users/1/deactivate" },
    "department": { "href": "/departments/5" }
  }
}

服务端在响应中告诉客户端"基于当前资源状态,你可以做哪些操作"。理论优势是客户端和服务端解耦------服务端改 URL 结构,客户端无需更新,因为它总是从响应中动态发现链接。

Richardson 模型认为只有到达 Level 3 才算真正的 REST。但这个要求在工业界几乎从未被满足。


为什么 Level 3 在实践中失败了?

1. 前端复杂度急剧增加

传统调用:

javascript 复制代码
// 简单直接
const user = await fetch('/users/1');
const orders = await fetch(`/users/1/orders`);

HATEOAS 调用:

javascript 复制代码
// 需要解析、查找、匹配
const userResp = await fetch('/users/1');
const userData = await userResp.json();
const ordersLink = userData._links.find(l => l.rel === 'orders');
if (!ordersLink) throw new Error('No orders link available');
const orders = await fetch(ordersLink.href);

代码量翻倍,而且这些链接结构几乎不会变------你为"灵活性"付出了"复杂性",而灵活性从未被使用。

2. 移动端不友好

App 发布有审核周期,不能像 Web 页面那样热更新。HATEOAS 的核心价值"服务端随意改 URL"对移动端完全无效------App 里的 URL 都硬编码在代码里。

3. OpenAPI/Swagger 支持差

OpenAPI 3.0 引入了 links 字段支持有限的 HATEOAS 描述,但没有一个主流工具能自动从 HATEOAS 响应生成可用的文档。这意味着你实现了 HATEOAS,但反而失去了 API 文档的可读性。

4. 没有成功的商业案例

PayPal、GitHub、Stripe、Twilio------所有顶级 API 提供商都停留在 Level 2。没有一个公开的、大规模使用的 Level 3 API。这不是巧合。


你应该关心的:REST 设计中的务实选择

1. URL 设计:名词复数 + 层级不超过 2 层

bash 复制代码
✅ GET  /users              # 资源列表
✅ GET  /users/1            # 单个资源
✅ GET  /users/1/orders     # 子资源
✅ POST /users              # 创建
✅ PUT  /users/1            # 全量更新
✅ PATCH /users/1           # 部分更新
✅ DELETE /users/1          # 删除

❌ GET  /getUser?id=1       # 动词 + 查询参数
❌ POST /createUser         # 动词
❌ GET  /users/1/orders/2/items/3/comments  # 4 层嵌套

当嵌套超过 2 层,有两种解:

  • 独立资源:GET /comments?user_id=1
  • BFF 聚合:一个 /api/page/detail 聚合多个后端服务

2. 版本管理:URL 路径 vs Header

http 复制代码
# 方式 A:URL 路径(直观,调试方便)
GET /api/v1/users

# 方式 B:自定义 Header(不污染 URL)
GET /api/users
Accept: application/vnd.myapp.v1+json

两种都可以,关键是全团队统一。方式 A 在 API 网关做路由更方便,方式 B 更符合 HTTP 标准。如果有 API 网关,选 A;如果对外提供 SDK,选 B。

3. 分页、过滤、排序的标准化

http 复制代码
GET /users?page=2&per_page=20&sort=-created_at&role=admin

返回格式统一:

json 复制代码
{
  "data": [...],
  "meta": {
    "current_page": 2,
    "per_page": 20,
    "total": 156,
    "total_pages": 8
  },
  "links": {
    "first": "/users?page=1&per_page=20",
    "prev": "/users?page=1&per_page=20",
    "next": "/users?page=3&per_page=20",
    "last": "/users?page=8&per_page=20"
  }
}

注意这里的 links 只是分页辅助信息,不是 HATEOAS------你不需要从 links 中动态发现如何翻页,这只是给客户端提供便利的预计算 URL。

4. 错误响应格式统一

json 复制代码
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email format is invalid",
    "details": [
      { "field": "email", "reason": "invalid_format" }
    ]
  }
}

比返回 { "code": 500, "msg": "error" } 强一万倍------后者叫"把 HTTP 降级为传输层"。


REST 不是万能药:什么时候不该用 REST?

场景 推荐方案 原因
微服务内部调用 gRPC 强类型、高性能、代码生成
移动端首页聚合 GraphQL 或 BFF 一次请求获取多种数据
实时通信 WebSocket / SSE 双向推送,REST 做不到
文件上传 REST + 分片 POST /upload + multipart
操作类接口 RPC 风格 POST /orders/123/cancelPATCH /orders/123 语义更清晰

操作类接口用 RPC 风格不是罪过。 POST /orders/123/cancel 比"硬套 REST 改成 PATCH /orders/123 加自定义字段"要清晰得多。语义清晰 > 范式纯正。


总结:你到底需要做到哪一级?

项目类型 最低要求 推荐
个人项目 / MVP Level 1 Level 2(简单)
内部管理系统 Level 2 Level 2
对外开放 API Level 2 + 文档 Level 2 + OpenAPI
微服务间调用 --- gRPC
理论论文 Level 3 ---

Level 2 就是工业界的最佳实践。 如果有人跟你说"不用 HATEOAS 就不是 REST",你的回答是:"对,我做的就是 Level 2 的 HTTP API,不是 Roy Fielding 论文里的 REST------这是两个概念。"


参考链接:


如果这篇文章帮你理清了 REST 的务实设计思路,欢迎点赞、收藏。有不同观点评论区聊。

相关推荐
xianjixiance_5 小时前
HarmonyOS应用开发实战:萌宠日记 - json5-配置文件详解
后端
b130538100495 小时前
HarmonyOS应用开发实战:萌宠日记 - 应用启动流程与闪屏页面设计
后端
寒草5 小时前
「寒草呈献」工作六年,是否仍有创造未来的勇气 ✨
前端·后端
程序员爱钓鱼6 小时前
为什么学习 Go?Go 能做什么?
后端·面试·go
程序员爱钓鱼6 小时前
Rust 切片 Slice 详解:安全访问连续数据
前端·后端·rust
咖啡八杯14 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式
掘金码甲哥14 小时前
这块终端神器, 必须吹爆!
后端
糖果店的幽灵15 小时前
【DeepAgents 从入门到精通】Context Management 上下文管理
java·人工智能·后端·spring·中间件·langgraph·deepagents
Csvn15 小时前
📊 SQL 入门 Day 8:集合操作 — 用 SQL 做数学里的"并交差"
后端·sql
码事漫谈18 小时前
告别数据孤岛与AI“水土不服”:金仓多模融合时序库如何让数据真正服务于业务
后端