让我们尝试使用GraphQL进行实现
你好。今天是 OPENLOGI 节日日历的第四天。
我平时负责仓库管理系统(WMS)的开发。
这次和物流完全无关,但是我用GraphQL来实现了一个内部系统,所以我会写一些关于GraphQL的内容。
2015年,Facebook发布了GraphQL已经过去了两年。我记得当时大家都感到震惊,但现在看来,它似乎还没有成为潮流。
最近,在越来越多的服务中开始逐渐使用GraphQL作为API,Github也不例外。虽然GraphQL的概念逐渐被人们所熟知,但是实际的实现方式和操作却只有少数人真正了解。这让人认为GraphQL的门槛比较高。因此,希望能够有个简单易懂的讲解,告诉大家“如何去实现GraphQL”。
关于GraphQL本身,我认为您可以参考《GraphQL入门——一个令人愿意使用GraphQL的优秀文章》。
无论如何,在本文中,我将大致解释具体的实施方法。(本文不涉及GraphQL的好处。)
好的。听描述然后提问获取信息的方式。应该怎么做呢?
准备材料
你可以使用任何语言,但我只在Node.js和Python上有实际实现的经验,所以主要以这些语言为例进行解释。
应用服务器
只需要从客户端那里得到一个终端节点就可以了,所以无论什么都可以。如果是用nodejs的话,就用koa吧。
GraphQL服务器 (GraphQL Server)
这个。GraphQL服务器这个说法经常出现,非常令人困惑!最初我以为是指有特殊功能的服务器。(只有我一个人这样想吗?)
实际上,这只是用来处理发送给GraphQL查询的请求的端点。它的作用就是解析GraphQL查询,并将处理逻辑分派到数据获取的实现中。虽然你也可以自己实现,但大多数语言/框架已经有相应的库,可以直接使用。比如Python的graphene,Node.js的apollo-server,还有Facebook的graphql-js。
作为一个实例,将koa × apollo结合起来,可以得到上述的应用服务器和GraphQL服务器。
import koa from 'koa';
import koaRouter from 'koa-router';
import koaBody from 'koa-bodyparser';
import { graphqlKoa, graphiqlKoa } from 'apollo-server-koa';
const app = new koa();
const router = new koaRouter();
router.post('/graphql', graphqlKoa({ schema: myGraphQLSchema }));
app.use(router.routes());
app.listen(3000);
当然。当请求被发送到/graphql终点时,只需将其传递给graphqlKoa库即可。非常简单。
其中的myGraphqlSchema对象是应用程序的实现部分,包括类型定义、数据获取逻辑等等(稍后会详细提到)。
数据库 (databases)
没问题。GraphQL 只是一个用于获取远程数据的查询语言,也就是一个接口。所以,在服务器端当然需要能够访问数据。无论是使用数据库还是API,如果要模拟它们都可以,不是必需的。如果要使用关系型数据库,也可以选择自己喜欢的OR映射工具。总之,这部分没有任何限制。我们将在前面提到的 myGraphqlSchema 对象中实现数据库访问等操作。
描述你的数据
這個看起來好像
type Project {
name: String
tagline: String
contributors: [User]
}
这好像是必须要这样写的样子,但其实并没有必要。这完全取决于上述GraphQL服务器的实现。说到底,Facebook的graphql-js中没有要求。
const Project = new GraphQLObjectType({
name: "Project",
description: "Project description",
fields: () => ({
name: {type: GraphQLString},
tagline: {type: GraphQLString},
})
})
就像这样写,并且用Python的graphene库。
import graphene
class Project(graphene.ObjectType):
name = graphene.String(name=graphene.String())
tagline = graphene.String(name=graphene.String())
.
.
以class的形式进行定义,apollo-server是这样的。
type Project {
name: String
tagline: String
contributors: [User]
}
写的是这个,忠实。
只要能定义类型,什么都可以。剩下的事情由库来处理。
那么为什么要进行这种定义呢?简单来说,是为了在解析查询或返回响应时进行类型检查。这是GraphQL非常重要的特点。
而且,这种类型不必与数据库表一对一对应。您可以更改名称,甚至可以使用完全不同的模式,还可以添加新的表,或将多个表组合成一个类型来表示。这些都是根据您希望为应用程序创建什么样的接口而进行的思考。
虽然我并不打算在本文中详细解释GraphQL的类型,但是让我们快速地继续下一步。
问你想要的。
是的,就是这种查询语言。而且,客户端会将这个字符串发送到服务器。
{
project(name: "Graphql"): {
tagline
}
}
特征性的是有参数存在。这不仅仅适用于根节点,而且可以在任何节点上指定参数。例如,如果想要仅获取特定项目的前5个贡献者,可以这样做。
{
project(name: "Graphql"): {
tagline
contributers(first: 5) {
name
}
}
}
获得可预测的结果 kě de
那么,如何返回数据呢?这当然取决于使用的库,为了更容易理解,我们以apollo-server为例介绍实际的实现。这是上述myGraphqlSchema的具体实现,请先大致浏览一下。
const { makeExecutableSchema } = require('graphql-tools');
// 型の定義部分
// projectは引数に必須で数値のidをとり、contributorsは引数に数値のfirstをとる。
const typeDefs = `
type Query {
project(id: Int!): Project
}
type Project {
name: String
tagline: String
contributors(first: Int): [User]
}
type User {
name: String
email: String
}
`;
const resolvers = {
// Query型から返す値を取得する実装。
Query: {
project: (root, args) =>
fetch(`select id, name, tagline from projects where id = ${args.id}`)
},
// Project型から返す値を取得する実装。
Project: {
name: project => project.name,
tagline: project => project.tagline,
contributors: (project, args) =>
fetch(`select * from contributors where project_id = ${project.id} limit ${args.first}`)
},
// User型から返す値を取得する実装
User: {
name: user => user.name,
email: user => user.email
}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
typeDefs 是描述您的数据的部分,与上述的“描述您的数据”相对应。而 resolvers 是编写实际数据获取逻辑的地方。您需要在 typeDefs 中定义自己属性的获取方法,逐一进行定义。
查询(Query)是一个保留词,用于表示查询的根节点类型。当前,Query类型仅定义了project(项目)类型,但如果想要编写其他查询,例如想要获取用户列表!想要获取活跃用户列表!等等,需要修改并定义Query类型。
type Query {
project(id: Int!): Project
users: [User]
activeUsers: [User]
}
同样地,我们也会实现 resolvers。
const resolvers = {
// Query型から返す値を取得する実装。
Query: {
project: (root, args) => fetch(`select id, name, tagline from projects where id = ${args.id}`),
users: () => fetch('select * from users'),
activeUsers: () => fetch('select * from users where active = 1'),
},
.
.
这样一来,
这么做的话,
如果这样做的话,
这样的话
query {
users {
name
}
activeUsers {
name
}
}
写下这个查询。
{
"users": [
{
"name": "harada"
}, {
"name": "yamada"
}, {
"name": "tanaka"
}
],
"activeUsers": [
{
"name": "harada"
}, {
"name": "yamada"
}
]
}
可以获取类似的数据。
“解析器”的定义就像在使用REST实现时的终端点一样,它是用于访问数据库等获取数据的部分。在GraphQL中,对于某个类型,只需实现与该类型关联的可获取数据,因此范围非常有限,易于理解和实现,这是其特点之一。
最后
那么,简单来说,您能想象一下什么是实现 GraphQL 的大致过程吗?
实际上,各种类型的模块化、n + 1 问题、安全性以及服务器负载等都需要考虑的事项非常多。如果下次有机会的话,我会再写一篇关于这个的。