本页目录
REST 与 API 设计
REST 不是规范,是一组架构约束(HATEOAS、无状态、统一接口)。GraphQL 和 gRPC 各自在不同的约束维度上对 REST 做了取舍——选哪个取决于"谁来决定响应形状"和"性能敏感度"。
概述
REST 由 Roy Fielding 在 2000 年博士论文中提出——它不是协议而是一种架构风格,主张把系统状态建模成"资源",用 HTTP 既有的方法语义(GET/POST/PUT/DELETE)和状态码去操作它们,而不是发明一套自定义 RPC。二十多年后,REST 仍是 Web API 的默认范式(GitHub/Stripe/Twilio),而 GraphQL(Facebook 2015)和 gRPC(Google 2016)在"客户端字段需求多变"和"内部高性能 RPC"这两个 REST 不擅长的场景里补位。好 API 的难点从来不是选哪种风格,而是一致性:可预测的 URL、正确的方法/状态码语义、统一的错误与分页、清晰的版本与兼容策略。
REST 六大约束
1. Client-Server : UI 与数据分离, 各自独立演进
2. Stateless : 每个请求自带全部 session 上下文 → 服务器不存会话 → 易水平扩展
3. Cacheable : 响应显式声明可缓存性 (Cache-Control / ETag)
4. Uniform Interface : 统一的资源标识与操作语义 (见下方方法表)
5. Layered System : client 不感知后端分层 (CDN / LB / API gateway)
6. Code on Demand : (可选) 服务器可下发可执行代码扩展 client
Stateless 是其中最有现实后果的一条:它让任意一台无状态实例都能处理任意请求,是 REST 能轻松上 CDN、负载均衡、自动扩缩的根因——代价是认证态要塞进每个请求(Bearer token),这也正是 JWT 流行的土壤。
方法语义、状态码与幂等性
幂等性(重复执行结果不变)直接决定客户端能不能安全重试——网络超时后能否盲重发,全看方法是否幂等。
方法 语义 安全 幂等 典型成功码
GET 读取 是 是 200
POST 创建/动作 否 否 201 (+ Location) / 200
PUT 整体替换 否 是 200 / 204
PATCH 部分更新 否 否* 200
DELETE 删除 否 是 204 (再删返回 404 也算幂等结果)
(* PATCH 是否幂等取决于 body 语义; JSON Merge Patch 通常幂等, JSON Patch 的数组操作不是)
状态码别只会 200/500:
201 Created / 202 Accepted(异步) / 204 No Content
400 参数错 · 401 未认证 · 403 已认证但无权 · 404 不存在
409 冲突 · 422 语义校验失败 · 429 限流(配 Retry-After)
500 服务端错 · 503 暂不可用
并发控制:用
ETag+If-Match做乐观锁——客户端带上读到的版本号,服务端版本变了就返回412 Precondition Failed,避免"后写覆盖先写"。
资源建模与 URL 设计
Collection GET /users 列表(过滤/排序/分页)
Create POST /users 201 + Location: /users/42
Single GET /users/42
Replace PUT /users/42
Patch PATCH /users/42
Delete DELETE /users/42 204
Sub-resource GET /users/42/orders
Action POST /users/42/actions/reset-password 非 CRUD 动作(显式标注)
反模式:
GET /getUser?id=42 URL 里塞动词 (HTTP 方法本身就是动词)
POST /createUser 冗余 (POST 即创建)
GET /users?action=delete 危险 (GET 必须 safe; 删除该用 DELETE)
错误与分页(被低估的设计)
错误格式要机器可读、全站统一。业界趋同到 RFC 9457 (Problem Details):
分页两种主流,按场景选:
Offset/limit : ?offset=40&limit=20 实现简单, 可跳页; 深分页慢 + 翻页时数据漂移
Cursor(keyset): ?after=<opaque>&limit=20 稳定、高效, 适合无限滚动/大数据集; 不能跳页
HATEOAS
REST 的"满分"约束:响应里内嵌可用动作的链接,客户端靠跟随链接驱动流程,而非把 URL 规则硬编码。
理想上服务端能自由改 URL、用状态控制可用动作。现实中绝大多数 API 不做完整 HATEOAS——客户端通常仍按文档硬编码路径,收益配不上复杂度。但它的退化形态很有用:分页的 next 链接、资源里嵌子资源 URL,都是 HATEOAS 思想的实用残留。
OpenAPI / Swagger
OpenAPI(前身 Swagger)是描述 REST API 的机器可读规范(YAML/JSON),是 REST 工程化的事实标准:
paths:
/users/{id}:
get:
parameters:
responses:
'200':
价值在于一份 spec 派生整条工具链:Swagger UI 交互文档、客户端/服务端 codegen、请求校验与 mock、契约测试。推荐 spec-first(先写契约再写实现),让前后端并行、契约即测试。
REST vs GraphQL vs gRPC
不是谁取代谁,而是各自的最佳区间:
| REST | GraphQL | gRPC | |
|---|---|---|---|
| 传输/编码 | HTTP/1.1+ · JSON | HTTP · JSON | HTTP/2 · Protobuf(二进制) |
| 取数模型 | 每资源一端点 | 单端点, 客户端声明字段 | 强类型 RPC 方法 |
| 解决的痛点 | 通用、可缓存 | over/under-fetching、字段多变 | 高性能、流式、严格契约 |
| 缓存 | HTTP 原生(URL+ETag) | 难(单 POST 端点) | 自管 |
| 浏览器直连 | 原生 | 原生 | 需 gRPC-web 经代理转换 |
| 典型场景 | 公共 API、CRUD | 富前端/移动聚合多源 | 内部微服务、低延迟、双向流 |
gRPC-web 的存在原因:浏览器无法直接发原生 gRPC(拿不到底层 HTTP/2 帧控制),需 Envoy 之类代理在 gRPC 与浏览器可用的编码之间转换。所以 gRPC 多用于服务间内网,对外仍常包一层 REST/GraphQL 网关。
版本策略
URL path /api/v1/users 最常用: 直观、缓存友好、易路由 ← 推荐
Header Accept: application/vnd.api+json; version=1 更"纯", 但调试/缓存麻烦
Query /api/users?version=1
比"放哪"更重要的是兼容纪律:加字段是兼容变更,可不升版本;删/改字段语义、改必填、改类型是破坏性变更,必须升大版本并给旧版本明确弃用期(Deprecation / Sunset 头)。
参考
- REST: Roy Fielding's dissertation (2000), Chapter 5
- OpenAPI: openapis.org · Swagger Editor · 错误格式 RFC 9457
- GraphQL: graphql.org/learn · Apollo 文档
- gRPC: grpc.io · grpc-web (github.com/grpc/grpc-web)
Keywords: REST, statelessness, idempotency, ETag, RFC 9457 Problem Details, cursor pagination, HATEOAS, OpenAPI, Swagger, GraphQL, over-fetching, gRPC, gRPC-web, Protobuf, API versioning