2017 年 12 月 12 日

GraphQL Schema 拼接详解:Schema 委托

理解 GraphQL schema 拼接(第二部分)

GraphQL Schema Stitching explained

上一篇文章中,我们讨论了远程(可执行)schema 的来龙去脉。这些远程 schema 是一组被称为 schema 拼接 的工具和技术的基础。

Schema 拼接是 GraphQL 社区中的一个全新话题。一般来说,它指的是组合和连接多个 GraphQL schema(或schema 定义)以创建单个 GraphQL API 的行为。

schema 拼接中有两个主要概念

  • 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 类型有一个类型为 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 的解析器很简单,我们可以返回一个描述我们应用程序的简单字符串。但是如何处理 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 来确保我们可以一次委托多个查询,并将它们的执行结果捆绑到一个承诺数组中

就是这样!我们现在已经为我们的自定义 GraphQL API 实现了所有三个解析器。虽然第一个(用于 info)很简单,只是返回一个自定义字符串,但 prismagraphqlprismagraphqlRepositories 正在使用schema 委托来将查询的执行转发到底层 GitHub API。

如果您想查看此代码的工作示例,请查看此仓库

使用 graphql-tools 进行 Schema 委托

在上面构建在 GitHub 之上的自定义 GraphQL API 的示例中,我们看到了delegateToSchema 如何使我们免于编写用于查询执行的样板代码。我们可以使用 graphql-tools 提供的 API 将查询的执行委托给 GraphQLSchema 的另一个(可执行)实例,而不是从头开始构建新查询并使用 fetch、graphql-request 或其他 HTTP 工具发送它。方便的是,此实例可以创建为远程 schema

在高层面上,delegateToSchema 只是充当来自 GraphQL.jsexecute 函数的“代理”。这意味着在底层,它将根据作为参数传递的信息重新组装 GraphQL 查询(或 mutation)。一旦查询构建完成,它所做的就是使用 schema 和查询调用 execute

因此,schema 委托不一定要求目标 schema 是远程 schema,它也可以使用本地 schema 完成。在这方面,schema 委托是一个非常灵活的工具——您甚至可能希望在同一 schema 内进行委托。这基本上是 graphql-toolsmergeSchemas 中采用的方法,其中多个 schema 首先合并为一个 schema,然后重新连接解析器。

本质上,schema 委托是关于能够轻松地将查询转发到现有 GraphQL API。

Schema 绑定:重用 GraphQL API 的简便方法

凭借我们新获得的关于 schema 委托的知识,我们可以引入一个新概念,它不过是 schema 委托之上的一个薄的便利层,称为schema 绑定

公共 GraphQL API 的绑定

schema 绑定的核心思想是提供一种简单的方法,使现有的 GraphQL API 可重用,以便其他开发人员现在可以通过 NPM 将其拉入他们的项目中。这允许构建 GraphQL “网关” 的全新方法,在这种方法中,组合多个 GraphQL API 的功能非常容易。

通过 GitHub API 的专用绑定,我们现在可以简化上面的示例。这个部分现在由 graphql-binding-github 包完成,而不是手动创建远程可执行 schema。以下是完整实现的​​样子,其中删除了我们之前需要委托给 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 拼接”的第二篇文章。在第一篇文章中,我们做了一些基础工作,并了解了远程(可执行)schema,它们是大多数 schema 拼接场景的基础。

在本文中,我们主要讨论了schema 委托的概念,并通过一个基于GitHub GraphQL API的综合示例(该示例的代码可在此处获得)。Schema 委托是一种机制,用于将解析器函数的执行转发(委托)到不同(甚至相同)GraphQL schema 中的另一个解析器。它的主要优点是我们不必从头开始构建全新的查询,而是可以重用和转发(部分)传入的查询。

当使用 schema 委托作为基础时,可以创建专用的 NPM 包,以便轻松共享现有 GraphQL API 的可重用schema 绑定。要了解这些绑定的外观,您可以查看GitHub API 的绑定以及prisma-binding,它可以轻松地为任何 Graphcool 服务生成绑定。

不要错过下一篇文章!

注册 Prisma 新闻邮件