【Apollo Server v2.0使用】可以通过Schema stitching功能将多个GraphQL API服务合并,并传递头部信息

前提知識 – Premise knowledge

GraphQL是一种查询语言

这是用于Web API的查询语言。

# Schema
type Query {
  todo(todoId:ID!): Todo!
}
type Todo {
  id: ID!
  todoId: ID!
  title: String!
}

# Request
query GetTodo {
  todo(todoId: 1) {
    id
    todoId
    title
  }
}

# Response
{
  "data": {
    "todo": {
      "id": "dXNlci80Mgo=",
      "todoId": 1,
      "title": "foo",
    }
  }
}

# Request(必要なフィールドのみリクエスト)
query GetTodo {
  todo(todoId: 1) {
    title
  }
}

# Response(必要なフィールドのみがレスポンスされる)
{
  "data": {
    "todo": {
      "title": "foo",
    }
  }
}

「GraphQL」的入门非常全面,通过与REST的比较和API和前端实现的学习,非常易懂。

Schema stitching指的是将多个GraphQL模式合并成一个大型的模式的过程。

Schema stitching是一种功能,它将多个GraphQL API的模式组合成一个GraphQL模式。
从“stitching”(缝合、拼接)这个词的意思来看,可以大概想象出它是将多个GraphQL API服务的模式拼接在一起,形成一个GraphQL API模式的工具。

index-diagram.png

由于可以集成模式,即使端点分离,这也是与微服务架构兼容的功能。
与REST API不同,GraphQL作为一种语言规范,可以定义模式,因此可以实现该功能。

创建Apollo Server v2.0项目。

安装所需的软件包。

mkdir sample-project
cd sample-project
yarn init --yes
yarn add apollo-link-context apollo-link-http apollo-server-express express graphql graphql-request graphql-tools merge-graphql-schemas node-fetch path
yarn add -D @babel/cli @babel/core @babel/node @babel/preset-env eslint nodemon

将package.json文件中定义scripts。

  ...
  "scripts": {
    "dev": "nodemon --exec babel-node src/index.js",
    "build": "babel src --out-dir dist",
    "start": "node dist/index.js"
  },
  ...

目录结构

┣ schemas
  ┣ foo.gql
  ┣ ...
  ┗ bar.gql
┣ resolvers
  ┣ foo.js
  ┣ ...
  ┗ bar.js
┣ src
  ┗ index.js
┣ .babelrc
┣ .eslintrc.json(任意)
┣ package.json
┗ yarn.lock

在schemas和resolvers下定义本地想要定义的模式和解析器。

另外,我们还需要在.babelrc文件中进行以下设置。

{
  "presets": [
    [
      "@babel/preset-env", {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

如果不明确指定”targets”,则在后续步骤中会出现”regeneratorRuntime is not defined”错误。
参考:https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined#answer-53010685

Apollo Server 基本代码(不包含模式拼接)

这个不是本次主题,但是下面是一个没有使用Schema stitching的Apollo Server的代码示例。
这是一个由本地定义的Schema和Resolver构成的最简单的实现示例。

const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const path = require('path');
const { fileLoader, mergeTypes } = require('merge-graphql-schemas');

const runApp = async () => {
  const localSchema = mergeTypes(fileLoader(path.join(__dirname, '../schemas')), { all: true });
  const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './resolvers')));

  const app = express();

  const options = {
    schema,
    resolvers,
  };

  const server = new ApolloServer(options);
  server.applyMiddleware({ app });

  const port = process.env.PORT || 4000;

  app.listen({ port }, () => {
    // eslint-disable-next-line no-console
    console.log(`App listening on port ${port}`);
    // eslint-disable-next-line no-console
    console.log('Press Ctrl+C to quit.');
  });
};

try {
  runApp();
} catch (err) {
  // eslint-disable-next-line no-console
  console.error(err);
}

可以使用以下命令启动服务器。

$ yarn dev
App listening on port 4000
Press Ctrl+C to quit.

当您通过浏览器访问http://localhost:4000/graphql时,您可以在Apollo Server实现的Playground上进行娱乐。

Schema stitching的实现

好了,我们开始正题吧。 , ba.)

可以使用graphql-tools库中的introspectSchema、makeRemoteExecutableSchema和mergeSchemas函数来进行Schema stitching。

此外,如果需要发送JWT等令牌以进行终端用户认证,则可以在Apollo Server的选项中配置将必要的标头信息存储在上下文中,并使用apollo-link-context的setContext方法重新设置headers,从而可以将请求的标头信息转发到进行拼接的服务。

...
import { introspectSchema, makeRemoteExecutableSchema, mergeSchemas } from 'graphql-tools';
import { setContext } from 'apollo-link-context';
...

const createRemoteSchema = async (uri) => {
  const http = createHttpLink({uri, fetch});
  const link = setContext((_, previousContext) => {
    const headers = {};
    if (previousContext && previousContext.graphqlContext && previousContext.graphqlContext.Authorization) {
      headers.Authorization = previousContext.graphqlContext.Authorization;
    }
    return { headers };
  }).concat(http);
  return makeRemoteExecutableSchema({
    schema: await introspectSchema(link),
    link,
  });
};

const runApp = async () => {
  ...
  const remoteSchemas = await Promise.all([
    'http://example.com/service1/graphql',
    'http://example.com/service2/graphql',
    'http://example.com/service3/graphql',
    'http://example.com/service4/graphql',
  ].map(async (type) => {
    return await createRemoteSchema(remoteHost + type).catch(error => {
      // eslint-disable-next-line no-console
      console.log(error);
    });
  }));

  const schema = mergeSchemas({
    schemas: [localSchema].concat(remoteSchemas)
  });

  const options = {
    context: ({ req }) => {
      return {
        Authorization: req.headers.authorization
      };
    },
    schema,
    resolvers,
  };

  const server = new ApolloServer(options);
  ...
}
广告
将在 10 秒后关闭
bannerAds