我使用Golang进行了GraphQL的初步尝试
我在golang中进行了GraphQL的初步实践,所以我将写下我遇到的实现方面的问题。
GraphQL是什么
通过向一个终结点发送自定义的查询语言,GraphQL可以在一次请求中大量获取多个资源,这是非常强大的。
相对于REST,通常每个终结点只能获取一个资源,这导致终结点数量不断增加。另外,当资源增加时,我们还需要发送多个请求,这可能会带来各种繁琐的情况。
为了解决这些问题,GraphQL应运而生。
请务必阅读下方文章,其中详细解释了此事。
GraphQL入門 – 令你渴望使用GraphQL
用Golang实现GraphQL
因为有图书馆,所以我们会使用它。
以下是本次使用的库?
用Go语言编写的graphql-go工具包
我写过的东西 (Wǒ de
我已经在下面放置了我实际编写的代码。
mitubaEX/GraphQL样例
稍作解释
首先,让我们看一下 main.go。
package main
import (
"encoding/json"
"fmt"
"github.com/graphql-go/graphql"
"github.com/mitubaEX/graphQL_sample/application/graphql_util"
"io/ioutil"
"net/http"
)
func executeQuery(query string, schema graphql.Schema) *graphql.Result {
// schema と もらってきた query を入れて, graphql を実行
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
fmt.Printf("wrong result, unexpected errors: %v", result.Errors)
}
return result
}
func main() {
// POST /graphql のリクエストを受け取れるようにする
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
// bodyの読み取り処理
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
// query 実行
result := executeQuery(fmt.Sprintf("%s", body), graphql_util.Schema)
json.NewEncoder(w).Encode(result)
})
fmt.Println("Now server is running on port 8080")
http.ListenAndServe(":8080", nil)
}
为了执行GraphQL,需要提供schema和query。query是直接使用接收到的请求,而schema则使用graphql_util.Schema。
查看架构
我們來看一下剛才在main.go中使用的graphql_util.Schema。
package graphql_util
import "github.com/graphql-go/graphql"
// define schema, with our rootQuery and rootMutation
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
Query: rootQuery,
Mutation: rootMutation,
})
看起来很清晰。似乎是基于rootQuery和rootMutation构建的。
查看rootQuery和rootMutation。
package graphql_util
import (
"github.com/mitubaEX/graphQL_sample/application/graphql_util/fields"
"github.com/graphql-go/graphql"
)
var rootQuery = graphql.NewObject(graphql.ObjectConfig{
Name: "RootQuery",
Fields: graphql.Fields{
"user": fields.UserField,
"userList": fields.UserListField,
"event": fields.EventField,
"eventList": fields.EventListField,
},
})
package graphql_util
import (
"github.com/mitubaEX/graphQL_sample/application/graphql_util/fields"
"github.com/graphql-go/graphql"
)
var rootMutation = graphql.NewObject(graphql.ObjectConfig{
Name: "RootMutation",
Fields: graphql.Fields{
"createUser": fields.CreateUserField,
"createEvent": fields.CreateEventField,
},
})
让我们先来看一下rootQuery和rootMutation,它们都有一个叫做Fields的关键字。这部分大概对应了REST资源的概念。我们试试看fields.UserField。
我来看看 fields.UserField
package fields
import (
"errors"
"github.com/mitubaEX/graphQL_sample/application/graphql_util/types"
"github.com/mitubaEX/graphQL_sample/domain/model/user"
"github.com/mitubaEX/graphQL_sample/domain/service"
"github.com/mitubaEX/graphQL_sample/infrastructure"
"github.com/graphql-go/graphql"
)
// fetch single user
var UserField = &graphql.Field{
Type: types.UserType, // 返り値の型
Description: "Get single user",
Args: graphql.FieldConfigArgument{ //引数の定義
"id": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(params graphql.ResolveParams) (interface{}, error) { //実行関数
userId, isOK := params.Args["id"].(string) // 引数取り出し
if isOK {
return service.FindUserById(userId)
}
return nil, errors.New("no userId")
},
}
// fetch all user
var UserListField = &graphql.Field{
Type: graphql.NewList(types.UserType),
Description: "List of users",
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return infrastructure.NewUserRepository().UserList(), nil
},
}
// create user
var CreateUserField = &graphql.Field{
Type: types.UserType,
Description: "Create new user",
Args: graphql.FieldConfigArgument{
"userName": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"description": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"photoURL": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"email": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
userName, _ := params.Args["userName"].(string)
description, _ := params.Args["description"].(string)
photoURL, _ := params.Args["photoURL"].(string)
email, _ := params.Args["email"].(string)
newUser, err := user.NewUser(userName, description, photoURL, email)
if err != nil {
panic(err)
}
infrastructure.NewUserRepository().Store(newUser)
return newUser, nil
},
}
我写了很多东西,但首先看一下UserField,可以看到在key为Type的地方写了返回值的类型。然后在key为Args的值中写了作为参数传入的变量。由于存在返回值和参数的概念,所以查询可以写成以下形式。
{
user(id: "1") {// 中身は何かしらの欲しいkeyを指定}
}
这好像是一个函数。然后最后用Resolve函数来执行实际的操作内容。处理内容是通过params.Args[“id”]来接收参数的值,然后用该id进行搜索并返回结果。
这样一来,在资源不断增加的情况下,由于端点只有一个(可能只有一个),所以即使在多人开发中,也不需要共享大量的端点和资源文档,我觉得这样省事。
关于数据添加的事项
在GraphQL中添加数据时,需要在查询前添加关键字mutation。
之前的application/graphql_util/fields/UserField.go的CreateUserField在rootMutation中被使用。就像下面这样。
var rootMutation = graphql.NewObject(graphql.ObjectConfig{
Name: "RootMutation",
Fields: graphql.Fields{
"createUser": fields.CreateUserField, // ここ
"createEvent": fields.CreateEventField,
},
})
CreateUserField的样子类似于下面这样。
// create user
var CreateUserField = &graphql.Field{
Type: types.UserType, // 返り値の型
Description: "Create new user",
Args: graphql.FieldConfigArgument{ // 引数の定義
"userName": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"description": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"photoURL": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"email": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
userName, _ := params.Args["userName"].(string)
description, _ := params.Args["description"].(string)
photoURL, _ := params.Args["photoURL"].(string)
email, _ := params.Args["email"].(string)
newUser, err := user.NewUser(userName, description, photoURL, email)
if err != nil {
panic(err)
}
infrastructure.NewUserRepository().Store(newUser)
return newUser, nil
},
}
在处理中,根据参数传入的值,调用`NewUser`函数创建一个新的用户,并将该用户返回。当实际运行`CreateUserField`查询时,写法如下所示?。
mutation{
createUser(userName:"mituba", description: "des", photoURL: "photo", email: "email"){
userId, userName, description, photoURL, email
}
}
只需在前面加上”mutation”关键词。使用”mutation”来发出值更改的查询。
在 graphql-go/graphql 中发生的困难。
如果给定的参数是String类型等,那就没有问题,但是当试图传递自定义类型(如User类型)时,就会出现变成nil的问题。我希望能以以下的方式发送查询。
mutation{
createEvent(
user: {
userId: "1", userName:"mituba", description: "des", photoURL: "photo", email: "email"
},
eventName: "event",
description: "des",
location: "loc",
startTime: "start",
endTime: "end"){
eventId, user{userName}
}
}
当我搜寻问题时,发现有一个类似的选项。
- 該当しそうなissue
解决方案 cè àn)
听说可以使用NewObject来创建返回值的类型,使用NewInputObject来创建参数的类型。
// 返り値用
var UserType = graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"userId": &graphql.Field{
Type: graphql.String,
},
"userName": &graphql.Field{
Type: graphql.String,
},
"description": &graphql.Field{
Type: graphql.String,
},
"photoURL": &graphql.Field{
Type: graphql.String,
},
"email": &graphql.Field{
Type: graphql.String,
},
},
})
// 引数用
var UserInput = graphql.NewInputObject(graphql.InputObjectConfig{
Name: "user",
Fields: graphql.InputObjectConfigFieldMap{
"userId": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
"userName": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
"description": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
"photoURL": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
"email": &graphql.InputObjectFieldConfig{
Type: graphql.String,
},
},
Description: "user input type",
})
如果还有其他好的方法的话,如果您能给予一些建议就非常感激。
总结
暂时粗略地说,我尝试在Go语言中写了一个GraphQL的原型。
个人觉得写起来很有趣的Go语言和能够处理大量资源而不需要编写端点的GraphQL,我将来想继续学习它。