以下是 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 并不坏,它只是小众。而你,很可能根本不需要它。
尤其是当你的架构早已解决了它本想解决的问题时。