Voocii博客
首页博客AI 热榜作品集读书友链工具关于
© 2026 Voocii. Built with Next.js & tRPC.
GitHubXEmailRSS


API 选择:REST 还是 GraphQL

技术RickRick2025年4月17日·1 次阅读

两种 API 范式解决的其实不是同一个问题——理解它们的分歧点,比记住语法差异更重要。

每当团队要为新项目选型 API 风格时,"REST 还是 GraphQL" 几乎是绕不开的争论。很多讨论止步于语法层面的对比——"REST 用多个端点,GraphQL 用一个端点"——但这掩盖了两者更本质的分歧:它们对"谁来决定返回什么数据"这件事,给出了完全相反的答案。

这篇文章会从设计哲学出发,逐层拆解两者在数据获取、类型系统、版本管理、缓存、性能和生态系统上的差异,并给出一个相对务实的选型框架。

1. 设计哲学的分歧

REST(Representational State Transfer)诞生于 2000 年 Roy Fielding 的博士论文,核心思想是把系统建模成"资源"的集合,每个资源有唯一的 URI,客户端通过标准的 HTTP 动词(GET / POST / PUT / DELETE)对资源进行操作。服务端决定每个端点返回什么形状的数据。

GraphQL 由 Facebook 在 2012 年内部孵化、2015 年开源,出发点是移动端在弱网环境下对"精确控制传输数据量"的强烈需求。它把 API 建模成一张类型图(Schema),客户端在请求中声明自己需要哪些字段,服务端按需返回。

rest-vs-graphql.svg

图 1:REST 中数据形状由服务端固定;GraphQL 中数据形状由客户端在查询中声明

这个分歧解释了后面几乎所有的具体差异:谁掌握"形状"的决定权,谁就承担相应的复杂度。REST 把复杂度留在了"如何设计出恰到好处的端点"上;GraphQL 把复杂度转移到了"如何设计一个既灵活又安全高效的 Schema 与解析层"上。

2. 数据获取:过度获取 vs 请求瀑布

REST 最常被诟病的两个问题是过度获取(over-fetching)和获取不足(under-fetching)。前者是端点返回了比客户端需要更多的字段;后者是客户端需要多次请求才能拼出完整的视图——比如先 GET /user/1 拿到用户信息,再用返回的 id 去 GET /user/1/posts,如果还要评论数据,可能还得再发一次请求,形成"请求瀑布"。

GraphQL 通过单一端点和声明式查询天然解决了这两个问题:一次请求即可精确取回嵌套的关联数据。但这不是没有代价的——服务端需要一个能高效解析任意查询形状的执行引擎,稍不注意就会在解析嵌套字段时触发经典的 N+1 查询问题(例如查询 100 篇文章的作者,如果没有做批处理,会对数据库发起 101 次查询)。业界通常用 DataLoader 之类的批处理/缓存层来缓解这个问题,但这本身就是 GraphQL 服务端需要额外掌握的一层复杂度。

REST 请求瀑布示例:

# 1. 获取用户
GET /api/users/1
# 2. 用返回的 id 获取文章列表
GET /api/users/1/posts
# 3. 对每篇文章再获取评论数(N 次请求)
GET /api/posts/101/comments
GET /api/posts/102/comments
# ...

GraphQL 单次声明式查询:

query {
  user(id: "1") {
    name
    posts {
      title
      comments {
        author
        text
      }
    }
  }
}

3. 类型系统与契约

GraphQL 的 Schema 本身就是强类型契约,配合工具链(如 GraphQL Code Generator)可以自动生成前端类型定义,减少前后端沟通成本,并让 IDE 具备字段级别的自动补全和查询校验能力。

REST 本身不强制类型系统,但生态里有 OpenAPI(Swagger)规范来弥补——通过一份 YAML/JSON 描述文件定义接口、参数和响应结构,同样可以生成客户端 SDK 和文档。区别在于:OpenAPI 是"外挂"的约定,需要额外维护并容易与实现脱节;GraphQL 的 Schema 则是运行时自省(introspection)的一部分,类型定义与实际执行逻辑天然同步。

4. 版本管理策略

REST:通常通过 URI 或请求头显式做版本管理,例如 /api/v1/users 和 /api/v2/users 并存。这种方式直观,但长期会积累多套并行维护的端点。

GraphQL:官方推荐"演进式 Schema"而非显式版本号:新增字段不影响旧查询,废弃字段用 @deprecated 标记并给出迁移期,而不是直接切断。这依赖持续的 Schema 治理纪律,否则 Schema 会随时间膨胀、积累大量僵尸字段。

5. 缓存机制

这是 REST 相对 GraphQL 的一个显著优势。REST 天然借助 HTTP 语义——GET 请求可以被浏览器、CDN、反向代理在 URL 层面缓存,ETag、Cache-Control 等标准头部开箱即用。

GraphQL 所有请求通常都走 POST 到同一个端点,URL 本身不再携带查询语义,标准 HTTP 缓存机制基本失效。客户端库(如 Apollo Client、Relay)转而在应用层维护基于对象 ID 的规范化缓存,服务端也常引入持久化查询(Persisted Queries)等方案来部分找回缓存能力,但整体复杂度明显更高。

6. 性能与安全考量

GraphQL 赋予客户端构造任意深度查询的自由,这也意味着一个恶意或不小心写出的深度嵌套/循环引用查询可能让服务端付出巨大的计算与数据库开销(所谓"查询炸弹")。因此生产环境的 GraphQL 服务通常需要额外实现查询深度限制、查询复杂度评分、请求速率限制等防护措施,这些在 REST 里因为端点和返回形状都是预先定义好的,天然就不存在。

提醒:如果团队决定采用 GraphQL,查询复杂度限制和深度限制不是"可选的加分项",而应该在上线第一天就配置好——这是很多团队踩过的坑。

7. 综合对比表

维度RESTGraphQL
数据获取精确度较弱,易过度/不足获取强,客户端按需声明
HTTP 缓存原生支持需应用层方案
类型系统需 OpenAPI 外挂内建 Schema
版本管理显式版本号演进式 Schema,需治理
学习曲线低,生态成熟中,需理解 Schema/Resolver
文件上传/流式传输原生支持需扩展规范
服务端复杂度较低较高(N+1、深度限制等)
多端/移动端适配常需 BFF 层天然适配不同客户端需求

8. 该怎么选?

  • 公开 API / 面向第三方开发者:REST 通常更合适——语义清晰、缓存友好、生态工具(Postman、curl 直接可用)门槛低,第三方接入成本更低。
  • 多端产品,尤其移动端且网络条件多变:GraphQL 的按需获取能显著减少流量和请求次数,是它最初被设计要解决的场景。
  • 数据模型复杂、前端视图组合多样(如中后台、聚合仪表盘):GraphQL 能避免为每种视图组合单独开发 REST 端点。
  • 团队规模小、维护资源有限:REST 的心智负担和运维复杂度更低,没有必要为了"理论上更优雅"而引入 GraphQL 服务端的额外基础设施(解析层、批处理、复杂度限制)。
  • 文件上传、Webhook、需要利用 HTTP 缓存的只读内容:这些场景 REST 依然是更自然的选择,即使整体架构以 GraphQL 为主,也常见混合使用。

一个务实的现实是:很多大型系统并非"二选一",而是在同一个后端上,对外的公开 API 保留 REST,内部面向前端聚合的层用 GraphQL 作为 BFF(Backend For Frontend),两者各自发挥所长。

结语

REST 和 GraphQL 不是"新旧替代"关系,而是两种针对不同约束条件做出的设计权衡。REST 把简单性和缓存友好性放在优先位置,代价是数据获取的灵活性;GraphQL 把数据获取的精确性和客户端自由度放在优先位置,代价是服务端实现和运维的复杂度。理解这个权衡背后的动机,比记住某个语法细节更能帮你在真实项目中做出合适的选择。

评论 (0)

暂无评论,快来抢沙发吧!

发表评论

目录
  • 1. 设计哲学的分歧
  • 2. 数据获取:过度获取 vs 请求瀑布
  • 3. 类型系统与契约
  • 4. 版本管理策略
  • 5. 缓存机制
  • 6. 性能与安全考量
  • 7. 综合对比表
  • 8. 该怎么选?
  • 结语