Go GraphQL 教程

https://juejin.im/post/5d4051985188255d803f8234

Go GraphQL 教程

大家好,我叫谢伟,是一名程序员。

今天的主题:Go GraphQL 教程。

RESTful API 设计

一般的 Web 开发都是使用 RESTful 风格进行API的开发,这种 RESTful 风格的 API 开发的一般流程是:

  • 需求分析
  • 模型设计
  • 编码实现
    • 路由设计:
    • 参数操作:校验、请求
    • 响应:JSON 格式、状态码

一种资源一般都可以抽象出 4 类路由,比如投票接口:

  1. # 获取所有投票信息
  2. GET /v1/api/votes
  3. # 获取单个投票信息
  4. GET /v1/api/vote/{vote_id}
  5. # 创建投票
  6. POST /v1/api/vote
  7. # 更新投票
  8. PATCH /v1/api/vote/{vote_id}
  9. # 删除投票
  10. DELETE /v1/api/vote/{vote_id}

分别对应资源的获取、创建、更新、删除。

对于后端开发人员而言,重要的是在满足需求的前提下设计这类 API。

设计这类 API 一般需要处理这些具体的问题:

  • 根据需求进行模型设计:即 model 层,模型设计核心对应数据库表,所以又需要根据需求,设计字段、字段类型、表的多对多等关系
  • 抽象出资源实体,进行资源的增删改查操作
  • 返回JSON 格式的响应、状态码、或者错误信息

前端或者客户端,根据具体的需求,调用接口,对接口返回的字段进行处理。尽管有时候需求并不需要所有字段,又或者有时候需求需要 调用多个接口,组装成一个大的格式,以完成需求。

后端抽象出多少实体,对应就会设计各种资源实体的接口。后续需求变更,为了兼容,需要维护越来越多的接口。

看到没,这类的接口设计:

  • 需要维护多类接口,需求不断变更,维护的接口越来越多
  • 字段的获取,前端或者客户端不能决定,而是一股脑的返回,再由相应开发人员处理
  • 需要考虑接口版本 …

GraphQL API

GraphQL 是一种专门用于API 的查询语言,由大厂 Facebook 推出,但是至今 GraphQL 并没有引起广泛的使用, 绝大多少还是采用 RESTful API 风格的形式开发。

GraphQL 尝试解决这些问题:

  • 查询语法和查询结果高度相似
  • 根据需求获取字段
  • 一个路由能获取多个请求的结果
  • 无需接口版本管理

1

既然是一种专门用于 API 的查询语言,其必定有一些规范或者语法约束。具体 GraphQL 包含哪些知识呢?

  • Schema 是类型语言的合集,定义了具体的操作(比如:请求、更改),和对象信息(比如:响应的字段)

schema.graphql

  1. type Query {
  2. ping(data: String): Pong
  3. }
  4. type Mutation {
  5. createVote(name: String!): Vote
  6. }
  7. type Pong{
  8. data: String
  9. code: Int
  10. }
  11. type Vote {
  12. id: ID!
  13. name: String!
  14. }

具体定义了请求合集:Query, 更改或者创建合集:Mutation,定义了两个对象类型:Pong, Vote , 对象内包含字段和类型。

这个schema 文件,是后端开发人员的开发文档,也是前端或者客户端人员的 API 文档。

假设,后端开发人员依据 schema 文件,已经开发完毕,那么如何调用 API 呢?

推荐使用:PostMan

  1. # ping 请求动作
  2. query {
  3. ping{
  4. data
  5. code
  6. }
  7. }
  1. # mutation 更改动作
  2. mutation {
  3. createVote(name:"have a lunch") {
  4. id
  5. name
  6. }
  7. }

能发现一些规律么?

  • schema 文件几乎决定了请求的具体形式,请求什么格式,响应什么格式
  • API 请求动作包括:操作类型(query, mutation, subscription)、操作名称、请求名称、请求字段
  1. query HeartBeat {
  2. ping{
  3. data
  4. code
  5. }
  6. }
  • 操作类型: query
  • 操作名称: HeartBeat (操作名称一般省略)
  • 请求名称: ping
  • 响应字段:Pong 对象的字段 data、code

GraphQL 是一种专门用于 API 的查询语言,有语法约束。

具体包括:

  • 别名:字段或者对象重命名、主要为解决冲突问题
  • 片段:简单来说,就是提取公共字段,方便复用
  • 变量:请求参数以变量的形式
  • 指令:根据条件动态显示字段:@include 是否包含该字段、@skip 是否不包含该字段、@deprecate 是否废弃该字段
  • 内联片段:接口类型或者联合类型中获取下层字段
  • 元字段
  • 类型定义、对象定义
  • 内置的类型:ID、Int、Float、String、Boolean, 其他类型使用基本类型构造对象类型即可
  • 枚举:可选值的集合
  • 修饰符: ! 表示非空
  • 接口:interface
  • 联合类型: | 通过对象类型组合而成
  • 输入类型: 为解决传递复杂参数的问题

讲了这么些,其实最好的方式还是亲自调用下接口,参照着官方文档,按个调用尝试下,熟悉这套语法规范。

最佳的当然是:Github 的 GraphQL API4 (developer.github.com/v4/)

  • 熟络 GraphQL 语法规范
  • 学习 GraphQL 设计规范

登入自己的账号:访问:developer.github.com/v4/explorer…

仅举几个示例:

0. viewer: User!

  • 请求名称:viewer
  • 响应对象:User 非空,即一定会返回一个 User 对象,User 对象由一系列字段、对象组成

1. 基本请求动作

  1. {
  2. viewer {
  3. __typename
  4. ... on User {
  5. name
  6. }
  7. }
  8. }
  9. // 结果
  10. {
  11. "data": {
  12. "viewer": {
  13. "__typename": "User",
  14. "name": "XieWei"
  15. }
  16. }
  17. }

2. 别名

  1. {
  2. AliasForViewer:viewer {
  3. __typename
  4. ... on User {
  5. name
  6. }
  7. }
  8. }
  9. # 结果
  10. {
  11. "data": {
  12. "AliasForViewer": {
  13. "__typename": "User",
  14. "name": "XieWei"
  15. }
  16. }
  17. }

3.操作名称,变量,指令

  1. query PrintViewer($Repository: String!,$Has: Boolean!){
  2. AliasForViewer:viewer{
  3. __typename
  4. ... on User {
  5. name
  6. }
  7. url
  8. status{
  9. createdAt
  10. emoji
  11. id
  12. }
  13. repository(name: $Repository) {
  14. name
  15. createdAt
  16. description @include(if:$Has)
  17. }
  18. }
  19. }
  20. # 变量
  21. {
  22. "Repository": "2019-daily",
  23. "Has": false
  24. }
  25. # 结果
  26. {
  27. "data": {
  28. "AliasForViewer": {
  29. "__typename": "User",
  30. "name": "XieWei",
  31. "url": "https://github.com/wuxiaoxiaoshen",
  32. "status": null,
  33. "repository": {
  34. "name": "2019-daily",
  35. "createdAt": "2019-01-11T15:17:43Z"
  36. }
  37. }
  38. }
  39. }
  40. # 如果变量为:
  41. {
  42. "Repository": "2019-daily",
  43. "Has": true
  44. }
  45. # 则结果为
  46. {
  47. "data": {
  48. "AliasForViewer": {
  49. "__typename": "User",
  50. "name": "XieWei",
  51. "url": "https://github.com/wuxiaoxiaoshen",
  52. "status": null,
  53. "repository": {
  54. "name": "2019-daily",
  55. "createdAt": "2019-01-11T15:17:43Z",
  56. "description": "把2019年的生活过成一本书"
  57. }
  58. }
  59. }
  60. }

对照着文档多尝试。

上文多是讲述使用 GraphQL 进行查询操作时的语法。

2

schema 是所有请求、响应、对象声明的集合,对后端而言,是开发依据,对前端而言,是 API 文档。

如何定义 schema ?

你只需要知道这些内容即可:

  • 内置的标量类型:ID(实质是字符串,唯一标识符)、Boolean、String、Float
  • 修饰符 ! 表示非空
  • 对象类型:type 关键字
  • 枚举类型:enum 关键字
  • 输入类型:input 关键字

举一个具体的示例:小程序: 腾讯投票

首页






详情






Step1: 定义类型对象的字段

定义的类型对象和响应的字段设计几乎保持一致。

  1. # 类似于 map, 左边表示字段名称,右边表示类型
  2. # [] 表示列表
  3. # ! 修饰符表示非空
  4. type Vote {
  5. id: ID!
  6. createdAt: Time
  7. updatedAt: Time
  8. deletedAt: Time
  9. title: String
  10. description: String
  11. options: [Options!]!
  12. deadline: Time
  13. class: VoteClass
  14. }
  15. type Options {
  16. name: String
  17. }
  18. # 输入类型: 一般用户更改资源中的输入是列表对象,完成复杂任务
  19. input optionsInput {
  20. name:String!
  21. }
  22. # 枚举类型:投票区分:单选、多选两个选项值
  23. enum VoteClass {
  24. SINGLE
  25. MULTIPLE
  26. }
  27. # 自定义类型,默认类型(ID、String、Boolean、Float)不包含 Time 类型
  28. scalar Time
  29. # 对象类型,用于检查服务是否完好
  30. type Ping {
  31. data: String
  32. code: Int
  33. }

Step2: 定义操作类型:Query 用于查询,Mutation 用于创建、更改、删除资源

  1. # Query、Mutation 关键字固定
  2. # 左边表示操作名称,右边表示返回的值的类型
  3. # Query 一般完成查询操作
  4. # Mutation 一般完成资源的创建、更改、删除操作
  5. type Query {
  6. ping: Ping
  7. pinWithData(data: String): Ping
  8. vote(id:ID!): Vote
  9. }
  10. type Mutation {
  11. createVote(title:String!, options:[optionsInput],deadline:Time, description:String, class:VoteClass!): Vote
  12. updateVote(title:String!, description:String!): Vote
  13. }

schema 完成了对对象类型的定义和一些操作,是后端开发者的开发文档,是前端开发者的API文档。

3

客户端如何使用:Go : (graphql-go)

主题: 小程序腾讯投票

Step0: 项目结构

  1. ├── Makefile
  2. ├── README.md
  3. ├── cmd
  4. ├── root_cmd.go
  5. └── sync_cmd.go
  6. ├── main.go
  7. ├── model
  8. └── vote.go
  9. ├── pkg
  10. ├── database
  11. └── database.go
  12. └── router
  13. └── router.go
  14. ├── schema.graphql
  15. ├── script
  16. └── db.sh
  17. └── web
  18. ├── mutation
  19. └── mutation_type.go
  20. ├── ping
  21. └── ping_query.go
  22. ├── query
  23. └── query_type.go
  24. └── vote
  25. ├── vote_curd.go
  26. ├── vote_params.go
  27. └── vote_type.go
  • cmd: 命令行文件:主要用于同步数据库表结构
  • main.go 函数主入口
  • model 模型定义,每种资源单独一个文件 比如 vote.go
  • pkg 基础设施:数据库连接、路由设计
  • web 核心业务路径,总体上按资源划分文件夹
    • vote
      • vote_curd.go 资源的增删改查
      • vote_params.go 请求参数
      • vote_type.go schema 中资源,即类型对象的定义
    • query
      • query.go
    • mutation
      • mutation.go

和之前的 RESTful API 的设计项目的结构基本保持一致。

Step1: 依据Schema 的定义:完成数据库模型定义

  1. type base struct {
  2. Id int64 `xorm:"pk autoincr notnull" json:"id"`
  3. CreatedAt time.Time `xorm:"created" json:"created_at"`
  4. UpdatedAt time.Time `xorm:"updated" json:"updated_at"`
  5. DeletedAt *time.Time `xorm:"deleted" json:"deleted_at"`
  6. }
  7. const (
  8. SINGLE = iota
  9. MULTIPLE
  10. )
  11. var ClassMap = map[int]string{}
  12. func init() {
  13. ClassMap = make(map[int]string)
  14. ClassMap[SINGLE] = "SINGLE"
  15. ClassMap[MULTIPLE] = "MULTIPLE"
  16. }
  17. type Vote struct {
  18. base `xorm:"extends"`
  19. Title string `json:"title"`
  20. Description string `json:"description"`
  21. OptionIds []int64 `json:"option_ids"`
  22. Deadline time.Time `json:"deadline"`
  23. Class int `json:"class"`
  24. }
  25. type VoteSerializer struct {
  26. Id int64 `json:"id"`
  27. CreatedAt time.Time `json:"created_at"`
  28. UpdatedAt time.Time `json:"updated_at"`
  29. Title string `json:"title"`
  30. Description string `json:"description"`
  31. Options []OptionSerializer `json:"options"`
  32. Deadline time.Time `json:"deadline"`
  33. Class int `json:"class"`
  34. ClassString string `json:"class_string"`
  35. }
  36. func (V Vote) TableName() string {
  37. return "votes"
  38. }
  39. func (V Vote) Serializer() VoteSerializer {
  40. var optionSerializer []OptionSerializer
  41. var options []Option
  42. database.Engine.In("id", V.OptionIds).Find(&options)
  43. for _, i := range options {
  44. optionSerializer = append(optionSerializer, i.Serializer())
  45. }
  46. classString := func(value int) string {
  47. if V.Class == SINGLE {
  48. return "单选"
  49. }
  50. if V.Class == MULTIPLE {
  51. return "多选"
  52. }
  53. return ""
  54. }
  55. return VoteSerializer{
  56. Id: V.Id,
  57. CreatedAt: V.CreatedAt.Truncate(time.Second),
  58. UpdatedAt: V.UpdatedAt.Truncate(time.Second),
  59. Title: V.Title,
  60. Description: V.Description,
  61. Options: optionSerializer,
  62. Deadline: V.Deadline,
  63. Class: V.Class,
  64. ClassString: classString(V.Class),
  65. }
  66. }
  67. type Option struct {
  68. base `xorm:"extends"`
  69. Name string `json:"name"`
  70. }
  71. type OptionSerializer struct {
  72. Id int64 `json:"id"`
  73. CreatedAt time.Time `json:"created_at"`
  74. UpdatedAt time.Time `json:"updated_at"`
  75. Name string `json:"name"`
  76. }
  77. func (O Option) TableName() string {
  78. return "options"
  79. }
  80. func (O Option) Serializer() OptionSerializer {
  81. return OptionSerializer{
  82. Id: O.Id,
  83. CreatedAt: O.CreatedAt.Truncate(time.Second),
  84. UpdatedAt: O.UpdatedAt.Truncate(time.Second),
  85. Name: O.Name,
  86. }
  87. }

依然保持了个人的模型设计风格:

  • 定义一个结构体,对应数据库表
  • 定义个序列化结构体,对应模型的响应
  • 单选、多选项,实质在数据库中用0,1 表示,响应显示中文:单选、多选

Step2: query.go 文件描述

  1. var Query = graphql.NewObject(graphql.ObjectConfig{
  2. Name: "Query",
  3. Fields: graphql.Fields{
  4. "ping": &graphql.Field{
  5. Type: ping.Ping,
  6. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  7. return ping.Default, nil
  8. },
  9. },
  10. },
  11. })
  12. func init() {
  13. Query.AddFieldConfig("pingWithData", &graphql.Field{
  14. Type: ping.Ping,
  15. Args: graphql.FieldConfigArgument{
  16. "data": &graphql.ArgumentConfig{
  17. Type: graphql.NewNonNull(graphql.String),
  18. },
  19. },
  20. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  21. if p.Args["data"] == nil {
  22. return ping.Default, nil
  23. }
  24. return ping.MakeResponseForPing(p.Args["data"].(string)), nil
  25. },
  26. })
  27. }
  28. func init() {
  29. Query.AddFieldConfig("vote", &graphql.Field{
  30. Type: vote.Vote,
  31. Args: graphql.FieldConfigArgument{
  32. "id": &graphql.ArgumentConfig{
  33. Type: graphql.NewNonNull(graphql.ID),
  34. },
  35. },
  36. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  37. id := p.Args["id"]
  38. ID, _ := strconv.Atoi(id.(string))
  39. return vote.GetOneVote(int64(ID))
  40. },
  41. })
  42. }

基本和 schema 文件中 Query 定义一致:

  1. type Query {
  2. ping: Ping
  3. pinWithData(data: String): Ping
  4. vote(id:ID!): Vote
  5. }
  • Fields 表示对象字段
  • Type 表示返回类型
  • Args 表示参数
  • Resolve 表示具体的处理函数

内置类型:(ID, String, Boolean, Float)

  1. - graphql.ID
  2. - graphql.String
  3. - graphql.Boolean
  4. - graphql.Float
  5. ...

简单的说:所有的对象、字段都需要有处理函数。

  1. var Query = graphql.NewObject(graphql.ObjectConfig{
  2. Name: "Query",
  3. Fields: graphql.Fields{
  4. "ping": &graphql.Field{
  5. Type: ping.Ping,
  6. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  7. return ping.Default, nil
  8. },
  9. },
  10. },
  11. })
  12. func init() {
  13. Query.AddFieldConfig("pingWithData", &graphql.Field{
  14. Type: ping.Ping,
  15. Args: graphql.FieldConfigArgument{
  16. "data": &graphql.ArgumentConfig{
  17. Type: graphql.NewNonNull(graphql.String),
  18. },
  19. },
  20. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  21. if p.Args["data"] == nil {
  22. return ping.Default, nil
  23. }
  24. return ping.MakeResponseForPing(p.Args["data"].(string)), nil
  25. },
  26. })
  27. }
  28. var Ping = graphql.NewObject(graphql.ObjectConfig{
  29. Name: "ping",
  30. Fields: graphql.Fields{
  31. "data": &graphql.Field{
  32. Type: graphql.String,
  33. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  34. if response, ok := p.Source.(ResponseForPing); ok {
  35. return response.Data, nil
  36. }
  37. return nil, fmt.Errorf("field not found")
  38. },
  39. },
  40. "code": &graphql.Field{
  41. Type: graphql.String,
  42. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  43. if response, ok := p.Source.(ResponseForPing); ok {
  44. return response.Code, nil
  45. }
  46. return nil, fmt.Errorf("field not found")
  47. },
  48. },
  49. },
  50. })
  51. type ResponseForPing struct {
  52. Data string `json:"data"`
  53. Code int `json:"code"`
  54. }
  55. var Default = ResponseForPing{
  56. Data: "pong",
  57. Code: http.StatusOK,
  58. }
  59. func MakeResponseForPing(data string) ResponseForPing {
  60. return ResponseForPing{
  61. Data: data,
  62. Code: http.StatusOK,
  63. }
  64. }

使用 Go Graphql-go 客户端,绝大多数工作都在定义对象、定义字段类型、定义字段的处理函数等。

  • graphql.Object
  • graphql.InputObject
  • graphql.Enum

Step3: mutation.go 文件描述

  1. var Mutation = graphql.NewObject(graphql.ObjectConfig{
  2. Name: "Mutation",
  3. Fields: graphql.Fields{
  4. "createVote": &graphql.Field{
  5. Type: vote.Vote,
  6. Args: graphql.FieldConfigArgument{
  7. "title": &graphql.ArgumentConfig{
  8. Type: graphql.NewNonNull(graphql.String),
  9. },
  10. "options": &graphql.ArgumentConfig{
  11. Type: graphql.NewNonNull(graphql.NewList(vote.OptionInput)),
  12. },
  13. "description": &graphql.ArgumentConfig{
  14. Type: graphql.String,
  15. },
  16. "deadline": &graphql.ArgumentConfig{
  17. Type: graphql.NewNonNull(graphql.String),
  18. },
  19. "class": &graphql.ArgumentConfig{
  20. Type: graphql.NewNonNull(vote.Class),
  21. },
  22. },
  23. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  24. log.Println(p.Args)
  25. var params vote.CreateVoteParams
  26. params.Title = p.Args["title"].(string)
  27. if p.Args["description"] != nil {
  28. params.Description = p.Args["description"].(string)
  29. }
  30. params.Deadline = p.Args["deadline"].(string)
  31. params.Class = p.Args["class"].(int)
  32. var options []vote.OptionParams
  33. for _, i := range p.Args["options"].([]interface{}) {
  34. var one vote.OptionParams
  35. k := i.(map[string]interface{})
  36. one.Name = k["name"].(string)
  37. options = append(options, one)
  38. }
  39. params.Options = options
  40. log.Println(params)
  41. result, err := vote.CreateVote(params)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return result, nil
  46. },
  47. },
  48. "updateVote": &graphql.Field{
  49. Type: vote.Vote,
  50. Args: graphql.FieldConfigArgument{
  51. "title": &graphql.ArgumentConfig{
  52. Type: graphql.NewNonNull(graphql.String),
  53. },
  54. "description": &graphql.ArgumentConfig{
  55. Type: graphql.NewNonNull(graphql.String),
  56. },
  57. "id": &graphql.ArgumentConfig{
  58. Type: graphql.NewNonNull(graphql.ID),
  59. },
  60. },
  61. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  62. var params vote.UpdateVoteParams
  63. id := p.Args["id"]
  64. ID, _ := strconv.Atoi(id.(string))
  65. params.Id = int64(ID)
  66. params.Title = p.Args["title"].(string)
  67. params.Description = p.Args["description"].(string)
  68. return vote.UpdateOneVote(params)
  69. },
  70. },
  71. },
  72. })

Step4: 构建 schema 启动服务

  1. func RegisterSchema() *graphql.Schema {
  2. schema, err := graphql.NewSchema(
  3. graphql.SchemaConfig{
  4. Query: query.Query,
  5. Mutation: mutation.Mutation,
  6. })
  7. if err != nil {
  8. panic(fmt.Sprintf("schema init fail %s", err.Error()))
  9. }
  10. return &schema
  11. }
  12. func Register() *handler.Handler {
  13. return handler.New(&handler.Config{
  14. Schema: RegisterSchema(),
  15. Pretty: true,
  16. GraphiQL: true,
  17. })
  18. }
  19. func StartWebServer() {
  20. log.Println("Start Web Server...")
  21. http.Handle("/graphql", Register())
  22. log.Fatal(http.ListenAndServe(":7878", nil))
  23. }

Step5: 运行,接口调用

  • 只有一个路由:/graphql
  • 无需版本管理
  • 所有的请求方法都是:POST(query 动作当然也可以使用 GET,遇到请求参数较多时,不够方便)

接口调用示例:(根据查询文档,可以根据调用者的需求,自主选择响应的字段)

  1. mutation {
  2. createVote(
  3. title: "去哪玩?",
  4. description:"本次团建去哪玩?",
  5. options:[
  6. {
  7. name: "杭州西湖"
  8. },{
  9. name:"安徽黄山"
  10. },{
  11. name:"香港九龙"
  12. }
  13. ],
  14. deadline: "2019-08-01 00:00:00",
  15. class: SINGLE
  16. ) {
  17. id
  18. title
  19. deadline
  20. description
  21. createdAt
  22. updatedAt
  23. options{
  24. name
  25. }
  26. class
  27. classString
  28. }
  29. }
  30. # 结果
  31. {
  32. "data": {
  33. "vote": {
  34. "class": "SINGLE",
  35. "classString": "单选",
  36. "createdAt": "2019-07-30T19:33:27+08:00",
  37. "deadline": "2019-08-01T00:00:00+08:00",
  38. "description": "本次团建去哪玩?",
  39. "id": "1",
  40. "options": [
  41. {
  42. "name": "杭州西湖"
  43. },
  44. {
  45. "name": "安徽黄山"
  46. },
  47. {
  48. "name": "香港九龙"
  49. }
  50. ],
  51. "title": "去哪玩?",
  52. "updatedAt": "2019-07-30T19:33:27+08:00"
  53. }
  54. }
  55. }
  1. query{
  2. vote(id:1){
  3. id
  4. title
  5. deadline
  6. description
  7. createdAt
  8. updatedAt
  9. options{
  10. name
  11. }
  12. class
  13. classString
  14. }
  15. }
  16. # 结果
  17. {
  18. "data": {
  19. "createVote": {
  20. "class": "SINGLE",
  21. "classString": "SINGLE",
  22. "createdAt": "2019-07-30T19:33:27+08:00",
  23. "deadline": "2019-08-01T00:00:00+08:00",
  24. "description": "本次团建去哪玩?",
  25. "id": "1",
  26. "options": {
  27. {
  28. "name": "杭州西湖"
  29. },
  30. {
  31. "name": "安徽黄山"
  32. },
  33. {
  34. "name": "香港九龙"
  35. }
  36. },
  37. "title": "去哪玩?",
  38. "updatedAt": "2019-07-30T19:33:27+08:00"
  39. }
  40. }
  41. }

4

建议:

  • 优先设计:Schema, 指导着开发者
  • 如果请求或者更改动作过多,按功能或者资源划分(项目结构按功能划分,一定程度上有助于减轻思维负担)
  1. var Query = graphql.NewObject(graphql.ObjectConfig{}
  2. func init(){
  3. // 资源一
  4. Query.AddFieldConfig("filedsName", &graphql.Field{})
  5. }
  6. func init(){
  7. // 资源二
  8. }
  • 如何处理复杂请求参数:
  1. var Mutation = graphql.NewObject(graphql.ObjectConfig{
  2. Name: "Mutation",
  3. Fields: graphql.Fields{
  4. "createVote": &graphql.Field{
  5. Type: vote.Vote,
  6. Args: graphql.FieldConfigArgument{
  7. "title": &graphql.ArgumentConfig{
  8. Type: graphql.NewNonNull(graphql.String),
  9. },
  10. "options": &graphql.ArgumentConfig{
  11. Type: graphql.NewNonNull(graphql.NewList(vote.OptionInput)),
  12. },
  13. "description": &graphql.ArgumentConfig{
  14. Type: graphql.String,
  15. },
  16. "deadline": &graphql.ArgumentConfig{
  17. Type: graphql.NewNonNull(graphql.String),
  18. },
  19. "class": &graphql.ArgumentConfig{
  20. Type: graphql.NewNonNull(vote.Class),
  21. },
  22. },
  23. Resolve: func(p graphql.ResolveParams) (i interface{}, e error) {
  24. log.Println(p.Args)
  25. var params vote.CreateVoteParams
  26. params.Title = p.Args["title"].(string)
  27. if p.Args["description"] != nil {
  28. params.Description = p.Args["description"].(string)
  29. }
  30. params.Deadline = p.Args["deadline"].(string)
  31. params.Class = p.Args["class"].(int)
  32. var options []vote.OptionParams
  33. for _, i := range p.Args["options"].([]interface{}) {
  34. var one vote.OptionParams
  35. k := i.(map[string]interface{})
  36. one.Name = k["name"].(string)
  37. options = append(options, one)
  38. }
  39. params.Options = options
  40. log.Println(params)
  41. result, err := vote.CreateVote(params)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return result, nil
  46. },
  47. },
  48. },
  49. })

Args 定义所有该请求的字段和类型。 p.Args 类型(map[string]interface),可以获取到请求参数。返回是个 interface, 根据 Args 内定义的类型,类型转化

5

总结:本文简单讲解 GraphQL的语法和 Go 编程实现 GraphQL 操作。

建议如何学习?

<完>

ft_authoradmin  ft_create_time2019-07-31 14:49
 ft_update_time2019-07-31 14:50