GraphQL查询语言学习笔记

RSETful API接口想必大家都比较熟悉,GraphQL设计用于替代RESTful API接口,但目前不是所有情况都使用替代,他们各有优势。
GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

在 2015 React 欧洲大会上,Lee Byron 介绍了 Facebook 的 GraphQL ,包含 GraphQL 背后的故事,查询语句的示例,还有核心的概念。GraphQL 非常易懂,直接看查询语句就能知道查询出来的数据是什么样的。你可以把 GraphQL 的查询语句想成是没有值,只有属性的对象,返回的结果就是对应的属性还有对应值的对象。

GraphQL 基础篇

GraphQL特性是用户可以自定义查询对象字段,使用图(树)的形式表示业务对象,从而定义查询示例。Facebook定义GraphQL规范,GraphQL语法方面的知识可以参考:

英文

中文

官方提供了新手课程:

英文

中文

GraphQL包括: 类型系统(Type System) 类型语言(Type Language) 对象类型和字段(Object Types and Fields)等查询语言的定义和专用名词。如果你还是新手,请认真阅读GraphQL规范新手课程。你已经理解了GraphQL是什么,那么可以接下来就是如何使用Golang编写GraphQL Server,如何编写schema,如何运行以及调试。

GraphQL Server

首先使用golang语言实现官方示例,以了解GraphQL Server如何编写。当然GraphQL中有客户端和服务端。

客户端库:

  • Relay (github):Facebook 的框架,用于构建与 GraphQL 后端交流的 React 应用。
  • Apollo Client (github):一个强大的 JavaScript GraphQL 客户端,设计用于与 React、React Native、Angular 2 或者原生 JavaScript 一同工作。
  • graphql: 一个使用 Go 编写的 GraphQL 客户端实现。

服务端库:

  • graphql-go: 支持查询解析器,但不支持GraphQL SDL解析。
  • graphql-relay-go: 支持react-relay,一般配合graphql-go使用。
  • neelance/graphql-go: 支持查询解析器和GraphQL SDL解析。(缺少自动生成定义SDL代码工具)

接下来分别使用graphql-goneelance/graphql-go来实现一些简单示例。

graphql-go库实现

graphql-go支持查询解析器,不支持GraphQL SDL解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
"encoding/json"
"fmt"

"github.com/graphql-go/graphql"
)

type user struct {
ID string `json:"id"`
Name string `json:"name"`
}

var data map[string]user = map[string]user{
"1": user{
ID: "1",
Name: "Dan",
},
"2": user{
ID: "2",
Name: "Lee",
},
"3": user{
ID: "3",
Name: "Nick",
},
}

// Create User object type with fields "id" and "name" by using GraphQLObjectTypeConfig:
// - Name: name of object type
// - Fields: a map of fields by using GraphQLFields
// Setup type of field use GraphQLFieldConfig
var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
},
},
)

// Create Query object type with fields "user" has type [userType] by using GraphQLObjectTypeConfig:
// - Name: name of object type
// - Fields: a map of fields by using GraphQLFields
// Setup type of field use GraphQLFieldConfig to define:
// - Type: type of field
// - Args: arguments to query with current field
// - Resolve: function to query data using params from [Args] and return value with current type
var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"user": &graphql.Field{
Type: userType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
idQuery, isOK := p.Args["id"].(string)
if isOK {
if v, exist := data[idQuery]; exist {
return v, nil
}

return nil, nil
}
return nil, nil
},
},
},
})

var schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: queryType,
},
)

func executeQuery(query string, schema graphql.Schema, vars map[string]interface{}) \*graphql.Result {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: vars,
})
if len(result.Errors) > 0 {
fmt.Printf("wrong result, unexpected errors: %v", result.Errors)
}
return result
}

func main() {
query := `query userinfo($uid: String = "1") {
user(id: $uid){
id
name
}
}
`
vars := map[string]interface{}{"uid": "3"}
r := executeQuery(query, schema, vars)
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON)
}

上面示例是查询用户信息,作为第一个示例比较适合。示例中代码schema, _ = graphql.NewSchema定义了查询schema, 使用executeQuery查询结果并打印。schema中定义了查询入口Query: queryType,queryType中的Resolve处理请求。

点击源码,运行go run main.go查看结果。

提供GraphQL Server http服务

构建http服务只需简单几行代码(源码):

1
2
3
4
5
6
7
8
9
10
func main() {
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
result := executeQuery(r.URL.Query().Get("query"), schema, nil)
json.NewEncoder(w).Encode(result)
})

fmt.Println("Now server is running on port 8080")
fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(id:\"1\"){name}}'")
http.ListenAndServe(":8080", nil)
}

终端输入curl -g 'http://localhost:8080/graphql?query={user(id:\"1\"){name}}'查看结果吧。

使用graphql-go/handler包提供http服务

使用graphql-go/handler包(源码):

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
})
http.Handle("/graphql", h)

fmt.Println("Now server is running on port 8080")
fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(id:\"1\"){name}}'")
http.ListenAndServe(":8080", nil)
}

使用graphql-go/handler包后不需要executeQuery函数,并提供了额外的配置项。

neelance/graphql-go库实现

neelance/graphql-go支持查询解析器和GraphQL SDL解析。这里使用官方的starwars作为示例,查看starwars示例

实现服务端按下面步骤实现

  1. 编写GraphQL SDL 源码
  2. 编写GraphQL SDL实现 源码
  3. 编写GraphQL Server实现 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var schema *graphql.Schema

func init() {
schema = graphql.MustParseSchema(starwars.Schema, &starwars.Resolver{})
}

func main() {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(page)
}))

http.Handle("/query", &relay.Handler{Schema: schema})

fmt.Println("Now server is running on port 8080.")
fmt.Println("Test with Get : http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

init()函数中初始化schema,main()中监听8080端口出来请求并响应。starwars包下的schema定义和逻辑实现可查看源码

graphql-go-tools工具

neelance的实现需要两部分schema的定义与Reolver的实现。graphql-go-tools包是我自己写的将定义的schema自动生成Reolver代码模板。目前只实现了将graphql文件转换为schema string,后续整理后持续实现。
示例代码:graphql-go-tools

Dataloader缓存

dataloader是用于缓存数据的包,GraphQL是支持多层嵌套结构的(图关系,树结构),缓存数据可以减少请求次数提高性能。

GraphQL工具

chrome extension

  • GraphQL Network
  • Apollo Client Developer Tools
  • ChromeiQL

mac tools

  • GraphQL Ide
  • GraphQL PalyGround
  • GraphiQL

推荐使用GraphQL Ide,它有类似postman的collection功能,方便保存请求接口。也可以在程序debug模式中启用内置GraphiQL。