学习笔记-GraphQL

image.png

背景

在开发过程中可能你会遇到这样的情况,打开你的应用主页,需要去加载个人信息,签到记录,你发表的文章列表,文章里面的评论记录,评论里面的用户头像等一系列关联的数据。一次性需要发送很多次请次,你可能会问那我把这些数据全部放在一个接口里不就好了嘛整那么多花里胡哨的干嘛?确实是可以,但是这无疑增加了后端的工作量且降低接口的稳定性,而现有的接口也能满足你的需求。

简介

基于上面存在的问题GraphQL应运而生,GraphQL能很好的解决上面的问题,前提是我们已经根据关联关系(图:节点+边)的数据保存,通过GraphQL的描述就可以把数据查询出,如果有需要增加新的节点和边只需要更改GraphQL的描述即可,无需修改API。

官方对GraphQL的解释是“GraphQL是API的查询语言”,API的查询语言到底是什么东西?

我们可以简单的理解为其实就是前端按需拼接对应查询的查询语句,后端直接一次性返回所需数据,无需像rustful需要请求不同的接口拿到不同的数据

对比

  • rustful

  • graphql

定义

graphql的schema拥有自己的一套DDL

https://graphql.org/learn/schema/

语法

字段Fields

在查询数据时确定所需的字段

1
2
3
4
5
6
7
8
9
10
query findTodos {#查询名称任意
todos {#对应提供的查询接口
text#选择的字段
done#选择的字段
user {#选择的嵌套数据字段
name
id
}
}
}

参数Arguments

在查询过程中可能需要传入一些参数进行查询

1
2
3
4
5
6
7
8
9
10
query findTodos {
gets(id:1) {#参数名和参数值
text
done
user {
name
id
}
}
}

别名Aliases

可以通过别名来标记不同数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
query findTodosAlias {
one:gets(id:1) {
text
done
user {
name
id
}
}
two:gets(id:2) {
text
done
user {
name
id
}
}
}

片段Fragments

片段的作用就是为了提高其复用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
query findTodosAlias {
one:gets(id:1) {
...todofra
}
two:gets(id:2) {
...todofra
}
}

fragment todofra on Todo {
text
done
user {
name
id
}
}

需要参数的片段Fragment

内联片段Inline Fragments

内联片段的应用就是在一个对象里面具体查询,哪个片段里面有值就会返回他对应的fragment,例如我们在查询消息时,普通消息返回文本的fragment,如果是图片则返回url的fragment

操作名称Operation Name

query或者mutations后面紧跟着的就是操作名称

1
2
3
4
5
6
7
8
9
10
query findTodos {#查询名称任意
todos {#对应提供的查询接口
text#选择的字段
done#选择的字段
user {#选择的嵌套数据字段
name
id
}
}
}

变量Variables

graphql中支持定义变量然后在查询中进行使用

1
2
3
4
5
6
7
8
9
10
query findTodosVar($id:Int) {#定义
gets(id:$id) {#使用
text
done
user {
name
id
}
}
}
1
{"id":1}

带默认值的

1
2
3
4
5
6
7
8
9
10
query findTodosVar($id:Int=10) {
gets(id:$id) {
text
done
user {
name
id
}
}
}

指令Directives

该功能主要是用了在Query中做逻辑判断

1
2
3
4
5
6
7
8
9
10
query findTodosDirectives($id:Int,$withUser:Boolean!) {
gets(id:$id) {
text
done
user @include(if: $withUser){
name
id
}
}
}
1
2
3
4
{
"id":1,
"withUser":true
}

支持的指令

  • @include(if: Boolean) 只有参数为true时才会包含所选择的字段
  • @skip(if: Boolean) 只有参数为true时才会忽略所选择的字段

转变Mutations

mutation主要是用来对数据进行修改的操作,和查询类似,如果新增或者修改后返回数据对象,我们也可以像query那样进行操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mutation createTodo {
createTodo(input: { text: "abcdadfadfasdfasa", userId: "2" }) {
...todofra
}
}

mutation createTodoVar($text:String!,$id:String!) {
createTodo(input: { text: $text, userId: $id }) {
...todofra
}
}


fragment todofra on Todo {
text
done
user {
name
id
}
}
1
2
3
4
{
"id":1,
"text": "some text"
}

更多内容参考:https://graphql.org/learn/queries/

案例

笔者使用的是gqlgen库来实现的具体可以参考文末的资料链接

  1. 获取依赖

    1
    go get -d github.com/99designs/gqlgen
  2. 进入我们的项目根目录进行初始化

    1
    go run github.com/99designs/gqlgen init
  3. 此时会生成一些配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ├── go.mod
    ├── go.sum
    ├── gqlgen.yml - The gqlgen config file, knobs for controlling the generated code.
    ├── graph
    │ ├── generated - A package that only contains the generated runtime
    │ │ └── generated.go
    │ ├── model - A package for all your graph models, generated or otherwise
    │ │ └── models_gen.go
    │ ├── resolver.go - The root graph resolver type. This file wont get regenerated
    │ ├── schema.graphqls - Some schema. You can split the schema into as many graphql files as you like
    │ └── schema.resolvers.go - the resolver implementation for schema.graphql
    └── server.go - The entry point to your app. Customize it however you see fit
  4. 配置对应的操作接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
    todo := &model.Todo{
    Text: input.Text,
    ID: fmt.Sprintf("T%d", rand.Intn(100)),
    User: &model.User{ID: input.UserID, Name: "user " + input.UserID},
    }
    r.todos = append(r.todos, todo)
    return todo, nil
    }

    func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
    return r.todos, nil
    }
  5. 运行服务器

    1
    go run server.go
  6. 打开 GraphQL playground

    1
    localhost:8080
  7. 执行简单查询和创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    mutation createTodo {
    createTodo(input: { text: "todo", userId: "10" }) {
    user {
    id
    }
    text
    done
    }
    }

    query findTodos {
    todos {
    text
    done
    user {
    name
    }
    }
    }
  8. 修改schema.graphqls后重新生成

    1
    go run github.com/99designs/gqlgen generate

资料

官网 https://graphql.org
案例 https://github.com/graphql-java-kickstart/graphql-spring-boot
案例 https://github.com/graphql-go/graphql/tree/master/examples/
https://gqlgen.com/
https://graphql.org/learn/schema/


学习笔记-GraphQL
https://mikeygithub.github.io/2022/03/01/yuque/学习笔记-GraphQL/
作者
Mikey
发布于
2022年3月1日
许可协议