2017 年 12 月 12 日

GraphQL Schema Stitching 详解:Schema 委托

理解 GraphQL schema stitching(第二部分)

GraphQL Schema Stitching explained

上一篇文章中,我们讨论了远程(可执行)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"作为loginrepositoryOwner字段时,它会工作。

这里的一个问题是我们无法以直接的方式询问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 定义本身是不完整的:它缺少OrganizationRepository类型的定义。解决这个问题的一种直接方法是手动复制和粘贴 GitHub schema 定义中的定义。

这种方法很快变得繁琐,因为这些类型定义本身依赖于 schema 中的其他类型(例如,Repository类型有一个类型为codeOfconductCodeOfConduct字段),你也需要手动复制过来。这种依赖链深入 schema 的深度没有限制,你甚至可能最终手动复制完整的 schema 定义。

请注意,当手动复制类型时,有三种方法可以做到这一点

  • 整个类型都被复制过来,没有添加额外的字段
  • 整个类型都被复制过来,并且添加了额外的字段(或现有字段被重命名)
  • 只复制了类型字段的子集

简单地复制完整类型的第一种方法是最直接的。这可以使用graphql-import自动化,如下一节所述。

如果向类型定义添加了额外的字段或现有字段被重命名,你需要确保实现相应的解析器,因为底层 API 当然无法处理解析这些新字段。

最后,你可能会决定只复制类型字段的子集。如果你不想公开类型的所有字段,这可能是理想的(底层 schema 可能在User类型上有一个password字段,你不想在你的应用程序 schema 中公开它)。

导入 GraphQL 类型定义

graphql-import包通过让你在不同的.graphql文件中共享类型定义,从而将你从手动工作中解放出来。你可以像这样从另一个 GraphQL schema 定义中导入类型

在你的 JavaScript 代码中,你现在可以使用importSchema函数,它将为你解析依赖项,确保你的 schema 定义是完整的。

实现 API

有了上面的 schema 定义,我们只完成了一半。仍然缺少的是 schema 的实现,以解析器函数的形式。

如果你此时感到困惑,请务必阅读这篇文章,它介绍了 GraphQL schema 的基本机制和内部工作原理。

让我们考虑一下如何实现这些解析器!第一个版本可能如下所示

info的解析器是微不足道的,我们可以返回一个描述我们应用程序的简单字符串。但是如何处理prismagraphqlprismagraphqlRepositories的解析器,我们需要从 GitHub GraphQL API 返回信息?

在这里实现此功能的幼稚方法是查看info参数以检索传入查询的选择集——然后从头开始构建另一个 GraphQL 查询,该查询具有相同的选择集并将其发送到 GitHub API。这甚至可以通过为 GitHub GraphQL API 创建一个远程 schema来促进,但总的来说仍然是一个非常冗长和繁琐的过程。

这正是schema 委托发挥作用的地方!我们之前看到 GitHub 的 schema 公开了两个根字段,它们(在某种程度上)满足了我们的需求:repositoryOwner 和 repository。我们现在可以利用它来节省创建全新查询的工作,而是转发传入的查询。

委托给其他 schema

因此,与其尝试构建一个全新的查询,我们不如简单地获取传入的查询并将其执行委托给另一个 schema。我们将用于此的 API 称为delegateToSchema,由graphql-tools提供。

delegateToSchema接收七个参数(按以下顺序)

  1. schemaGraphQLSchema的可执行实例(这是我们想要将执行委托给的目标 schema
  2. fragmentReplacements:包含内联片段的对象(这适用于更高级的情况,我们将在本文中不讨论)
  3. operation:一个字符串,包含三个值中的任何一个(“query”、“mutation”或“subscription”),指示我们想要委托给哪个根类型
  4. fieldName:我们想要委托给的根字段的名称
  5. args:我们委托给的根字段的输入参数
  6. context:通过目标 schema 的解析器链传递的上下文对象
  7. 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"作为其值。最后,我们只是通过解析器链传递infocontext对象。

prismagraphqlRepositories的解析器可以以类似的方式处理,但它有点棘手。它与之前实现的不同之处在于,我们的prismagraphqlRepositories: [Repository!]!类型和 GitHub schema 定义中的原始字段repository: Repository类型不像以前那样完美匹配。我们现在需要返回一个仓库数组,而不是一个仓库。

因此,我们继续使用 Promise.all 来确保我们可以一次委托多个查询,并将它们的执行结果捆绑到一个 promise 数组中

就是这样!我们现在已经为我们的自定义 GraphQL API 实现了所有三个解析器。虽然第一个(对于info)是微不足道的,并且只是返回一个自定义字符串,但prismagraphqlprismagraphqlRepositories正在使用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 新闻通讯