← Blog
·11 blog.minutes

API 设计:REST vs GraphQL vs gRPC——如何选择

REST、GraphQL 和 gRPC 各解决不同的问题。本指南比较了它们的设计理念、权衡和理想用例,让你能够为你的下一个 API 做出明智的决策。

每个现代应用都通过 API 与其他应用通信。问题不再是是否要构建 API,而是构建哪种风格的 API。REST 作为默认选择已经超过十年。GraphQL 作为对 REST 不灵活性的回应而出现。gRPC 采用了一种完全不同的方法,基于 HTTP/2 和协议缓冲区。在它们之间做出选择不仅需要理解语法,还需要理解每种方法所代表的基础设计理念。

本指南从设计角度分解每种协议:你如何组织资源或模式、客户端如何与你的 API 交互、你如何处理版本控制和分页,以及实时需求如何影响决策。最后,你将拥有一个框架来为特定约束选择正确的工具。

REST:Web 理解的面向资源设计

REST——表述性状态转移——不是一种协议。它是一种由 Roy Fielding 在 2000 年博士论文中定义的架构风格。核心思想是你将领域建模为资源,每个资源由一个 URL 标识,你通过一组统一的 HTTP 动词来操作这些资源:GET、POST、PUT、PATCH 和 DELETE。

良好的 REST API 设计是面向资源的设计。你不是围绕动作设计端点——你是围绕名词设计端点。不是 /createUser 或 /getUserById,而是设计 POST /users 和 GET /users/:id。这看起来是一个小区别,但它深刻影响了 API 的扩展方式、文档方式以及客户端学习使用它的方式。

GET /users/42
Accept: application/json

Response 200:
{
  "id": 42,
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "role": "admin",
  "createdAt": "2025-11-14T09:00:00Z"
}

REST 的美在于其简单性。URL 标识资源,HTTP 方法标识操作,响应体包含表述。每种语言的每个 HTTP 客户端都已经理解这个契约。缓存通过 HTTP 头部开箱即用。像 OpenAPI(前身为 Swagger)这样的工具让你从单个模式文件生成文档、客户端 SDK 和服务器桩代码。

当客户端需要不同形状的数据时,REST 会遇到问题。移动客户端可能只需要用户的名称和角色,而仪表盘客户端需要带有嵌套组织数据的完整资料。使用 REST,你要么构建多个端点,要么强制每个客户端下载完整表述并丢弃不需要的部分。这就是过度获取问题,也是 GraphQL 的主要动机。

GraphQL:让客户端精确描述它需要什么

GraphQL 由 Meta 开发并于 2015 年发布,采取了相反的方法。不是由服务器定义固定的响应,而是客户端发送一个精确描述它想要的数据的查询,服务器返回精确的那一形状。一个端点,任何响应形状。

GraphQL 模式定义了领域中的类型和关系。客户端针对这个模式组合查询,只请求它需要的字段。REST 中嵌套资源的概念变成了 GraphQL 查询中的嵌套字段,但客户端控制深度和选择。

// GraphQL schema (SDL)
type User {
  id: ID!
  name: String!
  email: String!
  role: Role!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  body: String!
}

enum Role { USER ADMIN MODERATOR }

type Query {
  user(id: ID!): User
}

一个只需要用户名称和帖子标题的客户端发送一个查询,只要求这些精确字段:

// Client query
query {
  user(id: 42) {
    name
    posts {
      title
    }
  }
}

// Response
{
  "data": {
    "user": {
      "name": "Ada Lovelace",
      "posts": [
        { "title": "Notes on the Analytical Engine" }
      ]
    }
  }
}

GraphQL 中的关键设计原则是模式即契约。模式定义了什么是可能的,客户端从可用选项中决定要请求什么。这消除了过度获取,减少了网络请求的数量(单个查询可以替代多个 REST 往返),并赋予前端团队独立于后端变更的能力,因为新的客户端需求并不总是需要新的端点。

GraphQL 引入了它自己的复杂性。查询性能更难预测,因为客户端控制加载的内容。N+1 问题——其中解析嵌套字段会触发对每个父记录的独立数据库查询——需要像 DataLoader 这样的批处理解决方案。HTTP 级别的缓存不起作用,因为每个查询都命中同一个 POST 端点,所以应用级别的缓存策略是必要的。而解析器、变更、订阅和输入类型的学习曲线比 REST 直接的 URL-动词模型更陡峭。

GraphQL 让前端团队独立于后端变更。但这种独立伴随着理解你的客户端发送给服务器的每个查询的性能特征的责任。

gRPC:基于 HTTP/2 和 protobuf 的高性能 RPC

gRPC 最初由 Google 开发,又采用了另一种方法。gRPC 不是资源和查询,而是将 API 建模为远程过程调用——你定义一个带有方法的服务,客户端像调用本地函数一样调用这些方法。关键的技术区别在于 gRPC 使用 Protocol Buffers(protobuf)进行序列化而不是 JSON,使用 HTTP/2 进行传输而不是 HTTP/1.1。

syntax = "proto3";

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc StreamUserUpdates (StreamRequest) returns (stream UserUpdate);
}

message GetUserRequest {
  int32 id = 1;
}

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  Role role = 4;
  string created_at = 5;
}

enum Role { ADMIN = 0; USER = 1; }

message StreamRequest {}

message UserUpdate {
  User user = 1;
  string event_type = 2;
}

Protobuf 序列化为紧凑的二进制格式,比 JSON 小得多且解析更快。结合 HTTP/2 的多路复用——在单个连接上多路复用多个流——gRPC 在高容量通信中实现了比 REST 或 GraphQL 显著更低的延迟和更高的吞吐量。这使得 gRPC 成为微服务间通信的主导选择,因为每一毫秒的序列化开销和每一个字节的传输都很重要。

gRPC 还原生支持四种流式传输:一元(一个请求,一个响应)、服务端流式(一个请求,响应流)、客户端流式(请求流,一个响应)和双向流式(双方同时流式传输)。这使其成为三种协议中实时 API 的最强选项。

权衡在于 gRPC 在浏览器中使用更难。浏览器客户端不能直接访问 gRPC 服务,因为它们缺乏对 HTTP/2 帧的细粒度控制。gRPC-Web 作为一个变通方案存在,但增加了复杂性。Protobuf 消息在传输中不可人工阅读,这使得调试更加困难——你需要像 grpcurl 或反射服务这样的工具来检查流量。API 文档的生态系统不如 OpenAPI 或 GraphQL 的内省系统成熟。

各协议在实际考量上的比较

在 REST、GraphQL 和 gRPC 之间选择很少是一个纯粹的技术决策。正确的选择取决于你的客户端是谁、他们需要什么类型的数据以及你的网络和基础设施的约束。以下是每种协议在生产中最重要的实际考量上的比较。

API 版本控制

REST 通过 URL 或 Accept 头部处理版本控制。/v1/users 和 /v2/users 可以无限期共存,让客户端按自己的进度迁移。这很简单且经过实战检验,但它鼓励重复——v2 端点通常复制了 v1 的大部分逻辑只做了少量更改。GraphQL 通过演进模式完全避免了版本控制。弃用的字段用 @deprecated 指令标记并保留在模式中,直到每个客户端都已迁移。这在你可以控制所有客户端时工作良好,但在客户端更新缓慢的公共 API 中可能引起摩擦。gRPC 的 protobuf 模式天生向前兼容——你可以添加字段而不会破坏现有客户端,弃用的字段在迁移期后被移除。这是三种策略中最优雅的版本控制策略,但它需要纪律,永远不要在没有弃用周期的情况下重命名或移除字段。

分页模式

REST 分页通常使用基于游标的分页(带 nextCursor 字段)或基于偏移量的分页(带 page 和 limit 参数)进行。设计原则是响应包含下一个链接或令牌,客户端跟随它。GraphQL 分页使用 Relay Connection 规范,它将列表包裹在带有游标和页面信息的边缘中——比临时 REST 方法更冗长但更一致。gRPC 分页在请求和响应消息中手动处理,通常使用 page_token 和 page_size 字段。没有标准,但 protobuf 模式使契约显式化。

  • REST:推荐生产 API 使用基于游标的分页,在响应中返回 nextCursor
  • GraphQL:Relay Connection 规范提供了标准化的分页模型,包含 hasNextPage、hasPreviousPage 和游标
  • gRPC:分页在你的 proto 消息中定义;常见的模式是在请求中包含 page_token 和 page_size,在响应中包含 next_page_token

认证和授权

所有三种协议都依赖相同的底层传输安全。REST 通常在 Authorization 头部使用 Bearer 令牌。GraphQL 遵循相同的模式——因为所有请求都通过一个 POST 端点发送,令牌在 GraphQL 层或中间件中验证。gRPC 使用拦截器将认证元数据附加到每个调用,通常以在 gRPC 元数据头中携带的 JWT 令牌的形式。三种协议都没有规定特定的认证方法——OAuth2、API 密钥或基于会话的认证的选择与 API 风格正交。

限流

限流在 REST 中最简单,因为每个端点代表一个有界操作。你计算每个客户端每个端点的请求,并在超过限制时返回 429 Too Many Requests。GraphQL 使限流更加困难,因为一个查询可能触发截然不同的工作量——查询一个字段可能花费一次数据库查找,而带有深层嵌套关系的查询可能花费五十次。GraphQL API 通常实现基于成本的限流,其中每个字段有一个权重,总查询成本在执行前计算。gRPC 限流类似于 REST,但在方法级别操作——你计算每个方法每个客户端的 RPC 调用,附加要考虑流式调用在其持续时间内消耗资源,而不仅仅是其启动时。

实时 API 和流式传输

REST 可以通过 WebSocket 或服务端推送事件(SSE)处理实时更新,但两者都不是 REST 规范的一部分——它们是附加在 HTTP API 旁边的独立协议。GraphQL 有订阅,这是规范的一等部分。客户端订阅一个事件,并在事件发生时通过 WebSocket 连接接收更新。gRPC 拥有最强的实时能力,具有其原生的基于 HTTP/2 的双向流式传输。单个 gRPC 调用可以同时在两个方向流式传输数据,这对于实时仪表盘、聊天应用和事件驱动系统是理想的。如果实时通信是一个主要需求,gRPC 的流式传输是最成熟和性能最优的选择,其次是 GraphQL 订阅,再其次是 REST 加 WebSocket。

文档和开发者体验

REST 拥有最强的文档生态系统,这要归功于 OpenAPI(前身为 Swagger)。OpenAPI 规范用机器可读的 YAML 或 JSON 文件描述每个端点、请求参数、响应模式和认证方法。像 Swagger UI 这样的工具将其渲染成交互式文档页面,代码生成器为数十种语言生成客户端 SDK。这个生态系统的成熟意味着一个新开发者可以在几分钟内从阅读 OpenAPI 规范到做出他们的第一个成功的 API 调用。

GraphQL 有内省——一个内置系统,你可以通过特殊端点查询 API 的模式。像 GraphiQL 和 Apollo Studio Explorer 这样的工具让开发者浏览模式、使用自动补全编写查询并内联查看文档。这对于已经知道你的 API 存在的开发者来说是一个很好的体验,但它在搜索引擎可发现性或外部 API 市场方面不如 OpenAPI 规范有帮助。

gRPC 依赖 proto 文件作为其唯一真实来源。proto 文件既是模式也是文档。像 protoc-gen-doc 这样的工具从 proto 文件生成参考文档,gRPC 反射让客户端动态发现服务。gRPC 的开发者体验正在改进,但在工具成熟度上仍落后于 REST 和 GraphQL,尤其是在微服务生态系统之外。

何时使用每一种

当你的 API 被外部开发者消费、当你需要最大的互操作性、以及当你的客户端需要标准的 HTTP 缓存时,REST 是正确的选择。它是最安全的默认选择,因为每种语言、每个框架和每个代理都理解 HTTP。如果你在构建一个公共 API,从 REST 开始。

当你的 API 有多个具有不同数据需求的客户端类型、当前端团队需要独立于后端变更而工作、以及当你想要减少网络请求的数量时,GraphQL 是正确的选择。它在面向消费者的应用中表现出色,其中移动和 Web 客户端需要相同数据的不同形状。

对于内部微服务通信、对于序列化开销很重要的高吞吐量系统、以及对于实时流式传输应用,gRPC 是正确的选择。如果你在构建一个两端都在你控制之下且性能是首要关注的系统,gRPC 是最强的选择。

这些协议都不是互斥的。一个常见的生产架构在防火墙后使用 gRPC 进行服务间通信,暴露一个聚合多个 gRPC 服务数据的 GraphQL 网关,并为外部客户端提供 REST API。问题不是在何处使用哪种协议——而是为你的系统中每个接口使用哪种协议。理解每种协议背后的设计理念给了你正确做出该选择的判断力。