翻译:GraphQL:企业在蜜月期过后清醒了

39 阅读6分钟

以下是 GraphQL: the enterprise honeymoon is over 的中文翻译

GraphQL:企业在蜜月期过后清醒了

作者:John James
发布于:2025 年 12 月 14 日
阅读时间:约 3 分钟

我在一个真正的企业级应用中使用过 GraphQL,具体来说是 Apollo Client 和 Server,前后有两年时间。
这不是玩具项目,也不是从零开始的创业公司应用,而是一个真正投入生产的系统——涉及多个团队、BFF(面向前端的后端)、下游服务、可观测性要求,以及真实用户。

经过这么长时间的实战,我得出了一个相当“无聊”的结论:

GraphQL 确实解决了一个真实问题,但这个问题远比人们愿意承认的更加小众。在大多数企业架构中,这个问题其实已经通过其他方式解决了。而当你权衡所有利弊之后,GraphQL 往往反而成为一种净负面。

本文并不是要全盘否定 GraphQL,而是在“蜜月期”结束后,理性地看待它。


GraphQL 到底试图解决什么问题?

GraphQL 的主要目标是解决数据过载(overfetching)问题

其理念简单且吸引人:

  • 客户端只请求它真正需要的字段
  • 不多不少
  • 节省带宽
  • 无需为每个 UI 需求频繁修改后端接口

理论上,这很棒。但在实践中,事情远比想象复杂。


过载问题其实早已被 BFF 解决

大多数企业前端架构早已引入了 BFF(Backend for Frontend)
BFF 的存在目的正是:

  • 为 UI 定制数据结构
  • 聚合多个下游服务的调用
  • 隐藏后端复杂性
  • 仅返回 UI 所需的数据

如果你在 BFF 后面使用的是 REST,那么过载问题其实已经可控。BFF 完全可以裁剪响应,只返回前端关心的内容。

没错,GraphQL 也能做到这一点。但人们常常忽略一个关键事实:

大多数下游服务仍然是 REST 接口。

这意味着你的 GraphQL 层仍然需要从 REST 接口“过载”获取数据,然后重新裁剪。你并没有真正消除过载,只是把问题下移了一层。

仅此一点,就大幅削弱了 GraphQL 的核心卖点。

当然,GraphQL 在某些场景下确实有优势:比如多个页面调用同一个接口,但各自需要的字段略有不同。GraphQL 能针对每个查询精细指定字段。

但让我们坦诚面对代价:

你通常只是省下几个字段的数据量,却要付出:

  • 更复杂的搭建流程
  • 更多的抽象层
  • 更高的间接性
  • 更多需要维护的代码

为节省几 KB 数据而付出如此高昂的代价,真的值得吗?


实现成本远高于 REST

相比 REST BFF,GraphQL 的实现时间显著更长。

使用 REST 时,你通常只需要:

  • 调用下游服务
  • 适配响应格式
  • 返回 UI 所需数据

而使用 GraphQL,你却要:

  • 定义 Schema
  • 定义类型(Types)
  • 编写 Resolver
  • 配置 Data Source
  • 编写适配函数(本质上和 REST 一样)
  • 保持 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 根本不是为二进制数据设计的。

实践中,你通常只能:

  • 在 GraphQL 中返回一个下载 URL
  • 然后用 REST 去实际下载文件

如果强行把 PDF 等大文件嵌入 GraphQL 响应,会导致响应臃肿、性能下降。

这直接打破了“单一 API”的美好愿景。


上手成本更高

大多数前端和全栈开发者对 REST 的熟悉程度远高于 GraphQL。

引入 GraphQL 意味着团队要学习:

  • Schema 设计
  • Resolver 编写
  • 查询组合
  • 缓存规则
  • 错误语义

这条学习曲线会拖慢团队节奏,尤其在需要快速交付时。

REST 很无聊,但无聊的东西才能真正规模化。


错误处理比想象中复杂

GraphQL 的错误响应机制……很怪。

你得处理:

  • 可空 vs 不可空字段
  • 部分数据返回
  • errors 数组
  • extensions 中的自定义状态码
  • 追踪到底是哪个 Resolver 出了问题

所有这些都增加了间接性。

而 REST 的错误处理简单直接:

  • 输入校验失败?→ 400
  • 后端出错?→ 500
  • Zod 报错?→ 完事

简单的错误,比“优雅”的错误更容易理解和维护。


最终结论

GraphQL 当然有其适用场景。

但在大多数企业环境中:

  • 你已经有 BFF
  • 下游服务仍是 REST
  • 过载根本不是你的核心痛点
  • 可观测性、可靠性、开发速度才是关键

当你把所有因素加总起来,GraphQL 往往只是解决了一个狭窄问题,却引入了一整套新问题

因此,在多年生产环境实战后,我想说:

GraphQL 并不坏,它只是小众。而你,很可能根本不需要它。
尤其是当你的架构早已解决了它本想解决的问题时。