基本操作:Go创建GraphQL API
平台开发 360云计算
女主宣言
越来越多的项目中都能看到GraphQL的身影,不知道大家在项目中有没有使用过GraphQL呢?今天给大家分享一下使用Go,来创建基础GraphQL API,供大家参考学习。
PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!
引言
随着时间推移,我们在越来越多的项目中,都可以发现GraphQL的身影。
从几个方面看,包括图形化的数据、多个分布式团队和高度版本化的api,以及关于类型安全和文档的问题。GraphQL看起来非常适合许多不同的应用程序。
本文的目标不是去介绍GraphQL的基础知识,而是在实际场景中看到它的实际操作。当计划将现有的REST API移动到GraphQL时,需要引入一个转换层,以实现平稳过渡。
在本篇文章中,我们将使用jsonplaceholder作为我们用GraphQL包装的API。这里有几个用于graphQL的库,在本篇文章示例中,将使用graphql-go和graphql-go-handler。
我们的目标是从jsonplaceholder获取文章和评论,最后以一种通过ID获取文章的方式,如果API使用者希望获取评论,则通过GraphQL将评论嵌套到文章中。
让我们开始吧。
开始实现
首先,我们为文章和评论定义数据模型:
type Post struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
type Comment struct {
PostID int `json:"postId"`
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Body string `json:"body"`
}
我们定义 fetchPostByiD(id)函数,它用来调用http://jsonplaceholder.typicode.com/posts/${id} 并将生成的JSON转换成Post。当然,还有一个fetchCommentByPostId(post.ID)助手函数,它对注释做同样的事情,从http://jsonplaceholder.typico/posts/${id}/注释中获取数据,并将其转换为[]Comment。
然后我们继续创建graphQL模式。我们从定义queryType开始,它是模式的根:
func createQueryType(postType *graphql.Object) graphql.ObjectConfig {
return graphql.ObjectConfig{Name: "QueryType", Fields: graphql.Fields{
"post": &graphql.Field{
Type: postType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.Int),
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
id := p.Args["id"]
v, _ := id.(int)
log.Printf("fetching post with id: %d", v)
return fetchPostByiD(v)
},
},
}}
}
根查询类型只有一个字段 —— post。这个字段是由postType定义的,我们稍后会看到。它只接受一个名为id的参数。
文章通过从p.Args中解析出id,并将它传递给fetchPostsByID,返回已获取和转换后的Post以及错误信息。
接下来,我们定义postType,这很有趣。我们基本上只是把post字段从数据模型映射到graphQL类型,但我们也添加了comments字段。如果客户端显式地想要取回它们,那么评论的Resolve函数才会被调用。
为了解决评论,我们使用p.Source来访问这个查询的“父”。它为我们提供了一个Post实例*Post——已获取的文章。使用这篇文章的id,我们可以获取评论:
func createPostType(commentType *graphql.Object) *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: "Post",
Fields: graphql.Fields{
"userId": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
},
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
},
"title": &graphql.Field{
Type: graphql.String,
},
"body": &graphql.Field{
Type: graphql.String,
},
"comments": &graphql.Field{
Type: graphql.NewList(commentType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
post, _ := p.Source.(*Post)
log.Printf("fetching comments of post with id: %d", post.ID)
return fetchCommentsByPostID(post.ID)
},
},
},
})
}
在模式中唯一要定义的类型是commentType,这很无聊,因为它只将数据模型的字段映射到graphQL类型:
func createCommentType() *graphql.Object {
return graphql.NewObject(graphql.ObjectConfig{
Name: "Comment",
Fields: graphql.Fields{
"postid": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
},
"id": &graphql.Field{
Type: graphql.NewNonNull(graphql.Int),
},
"name": &graphql.Field{
Type: graphql.String,
},
"email": &graphql.Field{
Type: graphql.String,
},
"body": &graphql.Field{
Type: graphql.String,
},
},
})
}
好了,我们的模式被定义了,剩下的就是把它们放在一起。
我们实例化一个graphQL模式并将其传递给graphql-go-handler,它是一个http中间件,它帮助我们处理graphQL查询。然后我们简单地启动一个http服务器,它的返回处理程序被路由到/graphql。
这像这样:
func main() {
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(
createQueryType(
createPostType(
createCommentType(),
),
),
),
})
if err != nil {
log.Fatalf("failed to create schema, error: %v", err)
}
handler := gqlhandler.New(&gqlhandler.Config{
Schema: &schema,
})
http.Handle("/graphql", handler)
log.Println("Server started at http://localhost:3000/graphql")
log.Fatal(http.ListenAndServe(":3000", nil))
}
好了,以上就是全部实现!
启动服务器之后,我们可以使用GraphiQL查询一个具有特定id的文章,指定我们感兴趣的字段:
query {
post(id: 5) {
userId
id
body
title
comments {
id
email
name
}
}
}
获取到以下返回信息:
{
"data": {
"post": {
"userId": 1,
"id": 5,
"title": "...",
"body": "...",
"comments": [
{
"id": 21,
"email": "...",
"name": "..."
},
...
]
}
}
}
如果我们在查询中去掉了评论,那么就不会发出获取评论的请求,我们只是简单地将选定的帖子作为响应。
完整的示例代码地址 https://github.com/zupzup/example-go-graphql。
总结
这个例子展示了如何使用一个简单的Go层将现有的REST API转换成GraphQL。我们使用的这个库,graphql-go工作得很好,提供了可靠的文档和良好的示例。
当然,有更简洁、更有趣的方法来定义这样的模式,但是由于技术等有限,我选择了这个解决方案,它的重点是清晰性。希望大家举一反三,自己结合实际去编写适合项目的实现。
有疑问加站长微信联系(非本文作者)