API 设计的哲学:为什么 REST 和 GraphQL 都不是答案
> "工具本身从来不是问题的答案,理解问题才是。" —— 未署名的架构师
引言:一场永无止境的争论
在技术社区里,关于 API 设计的争论从未停止。REST 的支持者会告诉你:"遵循 RESTful 原则,你的 API 就会优雅、可扩展。" GraphQL 的拥趸则反驳:"单一端点、精确查询、类型安全,这才是现代 API 的未来。"
但如果我告诉你,这场争论本身就是一个错误的问题?
真正的问题不是"应该选择 REST 还是 GraphQL",而是我们为什么需要在两种范式之间做非此即彼的选择?更深层次的问题是:我们是否真正理解了 API 设计背后的本质问题?
REST 的谎言:资源导向的美丽陷阱
REST(Representational State Transfer)在 2000 年由 Roy Fielding 提出时,是一个革命性的概念。它将 Web 服务抽象为"资源"的集合,通过标准的 HTTP 方法(GET、POST、PUT、DELETE)进行操作。听起来很完美,对吧?
但现实是:大多数声称自己是 RESTful 的 API,其实只是 HTTP-based API。
问题一:真实世界不是资源的简单集合
想象一下这个场景:你需要设计一个 API 来处理"用户下单购买商品"的业务逻辑。按照 REST 的思路,这是一个什么操作?
-
- 创建一个订单资源?(
POST /orders) - 更新商品库存?(
PUT /products/{id}) - 更新用户的购买历史?(
PUT /users/{id}/purchase-history)
- 创建一个订单资源?(
你很快会发现,这个"简单"的业务操作,在 REST 的世界里被拆分成了多个资源操作。更糟糕的是,这些操作之间有复杂的依赖关系和事务性要求。如果其中一个失败了,你该如何回滚?
真实的业务逻辑往往是行为驱动的,而不是资源驱动的。强行将其套入资源模型,就像试图用螺丝刀拧钉子——技术上可行,但并不合适。
问题二:过度获取与不足获取的困境
REST 的另一个经典问题是:你无法精确地告诉服务器你需要什么。
假设你有一个 /users/{id} 端点,返回用户信息。对于移动端列表页,你只需要用户名和头像;但对于详情页,你需要用户的完整信息、发布的文章、关注者列表等。你会怎么做?
-
- 方案 A:创建多个端点(
/users/{id}/brief、/users/{id}/full) - 方案 B:使用查询参数(
/users/{id}?fields=name,avatar) - 方案 C:让客户端调用多个端点拼装数据
- 方案 A:创建多个端点(
每一种方案都有明显的缺陷。方案 A 导致端点爆炸;方案 B 让 API 变得复杂且难以维护;方案 C 则带来性能问题和网络开销。
这不是实现问题,而是 REST 范式本身的局限性。
问题三:版本管理的噩梦
当你的 API 需要演进时,REST 的版本管理就成了一个棘手的问题。常见的做法包括:
-
- URL 版本:
/v1/users、/v2/users - Header 版本:
Accept: application/vnd.myapi.v2+json - 参数版本:
/users?version=2
- URL 版本:
但无论哪种方式,你都面临一个根本性问题:版本边界在哪里?是整个 API 一起升版本,还是每个端点独立升版本?如果是后者,版本组合的复杂度会呈指数增长。
GraphQL 的幻觉:灵活性的代价
GraphQL 的出现,确实解决了 REST 的一些痛点。单一端点、精确查询、强类型系统——这些特性让它在 2015 年开源后迅速走红。
但 GraphQL 带来了新的、更隐蔽的问题。
问题一:查询复杂度的黑洞
GraphQL 的核心卖点是"客户端可以精确指定需要什么数据"。但这个优势的反面是:客户端可以构造任意复杂的查询。
考虑这个看似无害的查询:
query {
users {
posts {
comments {
author {
posts {
comments {
author {
# 无限嵌套...
}
}
}
}
}
}
}
}
这是一个合法的 GraphQL 查询,但它可能导致:
-
- 数据库的N+1 查询问题爆炸
- 服务器 CPU 和内存资源耗尽
- 响应时间从毫秒级跳到分钟级
你需要实现查询深度限制、复杂度计算、速率限制等机制。但这些机制本身又引入了新的复杂度:如何定义"合理"的查询复杂度?
问题二:缓存的噩梦
REST 的一个巨大优势是:它天然契合 HTTP 缓存机制。每个 URL 代表一个资源,可以用 ETag、Cache-Control 等标准 HTTP 头进行缓存。
GraphQL 呢?所有查询都发往同一个 POST 端点。传统的 HTTP 缓存机制完全失效。你需要:
-
- 实现客户端缓存(如 Apollo Client 的标准化缓存)
- 在服务器端维护查询缓存
- 处理缓存失效的复杂逻辑
你用灵活性交换了简单性。
问题三:权限控制的碎片化
在 REST 中,权限控制相对直观:每个端点可以有自己的权限规则。但在 GraphQL 中,权限控制必须下沉到字段级别。
这意味着:
-
- 权限逻辑散布在 resolver 函数中
- 难以全局审查和管理权限规则
- 容易出现权限漏洞(某个 resolver 忘记检查权限)
更糟糕的是,客户端可能请求了 100 个字段,但其中 3 个字段没有权限。你该如何处理?返回部分数据?抛出错误?这些决策都没有标准答案。
真正的问题:我们在解决什么?
REST 和 GraphQL 的支持者都犯了同一个错误:他们把技术范式当成了答案,而不是工具。
真正重要的问题是:
- 你的 API 的主要使用场景是什么?
- 公共 API vs. 内部服务?
- 移动端 vs. Web 端 vs. 服务间调用?
- 高频简单查询 vs. 低频复杂查询?
- 你的团队的技术栈和能力如何?
- 团队对 REST/GraphQL 的熟悉程度?
- 现有的基础设施支持?
- 运维和监控能力?
- 你的性能和可扩展性要求是什么?
- QPS 目标是多少?
- 可接受的延迟范围?
- 数据规模有多大?
- 你的 API 的生命周期是怎样的?
- 预期会频繁演进,还是相对稳定?
- 有多少客户端,它们的升级能力如何?
第三条路:混合与务实
在实际的生产环境中,最成功的 API 策略往往不是纯 REST 或纯 GraphQL,而是混合式、务实式的设计。
案例一:GitHub API
GitHub 同时提供 REST API 和 GraphQL API:
-
- REST API:用于简单的 CRUD 操作,如获取仓库信息、创建 issue
- GraphQL API:用于复杂的查询场景,如获取用户的贡献图谱
这不是犹豫不决,而是为不同场景选择合适的工具。
案例二:Netflix
Netflix 的 API 架构更为激进:他们使用了一个名为 Falcor 的内部框架,它既不是 REST 也不是 GraphQL,而是基于JSON Graph的概念:
-
- 客户端可以用类似 GraphQL 的语法查询数据
- 但底层是高度优化的缓存和批处理机制
- 针对视频流媒体的特定场景深度定制
没有完美的通用解决方案,只有适合特定问题的最优解。
案例三:Shopify
Shopify 的演进路径很有启发性:
-
- 早期:纯 REST API
- 2016 年:引入 GraphQL API
- 现在:REST API 用于简单操作和第三方集成,GraphQL 用于 Shopify Admin 等复杂界面
关键点:他们没有废弃 REST,而是让两者共存,各取所长。
设计原则:超越范式的思考
如果不拘泥于 REST 或 GraphQL,我们应该遵循什么样的 API 设计原则?
原则一:以用例为中心,而非以技术为中心
不要先选择技术,再把业务逻辑塞进去。而是:
- 明确核心用例(前 5 个最重要的 API 调用场景是什么?)
- 为这些用例设计最优的接口
- 再考虑用什么技术实现
例如,如果你的核心用例是"获取用户 feed 流",那么:
-
- 单独设计一个
/feed端点可能比试图用 REST 的/posts?user=...&filter=...更合适 - 或者用 GraphQL 的
query { feed { ... } }更灵活
- 单独设计一个
关键是用例驱动,而非范式驱动。
原则二:拥抱约束,而非追求完美的灵活性
GraphQL 的一个诱惑是"无限灵活性"。但在实际中,约束往往比灵活性更有价值:
-
- 明确的端点数量(REST)让 API 更容易理解和监控
- 预定义的查询模板(GraphQL Persisted Queries)提高安全性和性能
- 渐进式披露(Progressive Disclosure):先提供简单接口,再暴露高级功能
原则三:优化常见路径,容忍边缘情况
帕累托原则(80/20 法则)在 API 设计中同样适用:
-
- 识别出占 80% 流量的 20% 核心接口
- 为这些核心接口投入 80% 的优化精力
- 边缘情况可以用"不够优雅"但有效的方式处理
例如:
-
- 核心列表查询用优化过的 REST 端点
- 复杂的管理后台查询用 GraphQL
- 批量操作用专门的批处理端点
原则四:可演进性高于初始完美性
API 的最大成本不是初始开发,而是长期演进和维护。好的 API 设计应该:
-
- 预留扩展点(extension points)
- 使用语义化的版本策略
- 提供清晰的废弃和迁移路径
- 文档和实现同步演进
这意味着:
-
- 不要过度设计初始版本
- 但要为未来变化留下空间
- 用真实的使用反馈驱动演进
结论:成为工具的主人,而非奴隶
REST 和 GraphQL 都不是答案,因为它们不是答案,而是工具。真正的答案是:
- 深刻理解你要解决的问题
- 根据具体场景选择或组合工具
- 持续迭代和优化
最糟糕的决策不是选择了 REST 或 GraphQL,而是:
-
- 盲目跟风,因为"大厂都在用 GraphQL"
- 教条主义,坚持"必须 100% RESTful"
- 拒绝演进,认为"一旦选定就不能改"
好的架构师不是坚持某种范式,而是知道何时打破规则。
下次当团队再次陷入"REST vs. GraphQL"的争论时,不妨停下来问自己:
-
- 我们真正要解决的问题是什么?
- 这个问题的核心约束是什么?
- 什么样的接口设计最适合我们的用户和团队?
也许你会发现,答案既不是纯 REST,也不是纯 GraphQL,而是一个为你的特定场景量身定制的混合方案。
而这,才是真正的 API 设计艺术。
思考题:
- 回顾你参与过的项目,有多少 API 设计决策是基于"大家都这么做"而非实际需求分析?
- 如果让你重新设计当前项目的核心 API,在不受现有技术栈限制的情况下,你会如何设计?
- 你的团队是否有明确的 API 设计原则文档?如果没有,是否应该建立一个?
延伸阅读:
-
- Roy Fielding 的博士论文:"Architectural Styles and the Design of Network-based Software Architectures"
- GraphQL 官方文档的 Best Practices 章节
- Martin Fowler 的 "Richardson Maturity Model" - 理解 REST 的成熟度层次
添加新评论