理解 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 组织中的仓库列表
- 检索关于应用程序本身的简短描述
让我们探索GitHub 的 GraphQL schema 定义中的 Query
类型,看看如何将我们的需求映射到 schema 的根字段。
需求 1:检索 Graphcool 组织信息
第一个功能,检索 Prisma 组织信息,可以通过在 Query
类型上使用 repositoryOwner
根字段来实现
我们可以发送以下查询来获取 Prisma 组织的信息
当我们为 repositoryOwner
字段提供 "prismagraphql"
作为 login
时,它会起作用。
这里的一个问题是,我们无法直接查询 email
,因为 RepositoryOwner
只是一个接口,没有 email
字段。然而,由于我们知道 Prisma 组织的具体类型确实是 Organization
,我们可以通过在查询中使用内联片段来解决这个问题
好的,这会起作用,但我们已经遇到了一些阻碍点,这些阻碍点不允许我们为应用程序的目的直接使用 GitHub GraphQL API。
理想情况下,我们的 API 只需公开一个根字段,允许我们直接查询所需信息,而无需在每次查询时提供参数,并允许我们直接查询 Organization
上的字段
需求 2:按名称检索 Graphcool 仓库列表
第二个需求,按名称检索 Graphcool 仓库列表,又如何呢?再次查看 Query
类型,这变得有点复杂。该 API 不允许直接检索仓库列表——相反,您可以使用以下根字段通过提供 owner
和仓库的 name
来查询单个仓库
以下是对应的查询
然而,我们应用程序实际想要的是一个根字段,其形式如下(为了避免发出多个请求)
需求 3:检索关于应用程序本身的简短描述
我们的 API 应该能够返回一个描述我们应用程序的句子,例如 "此应用程序提供有关 Prisma GitHub 组织的信息"
。
这当然是一个我们无法基于 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
:一个包含有关要委托的查询信息的对象
为了使用这种方法,我们首先需要一个表示 GitHub GraphQL API 的 GraphQLSchema 可执行实例。我们可以使用 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 委托
在上面基于 GitHub 构建自定义 GraphQL API 的示例中,我们看到了 delegateToSchema
如何帮助我们省去编写查询执行的样板代码。我们无需从头构建新查询并使用 fetch、graphql-request
或其他 HTTP 工具发送它,而是可以使用 graphql-tools
提供的 API 将查询的执行委托给另一个(可执行的)GraphQLSchema
实例。方便的是,此实例可以作为远程 schema 创建。
从高层次来看,delegateToSchema
只是作为 GraphQL.js 中 execute
函数的“代理”。这意味着在底层,它将根据作为参数传递的信息重新组装 GraphQL 查询(或变更)。一旦查询构建完成,它所做的就是使用 schema 和查询调用 execute
。
因此,schema 委托不一定要求目标 schema 是远程 schema,它也可以与本地 schema 一起使用。在这方面,schema 委托是一个非常灵活的工具——您甚至可能希望在同一个 schema 内部进行委托。这基本上是 graphql-tools
中 mergeSchemas
采用的方法,其中多个 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 的绑定:由 Devan Beitel 开发的graphql-binding-yelp
自动生成的委托函数
这类 schema 绑定的 API 甚至可以改进到委托函数是自动生成的程度。与其编写 github.delegate('query', 'repository', ... )
,绑定可以暴露一个以相应根字段命名的函数:github.query.repository( ... )
。
当这些委托函数在构建步骤中生成并基于强类型语言(如 TypeScript 或 Flow)时,这种方法甚至可以为与其他 GraphQL API 的交互提供编译时类型安全!
要了解这种方法的样子,请查看 prisma-binding 仓库,它允许轻松地为 Graphcool 服务生成 schema 绑定,并使用上述自动生成委托函数的方法。
总结
这是我们“理解 GraphQL schema stitching”系列的第二篇文章。在第一篇文章中,我们做了一些基础工作,并了解了远程(可执行)schema,它们是大多数 schema stitching 场景的基础。
在本文中,我们主要通过一个基于 GitHub GraphQL API 的综合示例(示例代码可在此处获取)讨论了schema 委托的概念。Schema 委托是一种将解析器函数的执行转发(委托)到不同(甚至相同)GraphQL schema 中的另一个解析器的机制。其主要优点是,我们无需从头构建一个全新的查询,而是可以重用和转发(部分)传入的查询。
当使用 schema 委托作为基础时,可以创建专门的 NPM 包来轻松共享现有 GraphQL API 的可重用schema 绑定。要了解这些绑定的样子,您可以查看GitHub API 的绑定以及prisma-binding,后者允许轻松为任何 Graphcool 服务生成绑定。
不要错过下一篇文章!
订阅 Prisma 新闻通讯