GraphQL

GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

不要因为名字里带个QL就把它和数据库联想起来了,无论如何QL只代表它是一门查询语言(Query Language),它根本不能直接碰数据库。

(你看看yaml,就和html长得一点都不像对不对,名字不重要。)

大体来说,GraphQL是想要做掉Restful API的。

GraphQL是一门语言,有一定的复杂性,在此不讲解其语法,而只做概念上的讲解。

要学习其语法的同学,右转官网教程

GraphQL的运作过程

主要是:

  • 后端搭建GraphQL服务
  • 前端向这个服务发送GraphQL查询字符串
  • 这个服务向后端其他部分查询一整个GraphQL中的“对象”(这个操作可能会经过从数据库中查询值然后拼装成对象这种)
  • GraphQL服务返回(且仅返回)前端请求的值(常见用法是包裹在JSON中返回)

Route

当然除了查询之外,GraphQL也提供了inputmutation来创建、修改数据。用法仍然见官网教程

前端使用GraphQL

没啥好说的,直接向后端开的GraphQL服务上发送查询字符串,然后等着收对应的JSON数据就行。

至于哪种(JSON?Text?FormData?)这个没有规定,和后端约好了就行。

如果有鉴权相关的东西,比如JWT Token,像平常一样发给后端就行了。

不过,前端确实需要掌握GraphQL的语法……所以想用的前端可以去看一下……

后端使用GraphQL(Go)

这可是要了人老命了。

看看JS、Python里GraphQL的优雅实现,用Go的只能感叹当初为什么要选Go……

(不过你们这些用 JS、Python 的人一定会在性能上被Go用户打爆的😁)

(喂,访问量根本到不了那个地步好吧……)

不管怎么样,自己选的技术栈跪着也要用完。

要使用的第三方库

目前主流的Go+GraphQL库是:graphql-go

用过Go的都知道怎么安装,go get一把梭就是。

定义GraphQL类型

graphql.NewObject来定义GraphQL类型。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var UserInfoType = graphql.NewObject(graphql.ObjectConfig{
Name: "user",
Description: "用户信息描述",
// GraphQL类型user的ID字段
Fields: graphql.Fields{
"Id": &graphql.Field{
Description: "用户ID",
Type: graphql.Int,
},
"CardId": &graphql.Field{
Description: "用户校园卡号",
Type: graphql.String,
},
"NickName": &graphql.Field{
Description: "用户昵称",
Type: graphql.String,
},
},
})

其实这样就很不舒服,要是能像json和orm一样在定义struct的时候用反引号写出来就好了。

定义GraphQL查询

同样用GraphQL.newObject来定义:

如:

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
var QueryType = graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"UserInfo": &graphql.Field{
Description: "[用户管理] 获取指定用户的信息",
Type: UserInfoType,
Args: graphql.FieldConfigArgument{
// 要求前端查询时使用的参数
// 例如这个查询就要求前端写:
// UserInfo(userID:2) { ... }
"userID": &graphql.ArgumentConfig{
Description: "用户ID",
Type: graphql.NewNonNull(graphql.Int),
},
},
// 实际的查询逻辑
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 如果要做鉴权,可以在这里做
// 如获取jwt token,可以
// fmt.Println(p.Context.Value("token"))
// 这里的token要从外面传入
// 下面会说
userId := p.Args["userID"]
// 这里使用beego的orm,换成其他orm或者raw SQL甚至从内存中取数据都可以
orm_ := orm.NewOrm()
user := User{Id: uint64(userId.(int))}
orm_.Read(&user)
// 注意这里,不需要管前端问我们要的是Id字段、CardId字段还是两个都要
// 我们返回的就只是一个user对象而已
return user, nil
},
},
},
})

定义GraphQL修改

同样用GraphQL.newObject来定义:

如:

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
var MutationType = graphql.NewObject(
graphql.ObjectConfig{
Name: "userMutation",
Fields: graphql.Fields{
"addUser": &graphql.Field{
Type: InfoType,
Args: graphql.FieldConfigArgument{
"CardId": &graphql.ArgumentConfig{
Description: "用户校园卡号",
Type: graphql.String,
},
"NickName": &graphql.ArgumentConfig{
Description: "用户昵称",
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
newUser := User{
CardId: p.Args["CardId"].(string),
NickName: p.Args["NickName"].(string),
}
orm_ := orm.NewOrm()
orm_.Insert(&newUser)
return newUser, nil
},
},
},
},
)

使用

先定义Schema:

1
2
3
4
5
6
var Schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: models.QueryType,
Mutation: models.MutationType,
},
)

再写HTTPHandlerFuction:

1
2
3
4
5
6
7
8
9
10
11
12
func(w http.ResponseWriter, r *http.Request) {
requestContent, _ := ioutil.ReadAll(r.Body)
// 提取jwt
token, _ := request.AuthorizationHeaderExtractor.ExtractToken(r)
result := graphql.Do(graphql.Params{
Schema: Schema,
RequestString: string(requestContent),
// 这里是插入jwt用于鉴权的操作
Context: context.WithValue(context.Background(), "token", token),
})
json.NewEncoder(w).Encode(result)
}

即可用ServeMux的方式serve出去。