【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模式的工具。
由于可以集成模式,即使端点分离,这也是与微服务架构兼容的功能。
与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);
...
}