GraphQl与Golang
文章目录
- REST与GraphQL
- 为什么是GraphQL
- Hello World
- 造轮子
- 别人家的轮子
- 小结
- 劳动改造
- 对象与字段
- 更多类型
- 列表类型
- 枚举类型
- ID类型
- 接口类型
- 联合类型
- 参数
- 高级查询:变量
- 变更
- 输入类型
REST与GraphQL
如果你还不知道什么是GraphQL,那么,请移驾☞此处。
什么实API呢?API的全称是Application Programming Interface,翻译过来就是应用程序编程接口。在网络编程中,API决定了客户端如何从服务器获取数据,或者说API是客户端从服务器获取数据的方式,REST和GraphQL就是这样的方式。
REST和GraphQL本身并不是API,他们是用来修饰API的,也就是说,他们是两种不同的API设计规范,或者说风格。那么他们之间有什么区别呢?
如果你看过爱情公寓的话,有一集中一菲和曾小贤、关谷、子乔打赌。一菲问赌注是什么,子乔说,要是输了就把张伟输给你。一菲很奇怪,我要张伟干什么?这时子乔说了一句很经典的话:和我们赌,不是看你要什么,而是看我们有什么!如果非要形容REST风格的话,我想子乔的这句话应该是最贴切的了。
REST API为客户端提供了一些列端点,每个端点返回特定的数据结构。客户端只能根据自己需要的数据在这些入口点中选择,如果想获得新的结构化数据,就不得不跟服务器商量:嗨,哥们,再提供个API呗。然后等着服务器端不情愿的答复:好了,知道了。
如果说REST是捆绑消费的话,那么GraphQL就是自由消费。假如你的洗发水用完了,你想去超市买瓶洗发水。
你说:我要一瓶洗发水。
REST会说:行,买洗发水送洁厕灵,打八折。
你说:我不要洁厕灵,我就要洗发水。
REST会说:不行,这是活动规则,不要洁厕灵,洗发水就不卖给你。
你:。。。what ?
一脸懵逼的你很想反抗,于是带着洗发水和洁厕灵回家了,不得不接受这惨淡的现实。现在,让我们换成GraphQL再买一次洗发水。
你说:我要一瓶洗发水。
GraphQL会说:行,给你。
就是这么简单,你要什么,GraphQL就能给你什么。当然也不能超出GraphQL所能提供的范围,要是你想买鸽子蛋那么大的钻石,人家上哪儿偷去?
GraphQL只公开单个端点,支持客户端进行声明性数据提取。也就是客户端可以告诉服务器我想要什么数据,然后服务器就把这些数据返回给客户端。
GraphQL被称为是API查询语言,由Facebook实践和开源。服务器公开的单个端点就是一个一个的API,GraphQL提交查询的时候,就像是在查询这些API一样。
如果REST是在以命令的方式要求服务器返回数据,那么GraphQL就是在向服务器描述数据。
使用REST API获取数据时,需要经过四个步骤。
- 构造并发送HTTP请求
- 接收和响应服务器响应
- 在本地存储数据
- 在UI中显示数据
而使用GraphQL API时只需要两步。
- 描述数据
- 显示数据
GraphQL最初用于React语言,但是它可以由任何语言实现。幸运的是,已有Golang的爱好者对此作出了贡献,目前在Github上有三个包可用。如下:
graphql
graphql-go
magellan
其中第一个包显得有些笨拙,它大量使用了空接口来实现。第二个包和第三个包非常类似,使用了静态代码分析来实现。它们之间的另一个重大区别是,第二个包和第三个包支持SDL(Schema Definition Language:模式定义语言),而第一个包不支持。更多关于SDL的介绍会在后面展开。
我主要使用第二个包,第一个包会用一个例子做一个入门介绍。
使用GraphQL可以带来哪些好处呢?
-
减少需要通过网络传输的数据量,提高数据加载效率,因为买洗发水不会送你洁厕灵。
-
前端差异透明化。如果我和你一起去超市买东西,我要买洗发水和沐浴露,而你要买沐浴露和洗面奶,而最终的结果可能是REST将洗发水、沐浴露和洗面奶捆绑销售,我买了不需要的洗面奶,你买了不需要的洗发水。但GraphQL会将这三样东西分开卖,这样,我和你可以根据自己的需要选择商品,这样客户端的差异在服务端就透明化了。
-
灵活、快速开发。当沐浴露和洗发水的组合卖的热火朝天的时候,突然有一天你要买沐浴露和洗面奶,如果将洗面奶加到洗发水和沐浴露的组合中,又可能失去之前的客户,于是REST就不得不再推出一款洗面奶和沐浴露的组合。但是如果是GraphQL,完全不用对API做出任何改动,由客户端改变选择就行了。
总结起来就是灵活、高效。
为什么是GraphQL
GraphQL的出现号称是为了解决REST存在的问题,知道REST的问题,就相当于是知道了GraphQL的优势。
问题1:过度和不足
过度意味着下载了多于的数据,就好比买洗发水送的洁厕灵。不足意味着靠一个API无法得到所需的所有数据,由此就会引发n 1问题,也就是说客户端必须发起额外的请求才能获得所需数据。这就好比如果我需要的是洗发水和洗面奶,那么我就不得不买两个套餐。
问题2:限制前段迭代速度
如果后端定下来了,那么前端获取数据的方式也就确定了。如果这时前端需要改动,那么很有可能连同后端也需要作出适当的修改。而在GraphQL中,前端的改动几乎不会要求后端作出任何改动。
GraphQL还带来了另外两个优势。
优势1:后端分析
超市每个月要做的事就是分析商品的销售形式,卖的火的要增加库存,并把没人买的商品及时下架。GraphQL允许你对请求数据做细粒度的分析,了解哪个客户端对那些数据感兴趣,并弃用任何客户端都不再请求的字段。如果洁厕灵和洗发水绑定在了一起,而洁厕林又没人买的话,那么它只能长期占用库存。
此外,还可以对请求进行性能监视。GraphQL通过解析器函数返回客户端请求的数据,监测这些解析器的性能可以帮助你找到系统新能的瓶颈。
优势2:前后端独立开发
这一点依赖于GraphQL的Schema和类型系统,Schema和类型系统是GraphQL的发明。Schema是用SDL编写的,它是服务器和客户端之间的契约。Schema定义了客户端访问数据的方式,就像是一份商品目录清单,告诉你在这个超市中你可以买到哪些东西。类型系统则定义了API的功能,具体来说是定义了API应该返回什么样的数据。比如说,超市应该卖洗发水,可以是海飞丝,也可以是飘柔,还可以是蒂花之秀,但不能是舒肤佳,因为类型系统规定了必须是洗发水。
一旦Schema定义好了,前端和后台就可以各干各的了,因为后台知道自己该提供什么数据,前端也知道自己可以获取哪些数据。就好比一旦商品目录清单确定了,采购和导购员就可以各自干活了。
Hello World
准备工作:下载github.com/graph-gophers/graphql-go
包。
go get github.com/graph-gophers/graphql-go
下面的代码可能会让你一头雾水,但是不要问为什么,因为必须这么写,所有你心中的疑惑,我都会在后面解开。
首先我们需要写一段Schema代码,用SDL语言写。
var s = `
schema {
query: Query
}
type Query {
hello: String!
}
`
因为是在Golang中,所以这段SDL语言写的代码只能以字符串的形式存在。schema
定义的就是GraphQL的Schema,它是一份清单,定义客户端可以进行的操作,这里是query
,表示客户端可以进行查询操作。type
对应的就是类型系统,它定义了Query
对象,这个对象有一个String
类型的字段,叫做hello
,String
后面的感叹号表示该字段为非空字段,也就是说当客户端查询这个字段时,服务器一定会返回一个值。
Schema只是一份契约,至于契约的执行,也就是如何提供数据以及提供什么样的数据还需要Go的支持。
type query struct{}
func (_ *query) Hello() string {
return "hello world"
}
这里的query
结构体就是解析器,它的方法Hello
就是解析器函数。
下一步缔结契约,既然双方都已准备好,签了字契约才算生效。
var schema = graphql.MustParseSchema(s, &query{})
还差最后一步,为GraphQL API开通Http服务,直接用Golang提供的Http服务就行了。不过在此之前我们需要导入github.com/graph-gophers/graphql-go/relay
包,用它提供的功能将Schema转换成Golang处理器。
func main() {
h := relay.Handler{schema}
http.Handle("/hello", &h)
http.ListenAndServe(":8080", nil)
}
服务端的代码就编写完了,接下来是怎么玩的问题了。先将程序运行起来,现在是万事具备,只欠东风。
如果有安装curl的话,可以打开powershell,输入如下命令。
curl -X POST -d '{\"query\":\"{hello}\"}' localhost:8080/hello
如果一切正常,将会得到下面的输出。
{"data":{"hello":"hello world"}}
这是一段json字符串,表明查询结果是hello world
。
为什么要用这种方式?因为relay
提供的处理器是通过解析Body中的json串来获得客户端的查询请求的,使用curl是最简单的测试方式了,后面我会用一种更加好看的方式。
将curl POST的内容展开就是下面的内容。
{
"query": "{hello}"
}
其实真正扮演GraphQL查询角色的是{hello}
,这样写也是语法使然。而"query"
的存在只是为了处理器能正确的提取出后面的{hello}
。当你买洗发水的时候,你需要跟服务员说:我要一瓶洗发水,飘柔的。你说的这些话就如同这里的{hello}
,它告诉服务器我要查询hello
字段。
通过这个例子,我们已经了解到了GraphQL两个重要的内容:Schema和查询。更多的语法会在后面介绍,我们的主要工作也在于编写这两部分的内容。
附:完整代码☟
package main
import (
"net/http"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
type query struct{}
func (_ *query) Hello() string {
return "hello world"
}
var s = `
schema {
query: Query
}
type Query {
hello: String!
}
`
var schema = graphql.MustParseSchema(s, &query{})
func main() {
h := relay.Handler{schema}
http.Handle("/query", &h)
http.ListenAndServe(":8080", nil)
}
作为对比,我们来看看使用github.com/graphql-go/graphql
包如何写出Hello World。
首先还是需要下载这个包。
go get github.com/graphql-go/graphql
套路还是一样的,需要编写Schema。不同的是这次草拟契约和签订契约不再是两件事,而是变成了一件事。这就意味着,我们不再使用SDL语法以字符串的形式编写Schema了,而是要用Golang语法来编写。
首先需要写类型系统,与前边对应的是type Query{hello:String!}
这部分代码。
var queryType = graphql.NewObject(graphql.ObjectConfig {
Name: "Query",
Fields: graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, err) {
return "hello world", nil
},
},
},
})
这么长一段就说明了一件事:有一个叫做Query
的GraphQL对象,它有一个叫hello
的字段,类型是String
,而Resolve
函数定义了该字段的解析器函数,也就是说当客户端查询hello
字段时,就会调用这个函数。
然后是定义Schema,对应前面schema{query:Query}
这段代码。
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{Query: queryType})
最后一步是把Schema挂到Http服务上,不幸的是,这个包没有提供直接将Schema转为Handler的功能。然而万幸的是,有另一个包提供了这一功能。不过俗话说的好,自己动手,丰衣足食。我想说的是,我们不妨先自己造个轮子,然后在用用别人的轮子。
造轮子
graphql
包提供了Do
函数来执行查询,需要Schema和查询字符串作为参数,我们只需要在处理器中调用这个函数就可以完成GraphQL查询了。
func hello(w http.ResponseWriter, r *http.Request) {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: r.URL.Query().Get("query"),
})
if len(result.Errors) > 0 {
fmt.Fprintf(w, "Wrong result, unexpected errors: %v", result.Errors)
return
}
json.NewEncoder(w).Encode(result)
}
最后main
函数只需要像普通的Http程序那样开启Http父服务就行了。
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8080", nil)
}
迫不及待想看看结果了吗?在curl中输入curl localhost:8080/hello?'query=\{hello\}'
,回车之后就能看到服务器发回的数据了,和第一个例子一模一样。此外,也可以在浏览器中输入localhost:8080/hello?query={hello}
,也能得到相同的结果。
完整代码:
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/graphql-go/graphql"
)
var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "hello world", nil
},
},
},
},
)
var schema, _ = graphql.NewSchema(graphql.SchemaConfig{Query: queryType})
func hello(w http.ResponseWriter, r *http.Request) {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: r.URL.Query().Get("query"),
})
if len(result.Errors) > 0 {
fmt.Fprintf(w, "Wrong result, unexpected errors: %v", result.Errors)
return
}
json.NewEncoder(w).Encode(result)
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8080", nil)
}
别人家的轮子
不管怎么说自己造的轮子还是有点简陋,好在github.com/graphql-go/handler
包提供了GraphQL转Http处理器的支持。让我们下载过来试一试。
go get github.com/graphql-go/handler
现在我们不用自己写处理器函数了,mian
函数需要做一点修改。
func main() {
h := handler.New(&handler.Config{
Schema: &Schema,
pretty: true,
GraphiQL: true,
})
http.Handle("/hello", h)
http.ListenAndServe(":8080", nil)
}
在浏览器中输入localhost:8080/hello
,哇喔~ 有没有很酷。
左边区域用于输入查询,点击长得很像播放的那么按钮,右边就会显示查询结果。
这样就方便和直观多了,不得不承认,别人造的轮子就是比自己造的好,果然都是别人的的轮子。
完整代码:
package main
import (
"net/http"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
)
var queryType = graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "hello world", nil
},
},
},
})
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{Query: queryType})
func main() {
h := handler.New(&handler.Config{
Schema: &Schema,
Pretty: true,
GraphiQL: true,
})
http.Handle("/hello", h)
http.ListenAndServe(":8080", nil)
}
小结
对比这两个例子,graphql
包大量使用了结构体嵌套来模仿SDL,虽然结构清晰,不需要额外编写SDL代码,但是代码量却是有增无减,一旦功能复杂起来,代码结构会不忍直视。唯一的优势是有配套的处理器转换包,不过我们也可以为graphql-go
包写一套一模一样的处理器转换包。反观graphql-go
包,代码就简洁多了,而且SDL代码可以让人一眼就看出程序的功能。
综上所述,换而言之,后面的学习中,我们只会用graphql-go
这个包了。就决定是你了,去吧,皮卡丘~
劳动改造
为了在使用github.com/graph-gophers/graphql-go
包时也有漂亮的轮子可以用,我们需要fork一下github.com/graphql-go/handler
包,并做一点改造。
需要改动的地方不是很多,并且你可以根据自己的习惯,做出个性化的修改和定制。我属于有点强迫症的人,所以做了一些定制,怎么改其实无所谓,只要你自己喜欢而且能用就行。
为什么要在这里强调这一点呢?因为后面的学习中,我就全部改用自己改造的包来生成处理器了,这并不是最重要的部分,只是一种可以更加清晰和直观的看到代码效果的方式。
如果你跳过了这一章,或者因为它不重要而放弃。那么在后面你可能会看到一些奇怪的代码,在你的机器上编译器会告诉你找不到某些函数,或者在你那里看不到和我一样的效果。对于初学者,这是很糟糕的,我不希望因为这些本不重要的事而阻碍了你学习的进度和热情。我也是从菜鸟走过来的,虽然现在任然很菜,我要说的是我能体会初学者的心情,因此本章的内容务必认真完成,然后再往下走。
首先在你的GOPATH下新建一个目录GraphQL-Handler
,然后将handler
文件夹拷贝进去。这个过程完全可以自定义。我的目录是这样的,如果你建的目录和我的不一样,记得导入handler
包的时候换成你自己的目录就行了。
github.com/graphql-go/handler
包的内容并不多,我们只需要改其中两个脚本就可以使用了。
至于测试文件,它们并不会影响使用,甚至你可以将它们删除。但是写测试文件是一个好习惯,并且是必要的,这里暂时不修改它们只是为了不增加不必要的复杂度,因为我们并不打算发布它。
首先,在handler.go
文件中,我们需要添加一个代表查询的结构体和两个执行查询的方法。
然后再增加一个生成处理器的函数。
最后需要修改handler.go
中的ContextHandler
函数。
handler.go
就修改完了,再次声明,这只是一种符合我习惯的修改方式,不是必须这么做。
graphigl.go
文件的修改就简单多了,只需要修改renderGraphiQL
函数就可以了。
修改完以后可以用前面的例子测试一下,在[Hello World](#Hello World)的第一个例子中,首先导入我们修改过的handler
包。
import “GraphQL-Handler/handler"
然后将main
函数修改如下。
func main() {
h := handler.HttpHandler(schema, true, true)
http.Handle("/hello", h)
log.Fatal(http.ListenAndServe(":8080", nil))
}
在浏览器中输入localhost:8080/hello
回车,看到如下图的界面就OK了。
对象与字段
编写Schema非常像是在定义结构体,还是以[Hello World](#Hello World)中的例子来说明。
schema {
query: Query
}
type Query {
hello: String!
}
这是最简单的Schema了,schema
关键字用来定义一个Schema;query
是它的字段;Query
是query
字段的类型。但是这个类型并不是GraphQL的类型,而是我们自定义的对象类型,紧接着我们就用type
关键字解释了Query
类型。
在GraphQL中用type ObjectName{...}
来定义对象,对象也是类型。用Field:Type
来为对象定义字段,这里的Type
既可以是GraphQL的标量类型,也可以是自定义的对象类型,甚至枚举类型,这些类型都会在后面介绍。
schema
的定义十分刻板,它只能有三个字段:query
、mutation
、subscription
。这三个字段分别代表查询、修改和订阅。对比REST,query
就相当于GET
请求,mutation
就相当于POST
和DELETE
请求。因为现在只涉及查询,mutation
和subscription
会在后面专门介绍。
query
必须是一个对象类型。必须是Query
吗?当然不是,可以是任意名字,只要是一个对象类型,这样写只是一种习惯,并不是语法约束。
关键字type
用来定义对象,一个对象可以拥有若干字段。这里Query
对象拥有hello
字段,类型是String
类型。GraphQL中的标量类型还包括Int
、Float
、Boolean
。类型后面加上!
就表示该字段为非空字段,换句话说,当你查询这类字段时,一定会给你返回一个值。就好比食盐是超市必须卖的商品,当你去超市买食盐的时候,他绝不会跟你说没有。
那么Schema和查询有什么关系呢?在上一个例子中,我们测试程序时是输入下面的内容进行查询的。
{
hello
}
其实在大括号中输入的内容就是Query
对象的字段。那么这和schema
有什么关系呢?其实上面的查询是简写版,我们省略了query
关键字,完整的查询应该像下面这样。
query {
hello
}
QraphQL允许我们在查询时省略query
关键字,以大括号开头的默认就是查询。现在插叙和Schema之间的关系就非常明朗了,我们查询query
字段的hello
字段,其实就是Query
对象的hello
字段。
编写Schema的工作也就是在定义对象和字段,知道了如何定义字段和对象,我们就可以来实现一个复杂点的例子了,并以此填上一些坑。
####开工
查询一定是以数据为基础的,现在假设我们有一个film
结构体,定义如下。
type film struct {
Name string //电影名
Country string //国家
Year int32 //年份
Runtime int32 //时长(分钟)
Color bool //是否为彩色
Score float64 //评分
}
为了客户端能够查询这个结构体的数据,我们需要编写一个Schema。按照前面例子的经验,我们可以这样编写。
var s = `
schema {
query: Query
}
type Query {
name : String!
country: String!
year : Int!
runtime: Int!
color : Boolean
score : Float!
}
`
这样写是可以的,但不是最好的。这样会带来两个问题:一是结构混乱。因为写在Query
对象中的字段明显不是属于它的,而是属于Film
对象的。二是使Query
对象变得非常庞大。如果有几十上百个结构体数据,而所有字段都在写在Query
对象中,可想而知,Query
对象将变得多么复杂和庞大。无论是哪一点,无疑都会增加代码维护的难度。因此,我们再定义一个Film
对象,分担Query
对象的压力。
var s = `
schema {
query: Query
}
type Query {
film: Film
}
type Film {
name : String!
country: String!
year : Int!
runtime: Int!
color : Boolean
score : Float!
}
`
这样看起来的清晰明了多了。不过请特别注意一点,只有color
字段的类型Boolean
后面没有加!
,加不加!
并无强制规定,只是先记住这里的Boolean
后面没有!
,后面用得上。
第一步已经完成了,下面就是要得到Golang支持,也就是为每个字段编写解析器函数。再次回顾一下,Schema的每个对象对应着一个解析器,也就是Golang的结构体;每个字段对应一个解析器函数,也就是Golang结构体的一个方法。需要说明的是,解析器函数的名字必须和Schema中的字段名字一样,一般习惯是字段名全小写,解析器函数名就是首字母大写的字段名。一定一定,不要随便写解析器函数的名字。
这一次,我们需要从下至上编写解析器函数。为什么?因为Query
对象的film
字段并不是一个标量类型,可以直接解析,它是Film
对象类型,意味着它也对应着一个解析器。反观Film
对象的字段都是标量类型,直接对应解析器函数。
type filmResolver struct {
f *film
}
func (r *filmResolver) Name() string {
return r.f.Name
}
func (r *filmResolver) Country() string {
return r.f.Country
}
func (r *filmResolver) Year() int32 {
return r.f.Year
}
func (r *filmResolver) Runtime() int32 {
return r.f.Runtime
}
func (r *filmResolver) Color() *bool {
return &r.f.Color
}
func (r *filmResolver) Score() float64 {
return r.f.Score
}
如果你观察力敏锐的话,应该注意到Color
函数返回的是*bool
类型,而其他方法都是返回的值类型。如果注意到了这一点,先记住。
Query
对象的film
字段是Film
对象类型,而这个对象有自己的解析器,因此,film
字段的解析器函数只需要返回Film
对象的解析器就可以了。问题在于,filmResolver
需要一个film
类型的字段,这就是数据。查询是基于数据的,因此在编写Query
对象的解析器之前,还需要伪造一份数据。
var Hidden_Man = &film{
Name: "邪不压正",
Country: "China",
Year: 2018,
Runtime: 137,
Color: true,
Score: 7.1,
}
目前数据是我们自己伪造的,实际应用中应该是查询数据库得到的,有了数据,就可可以编写Query
对象的解析器了。
type Resolver struct{}
func (r *Resolver) Film() *filmResolver {
return &filmResolver{Hidden_Man}
}
双方都已准备就绪,是时候缔结契约了。
var schema = graphql.MustParseSchema(s, &Resolver{})
最后是main
函数。这次我们用自己改造过的轮子来生成处理器。所以要记得先导入上一章改造过的handler
包。
import "GraphQL-Handler/handler"
func main() {
h := handler.HttpHandler(schema)
http.Handle("/film", h)
http.ListenAndServe(":8080", nil)
}
####安排~
运行程序,在浏览器中输入localhost:8080/film
回车,让我们来试试查询Hidden_Man
的各个字段。需要注意的是,现在film
不是一个可直接查询的字段,它本身也是一个解析器,只有那些标量类型的字段才能直接查询,所以现在查询应该这样写。
{
film {
name
country
Year
Runtime
score
Color
}
}
查询结果应该如下图。
在结束本章之前,还有几个问题需要澄清一下,前面有两个地方让大家先记住。一是在Schema中定义Film
对象时只有Color
字段的类型Boolean
后面没有加!
;二是Color
字段的解析器函数返回的是*bool
类型。
现在我要提出这样四个问题。
Film
对象的其他字段的类型后可不可以也不加!
?Color
字段的解析器函数可不可以返回bool
类型的值?Year
字段和Runtime
字段的解析器函数可不可以返回int
类型?Score
字段的解析器函数可不可以返回float32
类型?
以上四个问题的答案都是否定的,不信可以亲自试一试。
这四个问题揭示了三个实现上的规则,是只有用Golang时才会有的规则,不是GraphQL本身的规则。
- 如果Schema中对象的某个字段可以为空,也就是其类型后没有
!
的话,那么它的解析器函数必须返回指针类型,而不能是值类型。 - GraphQL中的
Int
类型必须对应Golang中的int32
类型。 - GraphQL中的
Float
类型必须对应Golang中的float64
类型。
收工,本章完~
附:完整代码☟
package main
import (
"GraphQL-Handler/handler"
"net/http"
graphql "github.com/graph-gophers/graphql-go"
)
// Schema
var s = `
schema {
query: Query
}
type Query {
name : String!
country: String!
year : Int!
runtime: Int!
color : Boolean
score : Float!
}
`
type film struct {
Name string //电影名
Country string //国家
Year int32 //年份
Runtime int32 //时长(分钟)
Color bool //是否为彩色
Score float64 //评分
}
var Hidden_Man = &film{
Name: "邪不压正",
Country: "China",
Year: 2018,
Runtime: 137,
Color: true,
Score: 7.1,
}
//Query解析器
type Resolver struct{}
func (r *Resolver) Film() *filmResolver {
return &filmResolver{Hidden_Man}
}
type filmResolver struct {
f *film
}
//Film解析器
func (r您可能感兴趣的文章:
GraphQl与Golang
JWT Token认证
golang基础教程
golang timewheel 时间轮定时器设计与实现
golang的调度总结
golang key map 所有_golang推断map中指定key是不是存在_后端开发
golang会取代php吗
Golang环境安装&IDEA开发Golang
golang中的nil
ubuntu下安装golang(转)