理解 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 组织的信息
当我们提供 "prismagraphql" 作为 repositoryOwner 字段的 login 时,它会起作用。
这里有一个问题是,我们不能直接请求 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:包含有关要委托的查询信息的对象
为了使用这种方法,我们首先需要一个代表 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 查询(或 mutation)。一旦查询构建完成,它所做的就是使用 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 的绑定: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 新闻通讯