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.

posted @ 2018-08-28 10:44  tutu_python  阅读(596)  评论(0)    收藏  举报