RESTful API 进阶
HATEOAS、缓存与 ETag、版本演进、以及与 GraphQL / gRPC 的取舍。
Key Points进阶不是把 REST 做得更纯,而是在缓存、演进和协议选择上做对取舍。
HATEOAS
Responses carry links describing what can be done next, so clients navigate without hard-coded URLs. Worth it for public APIs whose capabilities change.
JSON
{
"id": 42, "status": "pending",
"_links": {
"self": {"href": "/orders/42"},
"pay": {"href": "/orders/42/payment", "method": "POST"},
"cancel": {"href": "/orders/42/cancel", "method": "POST"}
}
}缓存与 ETag
Text
GET /users/42
→ 200 OK
ETag: "a1b2c3"
Cache-Control: private, max-age=60
GET /users/42
If-None-Match: "a1b2c3"
→ 304 Not Modified (不传 body,省带宽)Cache-Control: public只给不因用户而异的资源- 带用户信息的响应一律
private,否则会被 CDN 缓存串号 - 写操作后要能主动失效缓存(版本号或 purge)
版本演进
| Change | Breaking? | How |
|---|---|---|
| Add a field | No | Just ship it |
| Add an optional parameter | No | Default keeps old behaviour |
| Rename a field | Yes | Keep both for one release |
| Change a type | Yes | New version or new field |
| Remove an endpoint | Yes | Deprecate → 410 → remove |
REST vs GraphQL vs gRPC
| REST | GraphQL | gRPC | |
|---|---|---|---|
| Transport | HTTP/1.1 + JSON | HTTP POST + JSON | HTTP/2 + protobuf |
| Fetching | Fixed per endpoint | Client picks fields | Fixed per method |
| Caching | Native HTTP cache | Hard, usually POST | Custom |
| Best for | Public APIs, CRUD | Complex UIs, mobile | Internal microservices |
Do not swap internal CRUD for GraphQL because it sounds modern. The test: do clients really need to pick fields across many endpoints, and are round trips the bottleneck?
继续阅读
- Swagger / OpenAPI —— 把上面这些约定写成机器可读的文档
- HTTP 状态码 —— 逐个状态码的精确语义
- OAuth 2.0 —— 授权码、客户端凭证与 token 生命周期
HATEOAS
响应里带上「下一步能做什么」的链接,客户端不靠硬编码 URL 也能导航。适合对外 API 和能力经常变化的场景。
JSON
{
"id": 42, "status": "pending",
"_links": {
"self": {"href": "/orders/42"},
"pay": {"href": "/orders/42/payment", "method": "POST"},
"cancel": {"href": "/orders/42/cancel", "method": "POST"}
}
}缓存与 ETag
Text
GET /users/42
→ 200 OK
ETag: "a1b2c3"
Cache-Control: private, max-age=60
GET /users/42
If-None-Match: "a1b2c3"
→ 304 Not Modified (不传 body,省带宽)Cache-Control: public只给不因用户而异的资源- 带用户信息的响应一律
private,否则会被 CDN 缓存串号 - 写操作后要能主动失效缓存(版本号或 purge)
版本演进
| 变更 | 破坏性 | 做法 |
|---|---|---|
| 新增字段 | 否 | 直接上线 |
| 新增可选参数 | 否 | 默认值保持旧行为 |
| 重命名字段 | 是 | 双写一个版本周期 |
| 改类型 | 是 | 新版本或新字段 |
| 下线端点 | 是 | 先标记废弃 → 410 → 移除 |
REST vs GraphQL vs gRPC
| REST | GraphQL | gRPC | |
|---|---|---|---|
| 传输 | HTTP/1.1 + JSON | HTTP POST + JSON | HTTP/2 + protobuf |
| 取数 | 每端点固定 | 客户端挑字段 | 每方法固定 |
| 缓存 | HTTP 原生缓存 | 困难,通常是 POST | 需自建 |
| 适合 | 对外 API、CRUD | 复杂前端、移动端 | 内部微服务 |
不要为了「先进」把内部 CRUD 换成 GraphQL。判断标准是:客户端是否真的需要在一堆接口里挑字段,且网络往返次数成为瓶颈。
继续阅读
- Swagger / OpenAPI —— 把上面这些约定写成机器可读的文档
- HTTP 状态码 —— 逐个状态码的精确语义
- OAuth 2.0 —— 授权码、客户端凭证与 token 生命周期