Go言語でgraphqlを触ってみた

ゆるっとアドベントカレンダー Advent Calendar 2018 2日目です。
最近ハッカソンでGraphqlを使って開発を行っているので、今回はGraphqlの復習で簡単なGraphqlサーバをGo言語を使って実装してみます。

Graphqlとは?

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data

GraphqlはAPIへ問い合わせるためのクエリ言語になります。一つのエンドポイントを用意して、予め定義しておいたGraphqlのクエリを投げることで必要な情報を持ってくることができます。

graphql.org

今回使う外部ライブラリ

  • labstack/echo
  • graphql-go/graphql
  • graphql-go/handler

github.com

github.com

github.com

今回実装したもの

localhost:8080/graphqlにブラウザでアクセスするとGraphqlのIDEが起動して、Graphqlのqueryを投げると下の写真の右のようにdataが返ってくるようなものになります。 f:id:jon20:20181202050619p:plain

今回実装したものは↓のGithubに置いておきます。

github.com

解説

基本的にgraphql-go/graphqlのexampleを参考に実装しています。
まずREST APIのように何か投げたら何か返ってくるものをGraphqlで実装したいためSchemaを定義します。ここで気をつけることは投げられるqueryにはQueryとMutationがあります。SchemaConfigを見ているとSubscriptionがあるのですが調べたかんじだとリアルタイムのデータを取得するときに使うらしいです。まだ使ったことはないので今回は触れません。
QueryとMutationなのですが、QueryはCRUDでいうとCの部分で、単にデータを取得したいときに使います。MutationはR、U、Dの部分で、データに変更を加えたい際に使います。

graphql.org

今回はSampleとしてQueryとMutationを使ってschemaを定義します。
仕様は以下のとおりです。

  • Query
    • IDとPasswordを渡したらMessageが返ってくる
  • Mutation
    • UsernameとAgeを渡したらMessageが返ってくる(Mutationなので何かしらの更新処理を入れる必要があるが今回は省略します)

QueryのSchemaの実装

graphql-sample/query.go at master · jon20/graphql-sample · GitHub

func SetQuery() graphql.Fields {
    query := graphql.Fields{
        "SampleQuery": &graphql.Field{
            Type:    SampleQueryType(),
            Args:    SampleQueryArgs(),
            Resolve: SampleQueryResolve,
        },
    }
    return query
}

func SampleQueryType() *graphql.Object {
    sampleType := graphql.NewObject(graphql.ObjectConfig{
        Name: "SampleQuery",
        Fields: graphql.Fields{
            "Message": &graphql.Field{
                Type: graphql.String,
            },
        },
    })
    return sampleType
}

func SampleQueryArgs() map[string]*graphql.ArgumentConfig {
    sampleArgs := graphql.FieldConfigArgument{
        "ID": &graphql.ArgumentConfig{
            Type: graphql.NewNonNull(graphql.String),
        },
        "Password": &graphql.ArgumentConfig{
            Type: graphql.NewNonNull(graphql.String),
        },
    }
    return sampleArgs
}

func SampleQueryResolve(params graphql.ResolveParams) (interface{}, error) {
    resp := &models.SampleMutateResp{
        Message: "Hello, This is SampleQuery",
    }
    return resp, nil
}

汚い実装ですが、肝となるのはSetQuery()です。Fieldsに定義したいFieldを定義します。FieldはArgsがqueryを使うときに渡す値の定義、Resolveにはqueryを叩いたときの処理を渡してあげます。

MutationのSchemaの実装

graphql-sample/mutation.go at master · jon20/graphql-sample · GitHub

func SetMutation() graphql.Fields {
    mutateQuery := graphql.Fields{
        "SampleMutate": &graphql.Field{
            Type:    SampleMutateType(),
            Args:    SampleMutateArgs(),
            Resolve: SampleMutateResolve,
        },
    }
    return mutateQuery
}

func SampleMutateType() *graphql.Object {
    sampleType := graphql.NewObject(graphql.ObjectConfig{
        Name: "SampleMutate",
        Fields: graphql.Fields{
            "Message": &graphql.Field{
                Type: graphql.String,
            },
        },
    })
    return sampleType
}

func SampleMutateArgs() map[string]*graphql.ArgumentConfig {
    sampleArgs := graphql.FieldConfigArgument{
        "Username": &graphql.ArgumentConfig{
            Type: graphql.NewNonNull(graphql.String),
        },
        "Age": &graphql.ArgumentConfig{
            Type: graphql.NewNonNull(graphql.Int),
        },
    }
    return sampleArgs
}

func SampleMutateResolve(params graphql.ResolveParams) (interface{}, error) {
    resp := &models.SampleMutateResp{
        Message: "Hello, This is SampleMutate",
    }
    return resp, nil
}

実装手順としてはqueryと同じです。どちらも共通して気をつけるポイントとしては指定できる型です。Scalar型があるのですが公式のDocumentを見たかんじだとID、String、Int、Float、Booleanだけになります。もしそれ以外の型を使いたい場合は自分で定義しなければなりません。

graphql.org

SchemaのSetupとIDEの用意

graphql-sample/graphql.go at master · jon20/graphql-sample · GitHub

func GraphqlSetting() http.Handler {
    query := fields.SetQuery()
    mutateQuery := fields.SetMutation()
    rootMutation := graphql.NewObject(graphql.ObjectConfig{Name: "RootMutation", Fields: mutateQuery})
    rootQuery := graphql.NewObject(graphql.ObjectConfig{Name: "RootQuery", Fields: query})

    schema, err := graphql.NewSchema(graphql.SchemaConfig{
        Query:    rootQuery,
        Mutation: rootMutation,
    })
    if err != nil {
        panic(err)
    }
    h := handler.New(&handler.Config{
        Schema:     &schema,
        Pretty:     true,
        GraphiQL:   false,
        Playground: true,
    })
    return h
}

GraphqlのIDEはhandler.Config{}のところで設定できます。指定できるIDEの種類としてはgraphiqlとgraphql-playgroundがあります。今回はgraphql-playgroundを使います。

github.com

github.com

Graphqlサーバの立ち上げ

graphql-sample/main.go at master · jon20/graphql-sample · GitHub

func main() {
    route := echo.New()

    route.POST("/graphql", echo.WrapHandler(graphql.GraphqlSetting()))
    route.GET("/graphql", echo.WrapHandler(graphql.GraphqlSetting()))

    route.Logger.Fatal(route.Start("localhost:8080"))
}

あとは localhost:8080/graphqlのroutingを追加するためechoを使います。

完成!!

定義したSchemaは↓のように見れます。 f:id:jon20:20181202071549p:plain

Queryに定義したqueryの実行結果 f:id:jon20:20181202072117p:plain Mutationに定義したqueryの実行結果 f:id:jon20:20181202072121p:plain

まとめ

  • 単一エンドポイントにまとめられるのでREST APIで実装する場合と比べてエンドポイントの管理が楽になりそう
  • Documentを自動で生成してくれるのでSwaggerなどを用いてDocumentを実装する手間が省ける。
  • ファイルアップロードとかを実装する場合はどうじっそうすればいいのだろうか?