graphql-code-generator 生成graphql

类似的工具比较多,比如prisma 、qloo、golang 的gqlgen、apollo-codegen
graphql-code-generator 也是一个不错的工具(灵活、模版自定义

npm install –save-dev graphql-code-generator graphql
Or
yarn add -D graphql-code-generator graphql
参考demo
项目初始化
yarn init -y
添加依赖
yarn add –dev graphql-code-generator graphql
package.json:



https://github.com/eatonphil/gosql
https://mp.weixin.qq.com/s/jOnqEz31rDcUxSNs_Hjwaw



GraphQL背景
REST API的使用方式是,server定义一系列的接口,client调用自己需要的接口,获取目标数据进行整合。REST API开发中遇到的问题:



扩展性 ,随着API的不断发展,REST API的接口会变得越来臃肿。
无法按需获取 ,一个返回id, name, age, city, addr, email的接口,如果仅获取部分信息,如name, age,却必须返回接口的全部信息,然后从中提取自己需要的。坏处不仅会增加网络传输量,并且不便于client处理数据
一个请求无法获取所需全部资源 ,例如client需要显示一篇文章的内容,同时要显示评论,作者信息,那么就需要调用文章、评论、用户的接口。坏处造成服务的的维护困难,以及响应时间变长 。
原因: REST API通常由多个端点组成,每个端点代表一种资源。所以,当client需要多个资源是,它需要向REST API发起多个请求,才能获取到所需要的数据。
REST API不好处理的问题 , 比如确保client提供的参数是类型安全的,如何从代码生成API的文档等。
GraphQL解决的问题:



请求你的数据不多不少 :GraphQL查询总是能准确获得你想要的数据,不多不少,所以返回的结果是可预测的。
获取多个资源只用一个请求 :GraphQL查询不仅能够获得资源的属性,还能沿着资源间进一步查询,所以GraphQL可以通过一次请求就获取你应用所需的所有数据。
描述所有的可能类型系统: GraphQL API基于类型和字段的方式进行组成,使用类型来保证应用只请求可能的类型,同时提供了清晰的辅助性错误信息。
使用你现有的数据和代码: GraphQL让你的整个应用共享一套API,通过GraphQL API能够更好的利用你的现有数据和代码。GraphQL 引擎已经有多种语言实现,GraphQL不限于某一特定数据库,可以使用已经存在的数据、代码、甚至可以连接第三方的APIs。
API 演进无需划分版本: 给GraphQL API添加字段和类型而无需影响现有查询。老旧字段可以废弃,从工具中隐藏。
什么是GraphQL
GraphQL官网给出定义:GraphQL既是一种用于API的查询语言 也是一个满足你数据查询的运行时 。GraphQL对你的API中的数据提供了一套易于理解的完整描述 ,使得客户端能够准确地获得它需要的数据 ,而且没有任何冗余,也让API更容易地随着时间推移而演进,还能用于构建强大的开发者工具。



API不是用来调用的吗?是的,者正是GraphQL的强大之处,引用官方文档的一句话ask exactly what you want
本质上来说GraphQL是一种查询语言
上述的定义其实很难理解,只有真的使用过GraphQL才能够理解。
在GraphQL中,通过定义一张Schema和声明一些Type来达到上述描述的功能,需要学习:



对于数据模型的抽象是通过Type来描述的 ,如何定义Type?
对于接口获取数据的逻辑是通过schema来描述的 ,如何定义schema?
如何定义Type
对于数据模型的抽象是通过Type来描述的,每一个Type有若干Field组成,每个Field又分别指向某个Type。



GraphQL的Type简单可以分为两种,一种是scalar type(标量类型) ,另一种是object type(对象类型)。



scalar type
GraphQL中的内建的标量包含,String、Int、Float、Boolean、Enum,除此之外,GraphQL中可以通过scalar声明一个新的标量 ,比如:



prisma ——一个使用GraphQL来抽象数据库操作的库中,还有DataTime(日期格式)和主键(ID)。
在使用GraphQL实现文件上传接口时,需要声明一个Upload标量来代表要上传的文件。
标量是GraphQL类型系统中最小的颗粒。
object type
仅有标量是不够抽象一些复杂的数据模型,这时需要使用对象类型。通过对象类型来构建GraphQL中关于一个数据模型的形状,同时还可以声明各个模型之间的内在关联(一对多,一对一或多对多)。



一对一模型



type Article {
id: ID
text: String
isPublished: Boolean
author: User
}
1
2
3
4
5
6
上述代码,声明了一个Article类型,它有3个Field,分别是id(ID类型)、text(String类型)、isPublished(Boolean类型)以及author(新建的对象类型User),User类型的声明如下:



type User {
id: ID
name: String
}
1
2
3
4
lType Modifier
类型修饰符,当前的类型修饰符有两种,分别是List和Required ,语法分别为[Type]和[Type!],两者可以组合:



[Type]! :列表本身为必填项,但内部元素可以为空
[Type!] :列表本身可以为空,但是其内部元素为必填
[Type!]! :列表本身和内部元素均为必填
如何定义Schema
schema用来描述对于接口获取数据逻辑 ,GraphQL中使用Query来抽象数据的查询逻辑,分为三种,分别是query(查询)、mutation(更改)、subscription(订阅) 。API的接口概括起来有CRUD(创建、获取、更改、删除)四类,query可以覆盖R(获取)的功能,mutation可以覆盖(CUD创建、更改、删除)的功能。



注意: Query特指GraphQL中的查询(包含三种类型),query指GraphQL中的查询类型(仅指查询类型)。



Query
query(查询):当获取数据时,选择query类型
mutation(更改): 当尝试修改数据时,选择mutation类型
subscription(订阅):当希望数据更改时,可以进行消息推送,使用subscription类型(针对当前的日趋流行的real-time应用提出的)。
以Article为数据模型,分别以REST和GraphQL的角度,编写CURD的接口



Rest接口



GET /api/v1/articles/
GET /api/v1/article/:id/
POST /api/v1/article/
DELETE /api/v1/article/:id/
PATCH /api/v1/article/:id/
GraphQL Query



query类型
query {
articles():[Article!]!
article(id: Int!): Article!
}
mutation类型
mutation {
createArticle(): Article!
updateArticle(id: Int): Article!
deleteArticle(id: Int): Article!
}
注意:



GraphQL是按照类型来划分职能的query、mutation、ssubscription,同时必须明确声明返回的数据类型。



如果实际应用中对于评论列表有real-time 的需求,该如何处理?



在REST中,可以通过长连接,或者通过提供一些带验证的获取长连接URL的接口,比如POST /api/v1/messages/之后长连接会将新的数据进行实时推送。



在GraphQL中,会以更加声明式的方式进行声明,如下:



subscription {
updatedArticle() {
mutation
node {
comments: [Comment!]!
}
}
}
1
2
3
4
5
6
7
8
此处声明了一个subscription,这个subscription会在有新的Article被创建或者更新时,推送新的数据对象。实际上内部仍然是建立于长连接之上 。



Resolve
上述的描述并未说明如何返回相关操作(query、mutation、subscription)的数据逻辑。所有此处引入一个更核心的概念Resolve(解析函数)



GraphQL中,默认有这样的约定,Query(包括query、mutation、subscription)和与之对应的Resolve是同名的,比如关于articles(): [Articles!]!这个query,它的Resolve的名字必然叫做articles



以已经声明的articles的query为例,解释下GraphQL的内部工作机制:



Query {
articles {
id
author {
name
}
comments {
id
desc
author
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
按照如下步骤进行解析:



首先进行第一次解析,当前的类型是query 类型,同时Resolver的名字为articles。
之后会尝试使用articles的Resolver获取解析数据,第一层解析完毕
之后对第一层解析的返回值,进行第二层解析,当前articles包含三个子query ,分别是id、author和comments
id在Author类型中为标量类型,解析结束
author在articles类型中为对象类型User,尝试使用User的Resolver获取数据,当前field解析完毕。
之后对第二层解析的返回值,进行第三层解析,当前author还包含一个query,name是标量类型,解析结束
comments解析同上
概括总结GraphQL大体解析流程就是遇见一个Query之后,尝试使用它的Resolver取值,之后再对返回值进行解析,这个过程是递归的,直到所有解析Field类型是Scalar Type(标量类型)为止。整个解析过程可以想象为一个很长的Resolver Chain(解析链)。



Resolver本身的声明在各个语言中是不同的,它代表数据获取的具体逻辑。它的函数签名(以golang为例):



func(p graphql.ResolveParams) (interface{}, error) {}



// ResolveParams Params for FieldResolveFn()
type ResolveParams struct {
// Source is the source value
Source interface{}



// Args is a map of arguments for current GraphQL request
Args map[string]interface{}

// Info is a collection of information about the current execution state.
Info ResolveInfo

// Context argument is a context value that is provided to every resolve function within an execution.
// It is commonly
// used to represent an authenticated user, or request-specific caches.
Context context.Context } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 值得注意的是,Resolver内部实现对于GraphQL完全是黑盒状态。这意味着Resolver如何返回数据、返回什么样的数据、从哪里返回数据,完全取决于Resolver本身。GraphQL在实际使用中常常作为中间层来使用,**数据的获取通过Resolver来封装,内部数据获取的实现可能基于RPC、REST、WS、SQL等多种不同的方式。


GraphQL例子
下面这部分将会展示一个用graphql-go实现的用户管理的例子,包括获取全部用户信息、获取指定用户信息、修改用户名称、删除用户的功能,以及如何创建枚举类型的功能,完整代码在这里。



生成后的schema文件内容如下:
type Mutation {
“”“[用户管理] 修改用户名称”””
changeUserName(
“"”用户ID”””
userId: Int!



"""用户名称"""
userName: String! ): Boolean


””“[用户管理] 创建用户”””
createUser(
“"”用户名称”””
userName: String!



"""用户邮箱"""
email: String!

"""用户密码"""
pwd: String!

"""用户联系方式"""
phone: Int ): Boolean


””“[用户管理] 删除用户”””
deleteUser(
“"”用户ID”””
userId: Int!
): Boolean
}



type Query {
“”“[用户管理] 获取指定用户的信息”””
UserInfo(
“"”用户ID”””
userId: Int!
): userInfo



””“[用户管理] 获取全部用户的信息”””
UserListInfo: [userInfo]!
}



”"”用户信息描述”””
type userInfo {
“"”用户email”””
email: String



”"”用户名称”””
name: String



”"”用户手机号”””
phone: Int



”"”用户密码”””
pwd: String



”"”用户状态”””
status: UserStatusEnum



”"”用户ID”””
userID: Int
}



”"”用户状态信息”””
enum UserStatusEnum {
“"”用户可用”””
EnableUser



”"”用户不可用”””
DisableUser
}
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
注意



GraphQL基于golang实现的例子比较少
GraphQL的schema可以自动生成,具体操作可查看graphq-cli文档,步骤大致包括npm包的安装、graphql-cli工具的安装,配置文件的更改(此处需要指定服务对外暴露的地址) ,执行graphql get-schema 命令。
GraphQL API以及Rsolve函数定义



type UserInfo struct {
UserID uint64 json:"userID"
Name string json:"name"
Email string json:"email"
Phone int64 json:"phone"
Pwd string json:"pwd"
Status model.UserStatusType json:"status"
}
//这段内容是如何使用GraphQL定义枚举类型
var UserStatusEnumType = graphql.NewEnum(graphql.EnumConfig{
Name: “UserStatusEnum”,
Description: “用户状态信息”,
Values: graphql.EnumValueConfigMap{
“EnableUser”: &graphql.EnumValueConfig{
Value: model.EnableStatus,
Description: “用户可用”,
},
“DisableUser”: &graphql.EnumValueConfig{
Value: model.DisableStatus,
Description: “用户不可用”,
},
},
})



var UserInfoType = graphql.NewObject(graphql.ObjectConfig{
Name: “userInfo”,
Description: “用户信息描述”,
Fields: graphql.Fields{
“userID”: &graphql.Field{
Description: “用户ID”,
Type: graphql.Int,
},
“name”: &graphql.Field{
Description: “用户名称”,
Type: graphql.String,
},
“email”: &graphql.Field{
Description: “用户email”,
Type: graphql.String,
},
“phone”: &graphql.Field{
Description: “用户手机号”,
Type: graphql.Int,
},
“pwd”: &graphql.Field{
Description: “用户密码”,
Type: graphql.String,
},
“status”: &graphql.Field{
Description: “用户状态”,
Type: UserStatusEnumType,
},
},
})
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
query与mutation的定义
var MutationType = graphql.NewObject(graphql.ObjectConfig{
Name: “Mutation”,
Fields: graphql.Fields{
“createUser”: &graphql.Field{
Type: graphql.Boolean,
Description: “[用户管理] 创建用户”,
Args: graphql.FieldConfigArgument{
“userName”: &graphql.ArgumentConfig{
Description: “用户名称”,
Type: graphql.NewNonNull(graphql.String),
},
“email”: &graphql.ArgumentConfig{
Description: “用户邮箱”,
Type: graphql.NewNonNull(graphql.String),
},
“pwd”: &graphql.ArgumentConfig{
Description: “用户密码”,
Type: graphql.NewNonNull(graphql.String),
},
“phone”: &graphql.ArgumentConfig{
Description: “用户联系方式”,
Type: graphql.Int,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
userId, _ := strconv.Atoi(GenerateID())
user := &model.User{
//展示如何解析传入的参数
Name: p.Args[“userName”].(string),
Email: sql.NullString{
String: p.Args[“email”].(string),
Valid: true,
},
Pwd: p.Args[“pwd”].(string),
Phone: int64(p.Args[“phone”].(int)),
UserID: uint64(userId),
Status: int64(model.EnableStatus),
}
if err := model.InsertUser(user); err != nil {
log.WithError(err).Error(“[mutaition.createUser] invoke InserUser() failed”)
return false, err
}
return true, nil



        },
},

}, })


var QueryType = graphql.NewObject(graphql.ObjectConfig{
Name: “Query”,
Fields: graphql.Fields{
“UserListInfo”: &graphql.Field{
Description: “[用户管理] 获取指定用户的信息”,
//定义了非空的list类型
Type: graphql.NewNonNull(graphql.NewList(UserInfoType)),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
users, err := model.GetUsers()
if err != nil {
log.WithError(err).Error(“[query.UserInfo] invoke InserUser() failed”)
return false, err
}
usersList := make([]*UserInfo, 0)
for _, v := range users {
userInfo := new(UserInfo)
userInfo.Name = v.Name
userInfo.Email = v.Email.String
userInfo.Phone = v.Phone
userInfo.Pwd = v.Pwd
userInfo.Status = model.UserStatusType(v.Status)
usersList = append(usersList, userInfo)



            }
return usersList, nil

},
},
}, }) 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 注意:


此处仅展示了部分例子
此处笔者仅列举了query、mutation类型的定义
如何定义服务main函数
type ServerCfg struct {
Addr string
MysqlAddr string
}



func main() {
//load config info
m := multiconfig.NewWithPath(“config.toml”)
svrCfg := new(ServerCfg)
m.MustLoad(svrCfg)
//new graphql schema
schema, err := graphql.NewSchema(
graphql.SchemaConfig{
Query: object.QueryType,
Mutation: object.MutationType,
},
)
if err != nil {
log.WithError(err).Error(“[main] invoke graphql.NewSchema() failed”)
return
}



model.InitSqlxClient(svrCfg.MysqlAddr)
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
})
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
//read user_id from gateway
userIDStr := r.Header.Get("user_id")
if len(userIDStr) > 0 {
userID, err := strconv.Atoi(userIDStr)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
ctx = context.WithValue(ctx, "ContextUserIDKey", userID)
}
h.ContextHandler(ctx, w, r)

})
log.Fatal(http.ListenAndServe(svrCfg.Addr, nil)) }


http://graphql.cn/
https://juejin.im/post/5ac1b03bf265da237b223e82
https://laravel-china.org/topics/3112/why-did-github-open-a-graphql-version-of-api
http://www.cnblogs.com/sunshine-anycall/p/6357577.html



Golang



Go 是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。为了方便搜索和识别,有时会将其称为Golang。
GraphQL



GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。
Gin



Gin is a HTTP web framework written in Go (Golang).
为什么要用GraphQL?



为了让api具有更强的适应性,采用graphql来编写查询接口是个不错的选择。现在的api需要适应的场景太多了,而且迭代节奏也很快,RESTful的查询接口在一些复杂的场景下显得特别的繁杂,如多重嵌套的资源。



为什么要用Go?



在 Go 语言出现之前,开发者们总是面临非常艰难的抉择,究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是使用编译速度较快但执行效率不佳的语言(如:.NET、Java),或者说开发难度较低但执行速度一般的动态语言呢?显然,Go 语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发。



为什么要用Gin?



因为Gin够简洁,适合定制合适自己风格的框架。



在这里主要记录集成GraphQL的部分,对于Gin的目录结构组织因人而异,就不做详细介绍。



在Go里面集成graphql需要用到graphql-go这个package,当然可以参考GraphQL文档提供的其他。



// 从schema开始
// schema/schema.go
package schema



// 引入包graphql-go
import (
“github.com/graphql-go/graphql”
)



// 定义跟查询节点
var rootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: “RootQuery”,
Description: “Root Query”,
Fields: graphql.Fields{
“hello”: &queryHello, // queryHello 参考schema/hello.go
},
})



// 定义Schema用于http handler处理
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: rootQuery,
Mutation: nil, // 需要通过GraphQL更新数据,可以定义Mutation
})



// schema/hello.go
package schema



import (
“golesson/model”



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


// 定义查询对象的字段,支持嵌套
var helloType = graphql.NewObject(graphql.ObjectConfig{
Name: “Hello”,
Description: “Hello Model”,
Fields: graphql.Fields{
“id”: &graphql.Field{
Type: graphql.Int,
},
“name”: &graphql.Field{
Type: graphql.String,
},
},
})



// 处理查询请求
var queryHello = graphql.Field{
Name: “QueryHello”,
Description: “Query Hello”,
Type: graphql.NewList(helloType),
// Args是定义在GraphQL查询中支持的查询字段,
// 可自行随意定义,如加上limit,start这类
Args: graphql.FieldConfigArgument{
“id”: &graphql.ArgumentConfig{
Type: graphql.Int,
},
“name”: &graphql.ArgumentConfig{
Type: graphql.String,
},
},
// Resolve是一个处理请求的函数,具体处理逻辑可在此进行
Resolve: func(p graphql.ResolveParams) (result interface{}, err error) {
// Args里面定义的字段在p.Args里面,对应的取出来
// 因为是interface{}的值,需要类型转换,可参考类型断言(type assertion): https://zengweigang.gitbooks.io/core-go/content/eBook/11.3.html
id, _ := p.Args[“id”].(int)
name, _ := p.Args[“name”].(string)



            // 调用Hello这个model里面的Query方法查询数据
return (&model.Hello{}).Query(id, name)
}, }


// 准备好了GraphQL在Go里面需要的东西之后,来看看如何跟Gin结合
// controller/graphql/graphql.go
package graphql



import (
“golesson/schema”



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


func GraphqlHandler() gin.HandlerFunc{
h := handler.New(&handler.Config{
Schema: &schema.Schema,
Pretty: true,
GraphiQL: true,
})



    // 只需要通过Gin简单封装即可
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
} }


// route/router.go
package router



import (
“golesson/controller/graphql”



"github.com/gin-gonic/gin" )


var Router *gin.Engine



func init() {
Router = gin.Default()
}



func SetRouter() {
// GET方法用于支持GraphQL的web界面操作
// 如果不需要web界面,可根据自己需求用GET/POST或者其他都可以
Router.POST(“/graphql”, graphql.GraphqlHandler())
Router.GET(“/graphql”, graphql.GraphqlHandler())
}



// main.go
package main



import “golesson/route”



func main () {
r := router.Router



router.SetRouter()

r.Run(":1234") }


https://zhuanlan.zhihu.com/p/35792985



Github提供的GraphQL接口非常全面,那么我们该如何搭建出自己的接口呢?好在GraphQL提供了很多语言的解决方案。本文主要阐述如何用go搭建自己的GraphQL服务器。如果了解GraphQL建议先阅读GraphQL — API查询语言 或相关资料。



graphql-go
An implementation of GraphQL in Go. Follows the official reference implementation graphql-js.
一套比较完善的框架,众所周知go的结构体对json非常友好,所以并不需要对数据有特殊的处理,还是很方便的。打开终端输入命令



go get github.com/graphql-go/graphql



Object
在服务端编程中,编写的一切都可以称之为对象(Object)。例如一个商品(goods)的实例可以有商品名(name)、价格(price)、购买链接(url)三个字段。此时商品可以很自然的被称为一个object,查询的语句可以写成:



{
goods{
name
price
url
}
}
如果此时我们要查询商品和文章两种object的信息:



/* query 可以省去 */
query{
goods{
name
}
article{
name
}
}
是否你已经发觉,query像一个大的object,它有goods和article两个字段。除此之外,mutation也是如此:



mutation{
addGoods(input:goodsInput){
name
}
}
这里的addGoods可以看做是一个可以处理参数的对象,也就是某种意义上的函数。



总之,GraphQL服务端的编程就是一个又一个的对象将形成的嵌套结构(schema)组织起来,并对外提供服务。



query&mutation
为了防止低级错误的发生,在当前pkg下新建一个名为query.go(随便起)的文件。



import (
“github.com/graphql-go/graphql”
“errors”
)
定义good object



type Goods struct {
ID string json:"id"
Name string json:"name"
Price float64json:"price"
Url string json:"url"
}



var goodsType = graphql.NewObject(
graphql.ObjectConfig{
Name: “Goods”,
Fields: graphql.Fields{
“id”: &graphql.Field{
Type: graphql.String,
},
“name”: &graphql.Field{
Type: graphql.String,
},
“price”: &graphql.Field{
Type: graphql.Float,
},
“url”: &graphql.Field{
Type: graphql.String,
},
},
},
)
var goodsListType = graphql.NewList(goodsType)
注意:数组相当于新的object类型。



定义query object



var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: “Query”,
Fields: graphql.Fields{
// 无需处理参数
“goodsList”: &graphql.Field{
Type:goodsListType,
// 处理结构体的回调函数,直接返回处理完成的结构体即可
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return result, err
},
},
// 参数是id
“goods”: &graphql.Field{
Type: goodsType,
Args: graphql.FieldConfigArgument{
“id”: &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 获取参数
idQuery, isOK := p.Args[“id”].(string)
if isOK {
return result, nil
}
err := errors.New(“Field ‘goods’ is missing required arguments: id. “)
return nil, err
},
},
},
},
)
mutation定义基本相同,新建一个名为mutation.go的文件:



定义input object



var goodsInputType = graphql.NewInputObject(
graphql.InputObjectConfig{
Name: “goodsInput”,
Fields: graphql.InputObjectConfigFieldMap{
“name”: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
“price”: &graphql.InputObjectFieldConfig{
Type: graphql.Float,
},
“url”: &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
},
},
)
定义 mutation object



var mutationType = graphql.NewObject(
graphql.ObjectConfig{
Name: “Mutation”,
Fields: graphql.Fields{
“addGoods”:&graphql.Field{
Type:goodsType,
Args:graphql.FieldConfigArgument{
“input”:&graphql.ArgumentConfig{
Type:goodsInputType,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
input,isOk := p.Args[“input”].(map[string]string)
if !isOk{
err := errors.New(“Field ‘addGoods’ is missing required arguments: input. “)
return nil,err
}
result := Goods{
Name:input[“name”].(string),
Price:input[“price”].(float64),
Url:input[“url”].(string),
}
// 处理数据
return result,err
},
},
},
},
)
然而,input类型并不能直接转换为struct,而是一个map[string]interface{}类型,还需要进行手动转换。



定义schema



var schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: queryType,
Mutation: mutationType,
},
)
至此,我们的全部的object定义完成。



提供服务
graphql-go为我们提供了一个方便的接口,封装好的handler可以直接与go自带的http包绑定。



package api
import “github.com/graphql-go/handler”



func Register() *handler.Handler {
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: true,
})
return h
}
func main() {
h := api.Register()
handler := cors.Default().Handler(h)
http.Handle(“/graphql”, handler)
fmt.Println(“The api server will run on port : “, apiPort)
http.ListenAndServe(apiPort, nil)
}
打开浏览器,访问http://localhost:apiPort/graphql, 查看你自己的GraphiQL界面吧!



结束语
如果你觉得这样的代码谈不上优雅,甚至非常丑陋,那就对了。因为我也这样觉得,看一看隔壁python的实现方式:



import graphene



class Query(graphene.ObjectType):
hello = graphene.String()



def resolve_hello(self, args, context, info):
return ‘Hello world!’



schema = graphene.Schema(query=Query)
有没有涌来一口老血。



可能是受限与golang本身反射系统并不够完善,没有python各种各样的魔术方法,没有泛型,或者说go本身不太适合编写框架类的代码。在编写的过程中,冗余非常多,当然也可能是框架本身的问题



不可否认的是,go确实是非常不错的一门语言,虽然开发效率无法与python媲美,但是在多并发环境下,go表现出非常出色,同时拥有与C级别的运行速度和丰富的生态。



GitHub 宣布开放了一套使用 GraphQL 开发的公共 API。



GitHub 的 REST API 已经非常完善,设计得很优秀,很多公司开发自己的 REST API 时都会参考 GitHub,也有很多爱好者写了非常丰富的教程。



GraphQL 的核心是一套数据查询语言的规范,是 Facebook 在 2012 年开发的,2015 年开源,Facebook 内部已经广泛应用,用于替代 REST。



GitHub 为什么选择 GraphQL?这是很多用户关心的问题,Github 对此做了解释。



REST API 有什么问题?
首要问题就是扩展性方面,随着 API 的不断发展,会变得越来越臃肿



REST API 的方式是:server 定义一系列的接口,client 调用自己需要的接口,获取目标数据进行整合



例如用户接口,刚开始时,返回的信息会比较少,例如只有 id,name



后来用户的信息增加了,就在用户接口中返回更多的信息,例如 id,name,age,city,addr,email,headimage,nick



但可能很多 client 只是想获取其中少部分信息,如 name,headimage,却必须得到所有的用户信息,然后从中提取自己想要的



这个情况会增加网络传输量,并且不便于 client 处理数据



还有一个不方便的地方,例如 client 在某个需求中,可能需要调用多个独立的 API 才能获取到足够的数据



例如 client 要显示一篇文章的内容,同时要显示评论、作者信息,那么就可能需要调用文章接口、评论接口、用户接口



这种方式非常不灵活



GitHub 还遇到其他一些 REST API 不好处理的问题,例如



想要确保 client 提供的参数的类型安全;想要从代码生成文档;想要识别每个端点的 OAuth 请求范围 ……



使用 GraphQL 有什么好处?
GraphQL 简单来说就是:取哪些数据是由 client 来决定



REST 中,给哪些数据是 server 决定的,client 只能从中挑选,如果 A 接口中的数据不够,再请求 B 接口,然后从他们返回的数据中挑出自己需要的



GraphQL 中,client 直接对 server 说想要什么数据,server 负责精确的返回目标数据



例如,你想要获取用户的几个属性信息,你的 GraphQL 请求就是这样的



{
viewer {
login
bio
location
isBountyHunter
}
}
返回的响应信息如下



{
“data”: {
“viewer”: {
“login”: “octocat”,
“bio”: “I’ve been around the world, from London to the Bay.”,
“location”: “San Francisco, CA”,
“isBountyHunter”: true
}
}
}
再看一个更复杂的例子,例如你想知道你给多少个项目点亮过星星、最初 3 个项目的名字、及他们 star fork watcher 的总数可以看到,返回的 JSON 数据中,key value 是和请求完全一致的



GraphQL 请求就是这样的



{
viewer {
login
starredRepositories {
totalCount
}
repositories(first: 3) {
edges {
node {
name
stargazers {
totalCount
}
forks {
totalCount
}
watchers {
totalCount
}
issues(states:[OPEN]) {
totalCount
}
}
}
}
}
}
响应信息如下



{

“data”:{

“viewer”:{

“login”: “octocat”,
“starredRepositories”: {
“totalCount”: 131
},
“repositories”:{
“edges”:[
{
“node”:{
“name”:”octokit.rb”,
“stargazers”:{
“totalCount”: 17
},
“forks”:{
“totalCount”: 3
},
“watchers”:{
“totalCount”: 3
},
“issues”: {
“totalCount”: 1
}
}
},
{

“node”:{

“name”:”octokit.objc”,
“stargazers”:{
“totalCount”: 2
},
“forks”:{
“totalCount”: 0
},
“watchers”:{
“totalCount”: 1
},
“issues”: {
“totalCount”: 10
}
}
},
{
“node”:{
“name”:”octokit.net”,
“stargazers”:{
“totalCount”: 19
},
“forks”:{
“totalCount”: 7
},
“watchers”:{
“totalCount”: 3
},
“issues”: {
“totalCount”: 4
}
}
}
]
}
}
}
}
代码已被折叠,点此展开
GraphQL 还有很多其他的特点,例如你只需要一个请求,就可以得到所有需要的数据



批量请求,可以定义两个独立请求的依赖关系,高效的获取数据
创建订阅,client 可以收到新的数据
数据延迟,可以对响应中一部分数据标识为时间不敏感
小结
不只是 Github,还有很多大公司已经使用 GraphQL,例如 Pinterest, Coursera, Shopify



Github 也表达了对 GraphQL API 的重视,接下来会持续完善,使其更加灵活



Category golang