fullstack GraphQL学习笔记(2)服务端基本知识
如何构建一个GraphQL服务端呢?由于GraphQL仅发布了指引文件(specification),因此可以使用任何编程语言实现GraphQL server。在开始构建GraphQL server之前,我们需要定义一个schema。schema定义了客户端如何访问服务端的协议。
1、GraphQL schema定义了服务器端的API
(1)定义schema,使用SDL
type User {
id: ID!
name: String
}
定义了这样一个User type,并没有为客户端增加新的功能,如果希望客户端能够请求相关数据,必须在根类Query上增加定义,这样客户端才能访问五福短的uer
type Query {
user(id: ID!): User
}
2、GraphQLSchema是GraphQL server的核心。
GraphQL.js是facebook的GraphQL 的参考实现,它为graph-tools和graphene-js提供了基础。当我们使用任何一个库的时候,其实就是围绕着一个核心:GraphQLScema.
GraphQLSchema由两部分组成:
(1)schema的定义
(2)解析函数(resolver)对schema定义的实现。
GraphQLSchema Object的示例如下:
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString }
}
})
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLID }
}
}
}
})
})
你可以看到,上面用SDL编写的schema可以被直接转变为javascipt语言定义的GraphQLSchema。由于我们还没有定义解析器(resolver),因此现在还不能进行任何的查询或修改操作。
3、resolvers 执行schema定义的API
(1)GraphQL server的结构和行为
GraphQL在结构和行为中间有明显的界限,schema负责定义结构,是对服务器能力的一个抽象表述。schema要想实现其定义的服务器端的行为,需要resolver(解析函数)的帮忙。
在Schema中定义的每一个字段field背后都对应着一个解析函数。每个解析器需要知道如何为这个field获取数据。
GraphQL的查询query实质都是一些field的集合,这些field的集合发送到服务器端,服务器端对应的解析函数集合将要工作,找到每个字段对应的数据,并按照查询的结构连接在一起发送给前端。因此,GraphQL可以说本质上是一种调用远端函数的语言。
(2)理解解析器函数(resolver function)
当我们使用GraphQL.js时,GraphQLSchema的每一个字段都会有一个对应的resolver。如上面的示例,为user字段增加一个resolver
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLID }
},
resolve: (root, args, context, info) => {
const { id } = args // the `id` argument for this field is declared above
return fetchUserById(id) // hit the database
}
}
}
})
})
假设fetchUserById函数已经存在,并且返回一个User对象。现在resolve保证了schema可以执行查询了。
让我们看一下解析器的四个参数:
root:有时也叫parent.GraphQL server在接受到请求query document的时候,服务器会按照广度优先的原则调用各个字段的解析函数。root代表了上一级解析函数的解析结果。如果没有定义的话初始值是null。
args:代表了客户端query传递过来的参数。
context:一个对象可以为所有的resolver共同使用,包括读取和修改。
info:查询或修改的AST表示。
上面我们的请求中,在user根字段下还有id和name字段,我们看下如何为id和name构建解析函数。
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: {
type: GraphQLID,
resolve: (root, args, context, info) => {
return root.id
}
},
name: {
type: GraphQLString,
resolve: (root, args, context, info) => {
return root.name
}
}
}
})
除了根字段外,根字段下面的解析都是相对简单的。
(3)query执行过程
让我们解析一下上面的请求执行的整个过程,客户端发送了一个请求,包含三个字段,根字段(user)以及根字段下的id和name两个字段。
第一步:请求到达服务器。
第二步:服务端首先启用根字段解析器,假设fetchUserById返回的是{‘ID:‘ABC,“name”:'sarah’’}
第三步:服务端开始启用user type下的id resolver。由于上面的解析结果包含在root中,因此要获取id,只要调用root.id便可以得到结果。
第四步:同第三步,得到name.
第五步:解析结束,结果被包裹在data字段当中返回。
{
"data": {
"user": {
"id": "abc",
"name": "Sarah"
}
}
}
如果使用graphql.js,像上面一样的简单的返回id和name可以不用写,Graphql.js可以自己根据字段名称和root返回对应的数据。
(4)优化请求你,使用DataLoader pattern.
如果请求的层次比较深,上面的执行过程会造成性能表现较差。
如:
query {
user(id: "abc") {
name
article(title: "GraphQL is great") {
comments {
text
writtenBy {
name
}
}
}
}
}
假设有5条评论(comments)但是都是一个人写的,按照上面的过程我们要调用五次writtenBy,但是得到的是一个结果。为了解决上述的重复调用,我们引入了DataLoader,这样可以避免重复的发送相同的请求。
4、GraphQL.js和graph-tools
graph-tools是对GraphQL.js的封装。
(1)让我们首先看一下GraphQL.js,他的核心功能都是围绕GraphQLSchema:
parse和buildASTSchema:这两个函数时解析SDL语言,创建js的GraphQLSchema:const schema = buildASTSchema(parse(sdlString))
validtate:给定一个query和GraphQLSchema,validate可以验证query是否符合GraphQLSchema
excute:给定一个query和GraphQLSchema,如果GraphQLSchema包含query的解析函数,excute将会一次调用与query字段相应的解析函数,并返回相应的数据。
printSchema:将一个GraphQLSchema转变成一个SDL字符串,和parse/buildASTSchema的功能正好相反。
graphql:接受两个参数一个是GraphQLSchema,另一个是query,然后调用validate和excute返回结果:
graphql(schema, query).then(result => console.log(result))
graphql将一个query映射到shema包含结构和解析函数,负责解析函数的调用,并打包解析的结果。因此graphql的功能也被称为GraphQL engine。
一个完整的示例:
const {
parse,
printSchema,
validate,
execute,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLID
} = require('graphql')
const fetchUserById = require('./sampleData')
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: {
type: GraphQLID,
// `resolve` is not needed here: graphql-js infers the returned value.
// Remove the comments to see that it's called when the query contains the `id` field.
// resolve: (root, args, context, info) => {
// console.log(`Resolver called: user.id`)
// return root.id
// }
},
name: {
type: GraphQLString,
// `resolve` is not needed here: graphql-js infers the returned value.
// Remove the comments to see that it's called when the query contains the `name` field.
// resolve: (root, args, context, info) => {
// console.log(`Resolver called: user.name`)
// return root.name
// }
}
}
})
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
args: {
id: { type: GraphQLID }
},
resolve: (root, args, context, info) => {
console.log(`Resolver called: user`)
return fetchUserById(args.id)
}
}
}
})
})
// Create and print SDL-representation of schema
// const sdlSchema = printSchema(schema)
// console.log(`Schema: \n${sdlSchema}`)
// Define the query
const queryString = `
{
user(id: "abc") {
id
name
}
}`
const queryAST = parse(queryString)
// Validate the query against the schema
const errors = validate(schema, queryAST)
if (errors.length === 0) {
console.log(`Validation successful`)
} else {
console.log(`Errors: ${JSON.stringify(errors)}`)
}
// Execute the query against the schema
execute(schema, queryAST).then(result => {
console.log(`Execution result: \n${JSON.stringify(result)}`)
}).catch(e => console.log(JSON.stringify(e)))
(2)graph-tools
使用GraphQL的一个好处是他使用schema有限原则,因此前端人员可以在不用理会后端开发情况,直接参照schema模拟数据请求。
GrpahQL.js最大的缺点是它不允许你使用SDL直接写schema.
上面我们提到可以使用parse和buildASTSchema可以将SDL的schema转变为GraphQLSchema,但是转化的GraphQLSchema并不包含resolve函数,resolve函数只能手工在GraphQLSchema中进行定义。
graph-tools很好的填补了这个缺陷:graphql-tools示例:
const { makeExecutableSchema } = require('graphql-tools')
const typeDefs = `
type Query {
user(id: ID!): User
}
type User {
id: ID!
name: String
}`
const resolvers = {
Query: {
user: (root, args, context, info) => {
return fetchUserById(args.id)
}
},
}
const schema = makeExecutableSchema({
typeDefs,
resolvers
})
graphql-tools通过makeExecutableSchema创建schema不仅包含了类型的定义,而且包含了解析函数。
什么时候不用graphql-tools
如果希望动态构建和修改schema,那么还是应该使用GraphQL.js
(3)graphql-js
graphql-js是一个新的库,它遵循了python的设计理念,也是基于GraphQL.js,但是不允许使用SDL来建立schema.
浙公网安备 33010602011771号