理解 GraphQL schema stitching(第二部分)

在上一篇文章中,我们讨论了远程(可执行)schema 的来龙去脉。这些远程 schema 是一组被称为schema stitching的工具和技术的基础。
Schema stitching 是 GraphQL 社区中一个全新的主题。一般来说,它指的是组合和连接多个 GraphQL schema(或schema 定义)以创建一个单一的 GraphQL API 的行为。
schema stitching 中有两个主要概念
- Schema 委托:schema 委托的核心思想是将特定解析器的调用转发(委托)到另一个解析器。本质上,schema 定义的各个字段正在被“重新连接”。
- Schema 合并:Schema 合并是创建两个(或更多)现有 GraphQL API 的并集的想法。如果涉及的 schema 完全不相交,则这没有问题;如果它们不相交,则需要一种方法来解决它们的命名冲突。
请注意,在大多数情况下,委托和合并实际上会一起使用,我们最终会得到一个同时使用两者的混合方法。在本系列文章中,我们将分别介绍它们,以确保每个概念都能被很好地理解。
示例:构建自定义 GitHub API
让我们从一个基于公共GitHub GraphQL API的示例开始。假设我们想要构建一个小应用程序,提供关于Prisma GitHub 组织的信息。
我们应用程序需要的 API 应该公开以下功能
- 检索关于 Prisma 组织的信息(例如其ID、电子邮件地址、头像 URL 或置顶仓库)
- 按名称检索 Prisma 组织的仓库列表
- 检索关于应用程序本身的简短描述
让我们探索Query
类型,来自GitHub 的 GraphQL schema 定义,看看我们如何将我们的需求映射到 schema 的根字段。
需求 1:检索关于 Graphcool 组织的信息
第一个功能,检索关于 Prisma 组织的信息,可以通过使用Query
类型上的repositoryOwner
根字段来实现
我们可以发送以下查询来询问关于 Prisma 组织的信息
当我们提供"prismagraphql"
作为login
给repositoryOwner
字段时,它会工作。
这里的一个问题是我们无法以直接的方式询问email
,因为RepositoryOwner
只是一个没有email
字段的接口。但是,由于我们知道 Prisma 组织的具体类型确实是Organization
,我们可以通过在查询中使用内联片段来解决这个问题
好的,所以这会工作,但我们已经遇到了一些摩擦点,这些摩擦点不允许为了我们的应用程序的目的而直接使用 GitHub GraphQL API。
理想情况下,我们的 API 应该只公开一个根字段,允许直接询问我们想要的信息,而无需在每次查询时提供参数,并允许我们直接询问Organization
上的字段
需求 2:按名称检索 Graphcool 仓库列表
第二个需求,按名称检索 Graphcool 仓库列表怎么样。再次查看Query
类型,这变得更加复杂。API 不允许直接检索仓库列表——相反,你可以通过使用以下根字段提供owner
和仓库的name
来请求单个仓库
这是一个对应的查询
但是,我们实际为我们的应用程序想要的是(为了避免进行多次请求)一个如下所示的根字段
需求 3:检索关于应用程序本身的简短描述
我们的 API 应该能够返回一个描述我们应用程序的句子,例如"This app provides information about the Prisma GitHub organization"
。
这当然是一个完全自定义的需求,我们无法基于 GitHub API 来满足——但很明显,我们需要自己实现它,可能使用一个简单的Query
根字段,像这样
定义应用程序 schema
我们现在了解了我们的 API 的所需功能以及我们需要为 schema 定义的理想Query
类型
显然,这个 schema 定义本身是不完整的:它缺少Organization
和Repository
类型的定义。解决这个问题的一种直接方法是手动复制和粘贴 GitHub schema 定义中的定义。
这种方法很快变得繁琐,因为这些类型定义本身依赖于 schema 中的其他类型(例如,Repository
类型有一个类型为codeOfconduct
的CodeOfConduct
字段),你也需要手动复制过来。这种依赖链深入 schema 的深度没有限制,你甚至可能最终手动复制完整的 schema 定义。
请注意,当手动复制类型时,有三种方法可以做到这一点
- 整个类型都被复制过来,没有添加额外的字段
- 整个类型都被复制过来,并且添加了额外的字段(或现有字段被重命名)
- 只复制了类型字段的子集
简单地复制完整类型的第一种方法是最直接的。这可以使用graphql-import
自动化,如下一节所述。
如果向类型定义添加了额外的字段或现有字段被重命名,你需要确保实现相应的解析器,因为底层 API 当然无法处理解析这些新字段。
最后,你可能会决定只复制类型字段的子集。如果你不想公开类型的所有字段,这可能是理想的(底层 schema 可能在User
类型上有一个password
字段,你不想在你的应用程序 schema 中公开它)。
导入 GraphQL 类型定义
graphql-import
包通过让你在不同的.graphql
文件中共享类型定义,从而将你从手动工作中解放出来。你可以像这样从另一个 GraphQL schema 定义中导入类型
在你的 JavaScript 代码中,你现在可以使用importSchema
函数,它将为你解析依赖项,确保你的 schema 定义是完整的。
实现 API
有了上面的 schema 定义,我们只完成了一半。仍然缺少的是 schema 的实现,以解析器函数的形式。
如果你此时感到困惑,请务必阅读这篇文章,它介绍了 GraphQL schema 的基本机制和内部工作原理。
让我们考虑一下如何实现这些解析器!第一个版本可能如下所示
info
的解析器是微不足道的,我们可以返回一个描述我们应用程序的简单字符串。但是如何处理prismagraphql
和prismagraphqlRepositories
的解析器,我们需要从 GitHub GraphQL API 返回信息?
在这里实现此功能的幼稚方法是查看info
参数以检索传入查询的选择集——然后从头开始构建另一个 GraphQL 查询,该查询具有相同的选择集并将其发送到 GitHub API。这甚至可以通过为 GitHub GraphQL API 创建一个远程 schema来促进,但总的来说仍然是一个非常冗长和繁琐的过程。
这正是schema 委托发挥作用的地方!我们之前看到 GitHub 的 schema 公开了两个根字段,它们(在某种程度上)满足了我们的需求:repositoryOwner 和 repository。我们现在可以利用它来节省创建全新查询的工作,而是转发传入的查询。
委托给其他 schema
因此,与其尝试构建一个全新的查询,我们不如简单地获取传入的查询并将其执行委托给另一个 schema。我们将用于此的 API 称为delegateToSchema
,由graphql-tools
提供。
delegateToSchema
接收七个参数(按以下顺序)
schema
:GraphQLSchema的可执行实例(这是我们想要将执行委托给的目标 schema)fragmentReplacements
:包含内联片段的对象(这适用于更高级的情况,我们将在本文中不讨论)operation
:一个字符串,包含三个值中的任何一个(“query”、“mutation”或“subscription”),指示我们想要委托给哪个根类型fieldName
:我们想要委托给的根字段的名称args
:我们委托给的根字段的输入参数context
:通过目标 schema 的解析器链传递的上下文对象info
:包含有关要委托的查询的信息的对象
为了让我们使用这种方法,我们首先需要一个 GraphQLSchema 的可执行实例,它代表 GitHub GraphQL API。我们可以使用 graphql-tools 中的makeRemoteExecutableSchema来获得它。
请注意,GitHub 的 GraphQL API 需要身份验证,因此你需要一个身份验证令牌才能使其工作。你可以按照这个指南来获取一个。
为了为 GitHub API 创建远程 schema,我们需要两件事
- 它的schema 定义(以
GraphQLSchema
实例的形式) - 一个
HttpLink
,它知道如何从中获取数据
我们可以使用以下代码来实现这一点
GitHubLink
只是HttpLink
之上的一个简单包装器,为创建所需的 Link 组件提供了一些便利。
太棒了,我们现在有了一个 GitHub GraphQL API 的可执行版本,我们可以在我们的解析器中委托给它!🎉 让我们首先实现prismagraphql
解析器
我们正在传递delegateToSchema
函数期望的七个参数。总的来说,没有什么意外:schema
是 GitHub GraphQL API 的远程可执行 schema。在那里,我们希望将我们自己的prismagraphql
查询的执行委托给 GitHub API 中的repositoryOwner
查询。由于该字段期望一个login
参数,我们为其提供"prismagraphql"
作为其值。最后,我们只是通过解析器链传递info
和context
对象。
prismagraphqlRepositories
的解析器可以以类似的方式处理,但它有点棘手。它与之前实现的不同之处在于,我们的prismagraphqlRepositories: [Repository!]!
类型和 GitHub schema 定义中的原始字段repository: Repository
类型不像以前那样完美匹配。我们现在需要返回一个仓库数组,而不是一个仓库。
因此,我们继续使用 Promise.all 来确保我们可以一次委托多个查询,并将它们的执行结果捆绑到一个 promise 数组中
就是这样!我们现在已经为我们的自定义 GraphQL API 实现了所有三个解析器。虽然第一个(对于info
)是微不足道的,并且只是返回一个自定义字符串,但prismagraphql
和prismagraphqlRepositories
正在使用schema 委托将查询的执行转发到底层的 GitHub API。
如果你想查看此代码的工作示例,请查看此仓库。
使用graphql-tools
进行 Schema 委托
在上面构建自定义 GraphQL API 以在 GitHub 之上运行的示例中,我们看到了delegateToSchema
如何使我们免于编写用于查询执行的样板代码。我们可以使用graphql-tools
提供的 API 将查询的执行委托给另一个(可执行)GraphQLSchema
实例,而不是从头开始构建新查询并使用 fetch、graphql-request
或其他 HTTP 工具发送它。方便的是,此实例可以创建为远程 schema。
在高层,delegateToSchema
只是充当execute
函数的“代理”,该函数来自GraphQL.js。这意味着在底层,它将根据作为参数传递的信息重新组装 GraphQL 查询(或 mutation)。一旦查询被构建,它所做的就是使用 schema 和查询调用execute
。
因此,schema 委托不一定要求目标 schema 是远程 schema,它也可以使用本地 schema 完成。在这方面,schema 委托是一个非常灵活的工具——你甚至可能想要在同一个 schema 中进行委托。这基本上是graphql-tools
中的mergeSchemas
中采用的方法,其中多个 schema 首先被合并为一个 schema,然后重新连接解析器。
本质上,schema 委托是关于能够轻松地将查询转发到现有的 GraphQL API。
Schema 绑定:重用 GraphQL API 的简单方法
凭借我们新获得的关于 schema 委托的知识,我们可以引入一个新概念,它不过是 schema 委托之上的一个薄薄的便利层,称为schema 绑定。
公共 GraphQL API 的绑定
schema 绑定的核心思想是提供一种简单的方法,使现有的 GraphQL API 可重用,以便其他开发人员现在可以通过 NPM 将其拉入到他们的项目中。这允许构建 GraphQL“网关”的一种全新方法,在其中可以非常容易地组合多个 GraphQL API 的功能。
通过 GitHub API 的专用绑定,我们现在可以简化上面的示例。与其手动创建远程可执行 schema,不如由graphql-binding-github
包来完成这部分工作。以下是完整实现的样子的,其中删除了我们之前需要委托给 GitHub API 的所有初始设置代码
我们没有自己创建远程 schema,而是简单地实例化从graphql-binding-github
导入的GitHub
类,并使用它的delegate
函数。然后,它将在底层使用delegateToSchema
来实际执行请求。
公共 GraphQL API 的 Schema 绑定可以在开发者之间共享。除了
graphql-binding-github
之外,Yelp GraphQL API 也已经有一个可用的绑定:graphql-binding-yelp
,由Devan Beitel提供
自动生成的委托函数
这些 schema 绑定的 API 甚至可以改进到一个委托函数自动生成的级别。绑定可以公开一个以对应的根字段命名的函数:github.query.repository( ... )
,而不是编写以下代码github.delegate('query', 'repository', ... )
。
当这些委托函数在构建步骤中生成并基于强类型语言(如 TypeScript 或 Flow)时,这种方法甚至将为与其他 GraphQL API 交互提供编译时类型安全!
要了解这种方法的样子,请查看prisma-binding仓库,它允许轻松地为 Graphcool 服务生成 schema 绑定,并使用前面提到的自动生成委托函数的方法。
总结
这是我们“理解 GraphQL schema stitching”系列的第二篇文章。在第一篇文章中,我们做了一些基础工作,并了解了远程(可执行)schema,它们是大多数 schema stitching 场景的基础。
在本文中,我们主要讨论了schema 委托的概念,通过提供一个基于GitHub GraphQL API的综合示例(该示例的代码可在此处获得)。Schema 委托是一种将解析器函数的执行转发(委托)到不同(甚至相同)GraphQL schema 中的另一个解析器的机制。它的主要优点是我们不必从头开始构建全新的查询,而是可以重用和转发(部分)传入的查询。
当使用 schema 委托作为基础时,可以创建专用的 NPM 包,以轻松共享现有 GraphQL API 的可重用schema 绑定。要了解这些绑定的样子,你可以查看GitHub API 的绑定以及prisma-binding,后者允许轻松地为任何 Graphcool 服务生成绑定。
不要错过下一篇文章!
注册 Prisma 新闻通讯