GraphQL:企业蜜月期已结束

发布: (2025年12月15日 GMT+8 01:13)
9 min read

Source: Hacker News

By John James
Published on December 14, 2025
Read time ~3 min

我在真实的企业级应用中使用了 GraphQL,尤其是 Apollo Client 和 Server,已经有好几年了。不是玩具项目,也不是新创公司。是一个拥有多个团队、BFF、下游服务、可观测性需求以及真实用户的正式生产环境。

经过这么长时间的使用,我得出了一个相当无聊的结论:

GraphQL 解决了真实的问题,但这个问题比人们承认的要小得多。在大多数企业环境中,这个问题已经在别处得到了解决,而当你把所有权衡加在一起时,GraphQL 往往会成为净负面。

这不是一篇“GraphQL 很糟糕”的文章,而是“一篇 GraphQL 蜜月期结束后的感想”。

GraphQL 本应解决的痛点

GraphQL 试图解决的主要问题是 过度获取(overfetching)。其理念简单且诱人:

  • 客户端只请求它真正需要的字段
  • 不多也不少
  • 没有浪费的字节
  • 每次 UI 需求变化都不需要后端改动

纸面上看,这很棒。但在实际操作中,情况要复杂得多。

过度获取已经被 BFF 解决

大多数企业前端架构已经拥有 BFF(Backend for Frontend)。BFF 的存在正是为了:

  • 为 UI 整形数据
  • 聚合多个下游调用
  • 隐藏后端复杂性
  • 返回 UI 正好需要的内容

如果你在 BFF 后面使用 REST,过度获取已经可以解决。BFF 可以对响应进行裁剪,只返回 UI 所关心的部分。

是的,GraphQL 也可以做到这些。但大多数下游服务仍然是 REST,所以你的 GraphQL 层仍然需要从这些 API 中过度获取,然后再重新组织响应。你并没有消除过度获取,只是把它搬到了下一层。这一点就大大削弱了 GraphQL 的核心卖点。

有一种情况 GraphQL 能占优势:如果多个页面访问同一端点但需要的字段略有不同,GraphQL 允许你在每个查询中对差异进行裁剪。但这通常只为每次请求节省几百字节,却要付出:

  • 更多的搭建工作
  • 更多的抽象层
  • 更多的间接性
  • 更多需要维护的代码

这对几 KB 的节省来说代价极高。

实现时间远高于 REST

实现 GraphQL 所需的时间显著长于实现一个 REST BFF。

REST 工作流

  • 调用下游服务
  • 适配响应
  • 返回 UI 需要的内容

GraphQL 工作流

  • 定义 schema
  • 定义类型(type)
  • 定义解析器(resolver)
  • 定义数据源
  • 仍然要编写适配函数
  • 保持 schema、resolver 与客户端同步

GraphQL 用提升消费体验的代价换取了生产速度的下降。在企业环境中,生产速度往往比理论上的优雅更重要。

可观测性默认更差

GraphQL 使用一种古怪的状态码约定:

  • 400 表示查询无法解析
  • 200 并带有 errors 数组表示执行过程中出现错误
  • 200 表示成功或部分成功
  • 500 表示服务器不可达

从可观测性的角度来看,这非常痛苦。对比 REST:

  • 2XX 表示成功
  • 4XX 表示客户端错误
  • 5XX 表示服务器错误

如果你在仪表盘里按 2XX 过滤,就能知道这些请求成功了。而在 GraphQL 中,200 仍可能意味着部分或全部失败。Apollo 允许自定义这种行为,但这会带来额外的配置、约定以及认知负担——而 REST 开箱即用。

缓存听起来很棒,实际却很头疼

Apollo 的归一化缓存在理论上很吸引人,但在实践中相当脆弱。如果两个查询只在一个字段上不同,Apollo 会把它们视为两个独立查询,迫使你手动处理:

  • 已有字段从缓存中读取
  • 仅差异字段需要再次请求

结果是:

  • 仍然需要一次往返
  • 增加代码复杂度
  • 调试缓存问题本身就成了一个新问题

与此同时,REST 可以毫不介意多获取几个字段,直接缓存完整响应,然后继续前进。几 KB 的额外流量成本低,复杂度却高得多。

ID 要求是一个泄漏的抽象

Apollo 默认要求每个对象都有 id_id 字段,或者你必须配置自定义标识符。许多企业 API:

  • 不返回 ID
  • 缺少自然唯一键
  • 并未建模为全局可标识的实体

于是 BFF 必须在本地生成 ID 只为满足 GraphQL 客户端,这会增加更多逻辑、更多字段,最终不可避免地多获取一个字段——这与最初想要减少过度获取的目标恰恰相反。REST 客户端并没有这种限制。

文件上传与下载很尴尬

GraphQL 并不适合二进制数据。实际使用中你会:

  • 返回一个下载 URL
  • 然后仍然使用 REST 去获取文件

如果把大文件(比如 PDF)直接嵌入 GraphQL 响应,会导致响应体臃肿、性能下降,破坏“一站式 API”的叙事。

入职培训更慢

大多数前端和全栈开发者对 REST 的经验远超 GraphQL。引入 GraphQL 意味着要教授:

  • schema
  • resolver
  • 查询组合
  • 缓存规则
  • 错误语义

这条学习曲线会产生摩擦,尤其在团队需要快速迭代时。REST 虽然“无聊”,但它的无聊恰恰是极佳的可扩展性。

错误处理比想象中更麻烦

GraphQL 的错误响应…相当怪异。你会面对:

  • 可空 vs 非可空字段
  • 部分数据返回
  • errors 数组
  • 带有自定义状态码的 extensions
  • 必须追踪到底是哪一个 resolver 失败以及原因

所有这些都增加了间接性。相比之下,一个简单的 REST 设置只需要:

  • 输入校验失败 → 400
  • 后端错误 → 500
  • 校验库错误 → 完事

简单的错误更容易推理,而不是那些“优雅”的错误结构。

综合结论

GraphQL 确实有其合理的使用场景。但在大多数企业环境中:

  • 你已经有 BFF
  • 下游服务是 REST
  • 过度获取并不是最大的痛点
  • 可观测性、可靠性和交付速度更为关键

把所有因素加在一起,GraphQL 往往只解决了一个狭窄的问题,却引入了一系列更广泛的新问题。这也是为什么在多年生产使用后,我会说:

GraphQL 并不坏,只是适用范围有限。如果你的架构已经解决了它要解决的问题,那么你很可能根本不需要它。

另见

Back to Blog

相关文章

阅读更多 »

GraphQL:企业蜜月期结束

抱歉,我无法查看或提取图片中的内容。请您提供需要翻译的文字摘要,我会为您翻译成简体中文。