本页目录

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)

{ "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient funds", "status": 409,
  "detail": "Balance 10 < required 50", "instance": "/accounts/42/transfer/abc",
  "errors": [ {"field": "amount", "message": "exceeds balance"} ] }

分页两种主流,按场景选:

Offset/limit  : ?offset=40&limit=20   实现简单, 可跳页; 深分页慢 + 翻页时数据漂移
Cursor(keyset): ?after=<opaque>&limit=20   稳定、高效, 适合无限滚动/大数据集; 不能跳页

HATEOAS

REST 的"满分"约束:响应里内嵌可用动作的链接⁠,客户端靠跟随链接驱动流程,而非把 URL 规则硬编码。

{ "id": 42, "state": "pending",
  "_links": { "self":   {"href": "/orders/42"},
              "cancel": {"href": "/orders/42/actions/cancel"},
              "pay":    {"href": "/orders/42/payment"} } }

理想上服务端能自由改 URL、用状态控制可用动作。现实中绝大多数 API 不做完整 HATEOAS——客户端通常仍按文档硬编码路径,收益配不上复杂度。但它的退化形态很有用:分页的 next 链接、资源里嵌子资源 URL,都是 HATEOAS 思想的实用残留。

OpenAPI / Swagger

OpenAPI(前身 Swagger)是描述 REST API 的机器可读规范(YAML/JSON),是 REST 工程化的事实标准:

paths:
  /users/{id}:
    get:
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses:
        '200': { content: { application/json: { schema: { $ref: '#/components/schemas/User' } } } }

价值在于一份 spec 派生整条工具链⁠:Swagger UI 交互文档、客户端/服务端 codegen、请求校验与 mock、契约测试。推荐 spec-first(先写契约再写实现),让前后端并行、契约即测试。

REST vs GraphQL vs gRPC

不是谁取代谁,而是各自的最佳区间:

RESTGraphQLgRPC
传输/编码HTTP/1.1+ · JSONHTTP · JSONHTTP/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