2019年1月31日

“Schema-First” GraphQL 服务器开发的问题

在过去两年中,GraphQL 服务器开发的工具呈爆炸式增长。我们认为,大多数工具的需求源于流行的 schema-first 方法,可以通过一种替代方案来解决:code-first。

The Problems of "Schema-First" GraphQL Server Development
(当前正在阅读)
“Schema-First” GraphQL 服务器开发的问题
介绍 GraphQL Nexus:Code-First GraphQL 服务器开发
将 GraphQL Nexus 与数据库一起使用

概述:从 schema-first 到 code-first

本文概述了 GraphQL 服务器开发领域的现状。以下是涵盖内容的快速提纲

  1. 本文中“schema-first”是什么意思?
  2. GraphQL 服务器开发的演变
  3. 分析 SDL-first 开发的问题
  4. 结论:SDL-first 可能可行,但需要大量工具
  5. Code-first:一种 GraphQL 服务器开发的语言惯用方式

虽然本文主要给出 JavaScript 生态系统的示例,但其中大部分内容也适用于其他语言生态系统中的 GraphQL 服务器开发。


本文中“schema-first”是什么意思?

术语 schema-first 相当模糊,通常传达一个非常积极的想法:使 schema 设计成为开发过程中的优先事项。

在实现 schema 之前考虑 schema(以及 API),通常会产生更好的 API 设计。如果 schema 设计不足,则可能导致 API 成为后端实现方式的结果,而忽略业务领域的原语和 API 消费者的需求。

在本文中,我们将讨论 GraphQL schema 首先手动在 SDL 中定义,然后实现解析器的开发过程的缺点。在这种方法中,SDL 是 API 的事实来源为了澄清 schema-first 设计与这种特定实现方法之间的区别,我们从现在开始将其称为 SDL-first

相比之下,code-first(有时也称为 resolver-first)是一个 GraphQL schema 以编程方式实现,而 schema 的 SDL 版本是其生成工件的过程。通过 code-first,您仍然可以非常关注前期 schema 设计!


GraphQL 服务器开发的演变

The evolution of GraphQL server development

阶段 1:graphql-js 的早期

GraphQL 于 2015 年发布时,工具生态系统稀缺。当时只有官方的规范及其 JavaScript 参考实现:graphql-js。直到今天,graphql-js 仍用于最流行的 GraphQL 服务器中,例如 apollo-serverexpress-graphqlgraphql-yoga

当使用 graphql-js 构建 GraphQL 服务器时,GraphQL schema 被定义为一个普通的 JavaScript 对象

从这些示例可以看出,使用 graphql-js 创建 GraphQL schema 的 API 非常冗长。schema 的 SDL 表示更加简洁易懂

这篇文章中了解更多关于使用 graphql-js 构建 GraphQL schema 的信息。

阶段 2:graphql-tools 普及了 Schema-first

为了简化开发并提高对实际 API 定义的可见性,Apollo 于 2016 年 3 月开始构建 graphql-tools 库(这里是第一次提交)。

目标是将 schema 定义与实际实现分开,这导致了当前流行的 schema 驱动schema-first / SDL-first 开发过程

  1. 手动在 GraphQL SDL 中编写 GraphQL schema 定义
  2. 实现所需的解析器函数

使用这种方法,上面的示例现在看起来像这样

这些代码片段与上面使用 graphql-js 的代码 100% 等效,只是它们更具可读性,更容易理解。

可读性不是 SDL-first 的唯一优势

  • 这种方法易于理解并且非常适合快速构建事物
  • 由于每个新的 API 操作都需要首先在 schema 定义中体现,因此GraphQL schema 设计不是事后诸葛亮
  • schema 定义可以作为API 文档
  • schema 定义可以作为前端和后端团队之间的沟通工具——前端开发人员获得了赋能并更多地参与到 API 设计中
  • schema 定义支持快速模拟 API

阶段 3:开发新工具以“修复”SDL-first

虽然 SDL-first 有许多优点,但过去两年表明将其扩展到大型项目具有挑战性。在更复杂的环境中会出现许多问题(我们将在下一节中详细讨论这些问题)。

这些问题本身确实大多可以解决——实际问题是解决它们需要使用(和学习)许多额外的工具。在过去两年中,发布了大量工具,试图改进围绕 SDL-first 开发的工作流程:从编辑器插件到 CLI 再到语言库。

学习、管理和集成所有这些工具的开销会降低开发人员的速度,并使他们难以跟上 GraphQL 生态系统的步伐。


分析 SDL-first 开发的问题

现在让我们深入探讨 SDL-first 开发中的问题领域。请注意,其中大多数问题特别适用于当前的 JavaScript 生态系统。

问题 1:schema 定义和解析器之间不一致

使用 SDL-first,schema 定义必须与解析器实现的精确结构匹配。这意味着开发人员需要确保 schema 定义始终与解析器保持同步!

虽然即使对于小型 schema 这也是一个挑战,但随着 schema 增长到数百或数千行(例如,GitHub GraphQL schema 有超过 10k 行),这实际上变得不可能。

工具/解决方案:有一些工具可以帮助保持 schema 定义和解析器同步。例如,通过使用 graphqlgengraphql-code-generator 等库进行代码生成。

问题 2:GraphQL schema 的模块化

在编写大型 GraphQL schema 时,您通常不希望所有 GraphQL 类型定义都存在于同一个文件中。相反,您希望将它们拆分为更小的部分(例如,根据特性产品)。



工具/解决方案: graphql-import 或最近的 graphql-modules 库等工具可以帮助解决这个问题。graphql-import 使用以 SDL 注释形式编写的自定义导入语法。graphql-modules 是一个工具集,用于帮助 schema 分离解析器组合以及 GraphQL 服务器的可扩展结构的实现。

问题 3:schema 定义中的冗余(代码重用)

另一个问题是如何重用 SDL 定义。这个问题的一个常见例子是 Relay 风格的连接。虽然它们提供了一种强大的实现分页的方法,但它们需要大量的样板代码和重复代码。

目前还没有工具可以解决这个问题。开发人员可以编写自定义工具来减少重复代码的需求,但目前缺乏通用的解决方案。

问题 4:IDE 支持和开发者体验

GraphQL schema 基于强大的类型系统,这在开发过程中可能是一个巨大的优势,因为它允许对您的代码进行静态分析。不幸的是,SDL 通常在您的程序中表示为纯字符串,这意味着工具无法识别其中的任何结构。

那么问题就变成了如何在编辑器工作流程中利用 GraphQL 类型,以受益于自动完成和 SDL 代码的构建时错误检查等功能。

工具/解决方案: graphql-tag 库公开了 gql 函数,它将 GraphQL 字符串转换为 AST,从而实现静态分析和由此产生的功能。除此之外,还有各种编辑器插件,例如 VS Code 的 GraphQLApollo GraphQL 插件。

问题 5:组合 GraphQL schema

schema 模块化的想法也引出了另一个问题:如何将多个现有(和分布式)schema 组合成一个单一的 schema。

工具/解决方案: schema 组合最流行的方法是 schema stitching,它也是前面提到的 graphql-tools 库的一部分。为了更好地控制 schema 的具体组合方式,您还可以直接使用 schema delegation(它是 schema stitching 的一个子集)。

结论:SDL-first 可能可行,但需要大量工具

在探讨了问题领域和为解决这些问题而开发的各种工具之后,SDL-first 开发似乎最终可能奏效——但也需要开发人员学习和使用大量额外的工具。



变通方法,变通方法,变通方法,...

在 Prisma,我们在推动 GraphQL 生态系统向前发展方面发挥了重要作用。许多提及的工具都是由我们的工程师和社区成员构建的。

Workarounds cartoon

经过数月的开发以及与 GraphQL 社区的密切互动,我们意识到我们只是在解决症状。这就像与九头蛇作战——解决一个问题会导致几个新问题。

生态系统锁定:投入整个工具链

我们非常感谢我们在 Apollo 的朋友们的工作,他们不断致力于改进围绕 SDL-first 开发的开发工作流程。

另一个以 SDL-first 方式构建 GraphQL 服务器的流行例子是 AWS AppSync。它与 Apollo 模型有所不同,因为解析器(通常)不是通过编程实现的,而是从 schema 定义自动生成的。

虽然社区从如此多的工具中受益匪浅,但当开发人员需要完全依赖某个组织的工具链时,存在生态系统锁定的风险。真正的解决方案可能是在 GraphQL 核心本身中融入许多 SDL-first 的观点——这在可预见的未来不太可能发生。

SDL-first 忽略了编程语言的个体特征

SDL-first 的另一个问题是它忽略了编程语言的个体特性,通过施加类似的原则,无论使用哪种编程语言。

Code-first 方法在其他语言中运行得非常好:Scala 库 sangria-graphql 利用 Scala 强大的类型系统优雅地构建 GraphQL schema,graphlq-ruby 利用了 Ruby 语言的许多出色 DSL 特性。


Code-first:GraphQL 服务器开发的语言惯用方式

您唯一需要的工具是您的编程语言

大多数 SDL-first 问题源于我们需要将手动编写的 SDL schema 映射到编程语言这一事实。这种映射导致了对额外工具的需求。如果我们遵循 SDL-first 路径,那么所需的工具将需要为每个语言生态系统重新发明,并且看起来也各不相同。

我们不应该通过增加工具来增加 GraphQL 服务器开发的复杂性,而应该努力实现更简单的开发模型。理想情况下,一个让开发人员能够利用他们已经使用的编程语言的模型——这就是code-first的理念。

Code-first 到底是什么?

还记得最初在 graphql-js 中定义 schema 的例子吗?这就是 code-first 的精髓。没有手动维护的 schema 定义版本,相反,SDL 是从实现 schema 的代码中生成的。

尽管 graphql-js 的 API 非常冗长,但其他语言中也有许多流行的框架基于 code-first 方法,例如前面提到的 graphlq-ruby sangria-graphql,以及 Python 的 graphene 或 Elixir 的 absinthe-graphql



Code-first 实践

虽然本文主要是关于理解 SDL-first 的问题,但这里有一个小小的预告,展示了使用 code-first 框架构建 GraphQL schema 的样子


通过这种方法,您可以直接在 TypeScript/JavaScript 中定义 GraphQL 类型。通过正确的设置和智能代码完成,您的编辑器将能够在您定义 GraphQL 类型、字段和参数时提供建议。

典型编辑器工作流程包括在后台运行的开发服务器,该服务器在文件保存时重新生成类型。

一旦所有 GraphQL 类型都已定义,它们将被传递到一个函数中,以创建 GraphQLSchema 实例,该实例可在您的 GraphQL 服务器中使用。通过指定 outputs,您可以定义生成的 SDL 和类型应位于何处。

本系列文章的后续部分将更详细地讨论 code-first 开发。

获得 SDL-first 的优势,而无需所有工具

前面我们列举了 SDL-first 开发的优点。事实上,在使用 code-first 方法时,无需在大多数优点上做出妥协。

将 GraphQL schema 作为前端和后端团队之间关键沟通工具的最重要好处仍然存在。

以 GitHub GraphQL API 为例:GitHub 使用 Ruby 和 code-first 方法实现其 API。SDL schema 定义是根据实现 API 的代码生成的。然而,schema 定义仍然被签入版本控制。这使得在开发过程中跟踪 API 的更改变得异常容易,并改善了不同团队之间的沟通。

API 文档或赋能前端开发人员等其他好处也不会因为 code-first 方法而丢失。

Code-first 框架,即将登陆您的 IDE

本文相当理论化,代码不多——我们仍然希望我们能激起您对 code-first 开发的兴趣。要查看更多实践示例并了解更多关于 code-first 开发体验的信息,请继续关注 Prisma Twitter 账户,敬请期待 👀

您对这篇文章有什么看法?加入 Prisma Slack 与其他 GraphQL 爱好者讨论 SDL-first 和 code-first 开发。


🙏 非常感谢 SashkoApollo 团队对文章的反馈!

不要错过下一篇文章!

订阅 Prisma 新闻通讯

© . This site is unofficial and not affiliated with Prisma Data, Inc.