我使用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,我将来想继续学习它。

广告
将在 10 秒后关闭
bannerAds