go GraphQL实践(一)

graphql

相信大家对GraphQL早已不陌生,这一Facebook推出的接口查询语言,立志在简洁性和扩展性方面超越REST,并且已经被应用在很多复杂的业务场景中。GraphQL查询时结构化的,信息是类树结构展示的。值类型可以理解为叶子,对象类型可以理解为树干GraphQL是一种描述如何请求数据的语法,通常用于客户端向服务器请求数据。GraphQL层位于客户端和一个或多个数据源之间,按照你的指示接收客户端请求,然后获取必要的数据。

特点

无冗余

发出一个GraphQL请求就能准确获得你想要的数据,不多不少。GraphQL查询总是返回可预测的结果。使用GraphQL的应用可以工作得又快又稳,因为控制数据的是应用,而不是服务器。

减少请求

GraphQL查询不仅能够获得资源的属性,还能沿着资源间引用进一步查询。典型的REST API请求多个资源时得载入多个url,而GraphQL可以通过一次请求就获取你应用所需的所有数据。这样一来,即使是比较慢的移动网络连接下,使用GraphQL的应用也能表现得足够迅速。

强类型

GraphQL API是基于类型和字段的方式进行组织的。GraphQL使用强类型来保证请求数据的正确性,提前声明好类型可以避免编写代码手动进行解析。它还提供了清晰的辅助性错误信息。

向下兼容

给你的GraphQL API添加字段和类型而无需影响现有查询。老旧的字段可以废弃,从工具中隐藏。通过使用单一演进版本,GraphQL API使得应用始终能够使用新的特性,并鼓励使用更加简洁、更好维护的服务端代码。

只是一个规范

GraphQL让你的整个应用共享一套API,而不用被限制于特定存储引擎。GraphQL引擎已经有多种语言实现,通过GraphQL API能够更好利用你的现有数据和代码。你只需要为类型系统的字段编写函数,GraphQL就能通过优化并发的方式来调用它们。

go的实现

基础的实现和查询

graphql-go/graphql支持查询解析器,不支持GraphQL SDL解析,需要自己编写完整的FieldSchema等。

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
package main

import (
"fmt"

"github.com/gin-gonic/gin"

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

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",
},
}

var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
},
},
)

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, ok := data[idQuery]; ok {
return v, 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 {
res := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
VariableValues: vars,
})
if len(res.Errors) > 0 {
fmt.Printf("error: %v", res.Errors)
}
return res
}

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)
}

完整代码

http标准库

构建http服务只需添加简单几行代码。

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

完整代码
我们可以运行代码,并请求一下接口curl -g 'http://localhost:8080/graphql?query={user(id:"3"){name}}',查看结果。

使用graphql-go/handler

使用graphql-go/handler简化构建接口代码。使用graphql-go/handler包后不需要自定义graphql.Do去实现查询函数,内部已经帮你做了这些事情。之间在浏览器访问接口可以看到调试页面,可以直接在上面测试请求。

1
go get github.com/graphql-go/handler

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

完整代码

运用于gin

使用第三方web库时,只要实现一个handler的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func ginHandler() gin.HandlerFunc {
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
})

return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}

func main() {
router := gin.Default()
router.Any("/graphql", ginHandler())
router.Run(":8080")
}

完整代码

graph-gophers/graphql-go

graph-gophers/graphql-go是一个用于快速创建严格类型的GraphQL服务器库,支持查询解析器和GraphQL SDL解析。

我们基于它来写一个demo。

  1. 首先编写GraphQL SDL(Schema Definition Language) 源码

    1. 编写schema.graphql也就是SDL文件
    2. 运行go-bindata -ignore=\.go -pkg=schema -o=schema/bindata.go schema/...生成go静态代码
    3. 编写schema.go从静态代码中生成schemastring,用于graph-gophers/graphql-go解析
  2. 编写GraphQL SDL实现 源码

    • 编写Resolver需要的query方法
    • 根据query需要的返回type实现相应的type resolver
    • 编写Resolver需要的mutation方法
    • 根据mutation需要的input和返回,实现相应的数据结构和处理逻辑
    • 对接数据的存储和修改,代码里用map代替
  3. 编写GraphQL Server实现 源码

完整代码