最佳实践:简介
GraphQL 最佳实践
GraphQL 规范特意忽略了一些面向 API 的重要问题,例如处理网络、授权和分页。这并不意味着在使用 GraphQL 时没有针对这些问题的解决方案,只是因为它们并非 GraphQL 定义中的一部分,而应当由工程实践来解决。
本章节中的文章并非不可改动的真理,在某些情况下使用其他方式可能会更加合适。其中的一些文章介绍了 Facebook 在设计和部署 GraphQL 服务的过程中形成的一些开发理念,另一些文章则针对常见问题(如提供 HTTP 服务和执行授权)提出了更多策略建议。
以下是对 GraphQL 服务的一些常见最佳实践和主观立场的简要概述,本章节中的后续文章将对这些主题进行更深入的讨论。
HTTP
GraphQL 通常通过单入口提供 HTTP 服务的完整能力,这与 REST API 不同——后者通常暴露一组 URL,而每个 URL 只暴露一个资源。虽然 GraphQL 也可以使用多个资源 URL,但这可能使您在使用 GraphiQL 等工具时遇到困难。
了解更多:提供 HTTP 服务。
JSON(使用 GZIP 压缩)
GraphQL 服务通常返回 JSON 格式的数据,但 GraphQL 规范 并未要求这一点。对于追求网络性能的 API 层来说,选择 JSON 似乎有些奇怪,但由于 JSON 主要是文本,经过 GZIP 压缩后表现非常好。
建议任何生产环境下的 GraphQL 服务都启用 GZIP,并建议客户端在请求头中加入:
Accept-Encoding: gzip
客户端开发者与 API 开发者对 JSON 也非常熟悉,它易于阅读和调试。事实上,GraphQL 语法部分地受到 JSON 语法的启发。
版本控制
虽然 GraphQL 服务完全可以像其他 REST API 一样进行版本控制,但 GraphQL 强烈主张通过持续演进 schema 来避免版本控制。
为什么大多数 API 需要版本控制?当 API 端点返回的数据被限制时,任何更改都可能被视为破坏性变更,而破坏性变更需要发布新版本。如果向 API 添加新功能就必须发布新版本,那么就需要在频繁发布、拥有大量增量版本与保持 API 的可理解性和可维护性之间进行权衡。
相比之下,GraphQL 只返回客户端显式请求的数据,因此可以通过添加新类型以及这些类型上的新字段来扩展功能,而不会造成破坏性变更。这催生了一种通用实践:始终避免破坏性变更,提供无版本的 API。
可以为空的性质
大多数支持 “null” 的类型系统都会提供普通类型和对应的可空版本,默认情况下类型不包含 “null”,除非显式声明。而 GraphQL 类型系统恰恰相反,默认情况下每个字段都可以为空。这是因为在由数据库和其他服务支撑的网络服务中,很多事情都可能出错:数据库可能宕机、异步操作可能失败、异常可能被抛出。除了系统故障,授权也常常是细粒度的,请求中的不同字段可能具有不同的授权规则。
通过将每个字段默认设为可空,上述任何原因都只会导致该字段返回 “null”,而不会让整个请求失败。作为替代方案,GraphQL 提供了 non-null 变体类型,保证字段在查询时永远不会返回 “null”;相反,如果发生错误,其父级字段将变为 “null”。
在设计 GraphQL schema 时,请务必考虑:在可能出错的情况下,“null” 是否是该字段合理的失败返回值。通常答案是肯定的,但偶尔并非如此。此时,请使用非空类型来提供保证。
分页
GraphQL 类型系统允许某些字段返回 值的列表,但如何为长列表分页则交由 API 设计者自行实现。分页的 API 设计方案有多种,各有权衡。
通常情况下,当字段返回长列表时,可以通过 “first” 和 “after” 参数指定列表的特定范围,其中 “after” 是列表中每个值的唯一标识符。
这种需求最终催生了一种名为 “Connections” 的最佳实践模式,为 API 设计提供了功能丰富的分页能力。一些 GraphQL 客户端工具(如 Relay)采用了 Connections 模式,当 GraphQL API 使用该模式时,客户端可以自动获得分页支持。
了解更多:分页。
服务器端的批处理与缓存
GraphQL 的设计使您可以在服务器上编写整洁的代码:每种类型的每个字段都有一个专门且唯一的解析函数。但如果设计不够完善,一个过于简单的 GraphQL 服务可能会频繁地反复从数据库加载数据。
这个问题通常可以通过批处理技术解决:在短时间内收集后端的多个数据请求,然后借助 Facebook 的 DataLoader 等工具,将它们合并为单个请求发送给底层数据库或微服务。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
