转自官方文档

In the last article, we discussed the ins and outs of remote (executable) schemas. These remote schemas are the foundation for a set of tools and techniques referred to as schema stitching.

Schema stitching is a brand new topic in the GraphQL community. In general, it refers to the act of combining and connecting multiple GraphQL schemas (or schema definitions) to create a single GraphQL API.

There are two major concepts in schema stitching:

  • Schema delegation: The core idea of schema delegation is to forward (delegate) the invocation of a specific resolver to another resolver. In essence, the respective fields of the schema definitions are being “rewired”.
  • Schema merging: Schema merging is the idea of creating the union of two (or more) existing GraphQL APIs. This is not problematic if the involved schemas are entirely disjunct — if they are not, there needs to be a way for resolving their naming conflicts.

Notice that in most cases, delegation and merging will actually be used together and we’ll end up with a hybrid approach that uses both. In this article series, we’ll cover them separately to make sure each concept can be well understood by itself.

Example: Building a custom GitHub API

Let’s start with an example based on the public GitHub GraphQL API. Assume we want to build a small app that provides information about the Prisma GitHub organization.

The API we need for the app should expose the following capabilities:

  • retrieve information about the Prisma organization (like its IDemail addressavatar URL or the pinned repositories)
  • retrieve a list of repositories from the Prisma organization by their names
  • retrieve a short description about the app itself

Let’s explore the Query type from GitHub’s GraphQL schema definition to see how we can map our requirements to the schema’s root fields.

Requirement 1: Retrieve info about Graphcool organization

The first feature, retrieving information about the Prisma organization, can be achieved by using the repositoryOwner root field on the Query type:

type Query {

  # ...

  # Lookup a repository owner (ie. either a User or an Organization) by login.
repositoryOwner(
# The username to lookup the owner by.
login: String!
): RepositoryOwner # ... }

We can send the following query to ask for information about the Prisma organization:

query {
repositoryOwner(login: "prismagraphql") {
id
url
pinnedRepositories(first:100) {
edges {
node {
name
}
}
}
# ask for more data here
}
}

It works when we provide "prismagraphql" as the login to the repositoryOwnerfield.

One issue here is that we can’t ask for the email in a straightforward way, because RepositoryOwner is only an interface that doesn’t have an email field. However, since we know that the concrete type of the Prisma organization is indeed Organization, we can work around this issue by using an inline fragment inside the query:

query {
repositoryOwner(login: "prismagraphql") {
id
... on Organization {
email
}
}
}

Ok, so this will work but we’re already hitting some friction points that don’t allow for a straightforward use of the GitHub GraphQL API for the purpose of our app.

Ideally, our API would just expose a root field that allowed to ask directly for the info we want without needing to provide an argument upon every query and letting us ask for fields on Organization directly:

type Query {
prismagraphql: Organization!
}

Requirement 2: Retrieve list of Graphcool repositories by name

How about the second requirement, retrieving a list of the Graphcool repositories by their names. Looking at the Query type again, this becomes a bit more complicated. The API doesn’t allow to retrieve a list of repositories directly— instead you can ask for single repositories by providing the owner and the repo’s name using the following root field:

type Query {

  # ...

  # Lookup a given repository by the owner and repository name.
repository(
# The login field of a user or organization
owner: String! # The name of the repository
name: String!
): Repository # ... }

Here’s a corresponding query:

query {
repository(owner: "prismagraphql", name: "graphql-yoga") {
name
description
# ask for more data here
}
}

However, what we actually want for our app (to avoid having to make multiple requests) is a root field looking as follows:

type Query {
prismagraphqlRepositories(names: [String!]): [Repository!]!
}

Requirement 3: Retrieve short description about the app itself

Our API should be able to return a sentence describing our app, such as "This app provides information about the Prisma GitHub organization".

This is of course a completely custom requirement we can’t fulfil based on the GitHub API — but rather it’s clear that we need to implement it ourselves, potentially with a simple Query root field like this:

type Query {
info: String!
}

Defining the application schema

We’re now aware of the required capabilities of our API and the ideal Query type we need to define for the schema:

type Query {
prismagraphql: Organization!
prismagraphqlRepositories(names: [String!]): [Repository!]!
info: String!
}

Obviously, this schema definition in itself is incomplete: it misses the definitions for the Organization and the Repository types. One straightforward way of solving this problem is to just manually copy and paste the definitions from GitHub’s schema definition.

This approach quickly becomes cumbersome, since these type definitions themselves depend on other types in the schema (for example, the Repository type has a field codeOfconduct of type CodeOfConduct) which you then need to manually copy over as well. There is no limit to how deep this dependency chain goes into the schema and you might even end up copying the full schema definition by hand.

Note that when manually copying over types, there are three ways this can be done:

  • The entire type is copied over, no additional fields are added
  • The entire type is copied over and additional fields are added (or existing ones are renamed)
  • Only a subset of the type’s fields are copied over

The first approach of simply copying over the full type is the most straightforward. This can be automated using graphql-import, as explained in the next section.

If additional fields are added to the type definition or existing ones are renamed, you need to make sure to implement corresponding resolvers as the underlying API of course cannot take care of resolving these new fields.

Lastly, you might decide to only copy over a subset of the type’s fields. This can be desirable if you don’t want to expose all the fields of a type (the underlying schema might have a password field on the User type which you don’t want to be exposed in your application schema).

Importing GraphQL type definitions

The package graphql-import saves you from that manual work by letting you share type definitions across different .graphql-files. You can import types from another GraphQL schema definition like so:

# import Repository from "./github.graphql"
# import Organization from "./github.graphql" type Query {
info: String!
graphcoolRepositories(names: [String!]): [Repository!]!
graphcool: Organization!
}

In your JavaScript code, you can now use the importSchema function and it will resolve the dependencies for you, ensuring your schema definition is complete.

Implementing the API

With the above schema definition, we’re only halfway there. What’s still missing is the schema’s implementation in the form of resolver functions.

If you’re feeling lost at this point, make sure to read this article which introduces the basic mechanics and inner workings of GraphQL schemas.

Let’s think about how to implement these resolvers! A first version could look as follows:

const { importSchema } = require('graphql-import')

// Import the application schema, including the
// types it depends on from `schemas/github.graphql`
const typeDefs = importSchema('schemas/app.graphql') // Implement resolver functions for our three custom
// root fields on the `Query` type
const resolvers = {
Query: {
info: (parent, args) => 'This app provides information about the Prisma GitHub organization',
prismagraphqlRepositories: (parent, { names }, context, info) => {
// ???
},
prismagraphql: (parent, args, context, info) => {
// ???
}
}
}

The resolver for info is trivial, we can return a simple string describing our app. But how to deal with the ones for prismagraphql and prismagraphqlRepositories where we actually need to return information from the GitHub GraphQL API?

The naive way of implementing this here would be to look at the info argument to retrieve the selection set of the incoming query — then construct another GraphQL query from scratch that has the same selection set and send it to the GitHub API. This can even be facilitated by creating a remote schema for the GitHub GraphQL API but overall is still quite a verbose and cumbersome process.

This is exactly where schema delegation comes into play! We saw before that GitHub’s schema exposes two root fields that (somewhat) cater the needs for our requirements: repositoryOwner and repository. We can now leverage this to save the work of creating a completely new query and instead forward the incoming one.

Delegating to other schemas

So, rather than trying to construct a whole new query, we simply take the incoming query and delegate its execution to another schema. The API we’re going to use for that is called delegateToSchema provided by graphql-tools.

delegateToSchema receives seven arguments (in the following order):

  1. schema: An executable instance of GraphQLSchema (this is the target schema we want to delegate the execution to)
  2. fragmentReplacements: An object containing inline fragments (this is for more advanced cases we’ll not discuss in this article)
  3. operation: A string with either of three values ( "query" , "mutation" or "subscription") indicating to which root type we want to delegate
  4. fieldName: The name of the root field we want to delegate to
  5. args: The input arguments for the root field we’re delegating to
  6. context: The context object that’s passed through the resolver chain of the target schema
  7. info: An object containing information about the query to be delegated

In order for us to use this approach, we first need an executable instance of GraphQLSchema that represents the GitHub GraphQL API. We can obtain it using makeRemoteExecutableSchema from graphql-tools.

Notice that GitHub’s GraphQL API requires authentication, so you’ll need an authentication token to make this work. You can follow this guide to obtain one.

In order to create the remote schema for the GitHub API, we need two things:

  • its schema definition (in the form of a GraphQLSchema instance)
  • an HttpLink that knows how to fetch data from it

We can achieve this using the following code:

// Read GitHub's schema definition from local file
const gitHubTypeDefs = fs.readFileSync('./schemas/github.graphql', {encoding: 'utf8'}) // Instantiate `GraphQLSchema` with schema definition
const introspectionSchema = makeExecutableSchema({ typeDefs: gitHubTypeDefs }) // Create `HttpLink` based using person auth token
const link = new GitHubLink(TOKEN) // Create remote executable schema based on schema definition and link
const schema = makeRemoteExecutableSchema({
schema: introspectionSchema,
link,
})

GitHubLink is just a simple wrapper on top of HttpLink, providing a bit of convenience around creating the required Link component.

Awesome, we now have an executable version of the GitHub GraphQL API that we can delegate to in our resolvers!

转 GraphQL Schema Stitching explained: Schema Delegation的更多相关文章

  1. Full Schema Stitching with Apollo Server

    转自: https://tomasalabes.me/blog/nodejs/graphql/apollo/2018/09/18/schema-stitiching-apollo.html Full ...

  2. Schema、API Schema与MFn

    大部分知识都是相通的,Maya和USD在设计上有很多相似之处,USD的Schema粗看很难理解,但实际上与Maya的MFn有着异曲同工之处.这篇文章会简单介绍一下这两个知识点,做个对比,了解下它们在各 ...

  3. Oracle Schema Objects(Schema Object Storage And Type)

    One characteristic of an RDBMS is the independence of physical data storage from logical data struct ...

  4. Star Schema and Snowflake Schema

    在设计数据仓库模型的时候,最常见的两种是星型模型与雪花模型.选择哪一种需要根据业务需求以及性能的多重考量来定. 星型模型 在星型模型当中,一张事实表被若干张维度表所包围.每一个维度代表了一张表,有主键 ...

  5. [GraphQL] Add an Interface to a GraphQL Schema

    As we start building out more complex GraphQL schemas, certain fields start to repeat across differe ...

  6. hasura graphql-engine v1.0.0-alpha30 remote schema stitch 试用

    新的hasura graphql-engine 代码仓库中已经包含了一个基于express 的简单graphql server, 可以用来测试模式拼接 graphql server 代码 项目结构 ├ ...

  7. Security3: Schema 和 Permission

    Schema是Object的容器,授予对Schema访问的权限,就是授予对Schema下所有object的访问权限. 一,Schema 是object的container The new schema ...

  8. XML Schema and XMLspy notes

    Introduction An xml documents consists of elements, attributes and text. There are two structures in ...

  9. LDAP的Schema

    Schema是LDAP的一个重要组成部分,类似于数据库的模式定义,LDAP的Schema定义了LDAP目录所应遵循的结构和规则,比如一个 objectclass会有哪些属性,这些属性又是什么结构等等, ...

随机推荐

  1. python安装mysql-python1.2.5

    首先安装好python 然后安装C++ Microsoft Visual C++ Compiler for Python 2.7 下载后双击安装 登录https://pypi.python.org/p ...

  2. DB开发之oracle存储过程

    1. 存储过程格式 /* Formatted on 2011/1/17 13:20:44 (QP5 v5.115.810.9015) */ CREATE OR REPLACE procedure pr ...

  3. esLint参数设置

    package.json { "name": "testEsLint", "version": "1.0.0", &qu ...

  4. 基于Policy Gradient实现CartPole

    http://chenrudan.github.io/blog/2016/09/04/cartpole.html 首页 分类 关于 归档 标签 基于Policy Gradient实现CartPole ...

  5. SNMP学习笔记之SNMPWALK 命令

    SNMPWALK是一个通过SNMP GET-NEXT类型PDU,实现对目标AGENT的某指定MIB分支信息进行完整提取输出的命令工作. 命令行: snmpwalk [选项] agent [oid] 选 ...

  6. 20145105 《Java程序设计》第5周学习总结

    20145105 <Java程序设计>第5周学习总结 教材学习内容总结 第八章 异常处理 一.语法与继承架构 (一)使用try.catch 执行流程 尝试执行try区块中程序代码 如果出现 ...

  7. hdu 2841 Visible Trees 容斥原理

    Visible Trees Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Pr ...

  8. (CLR via C#学习笔记)任务和并行操作

    一 任务 可以调用ThreadPool的QueueUserWorkItem方法发起一次异步的计算限制操作.但这个技术有很多限制.最大的问题是没有内建的机制让你知道操作在什么时候完成和操作完成时的返回值 ...

  9. 读写 Excel 工作表

    导入诸如 CSV 之类文本格式的数据的优点是不需要依靠某些特定软件来读取数据,并且文件具有可读性,即软件中性.然而,它的缺点也很明显——我们不能直接对文本编辑器中的数据执行计算操作,因为这些内容是纯文 ...

  10. 关于浏览器的eventflow(capture and bubble up)

    因为,没有全面的学习javascript,及其事件原理: 全占的课程:4-5 浏览器 Bubble Up 事件模型中 不是很理解它所讲的.网上查找相关知识点.记录中在博客中: 理解了JS的加载 htt ...