GraphQL:企业蜜月期已结束
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 并不坏,只是适用范围有限。如果你的架构已经解决了它要解决的问题,那么你很可能根本不需要它。