我要写一下最近流行的GraphQL

这篇文章是2017年go Advent Calendar的第15天的文章。

介绍

最近有很多新技术出现,例如grpc和GraphQL。您写api的时候,通常会使用什么作为api的接口呢?一般来说,返回json格式的响应很常见。

我想要解释一下本次的主题,即关于GraphQL的概述和在Go语言中的实现。

GraphQL的概述

GraphQL是由Facebook开发的一种查询语言,于2015年在React.js Conf上进行了发布。
虽然在国内并不像其他地方那样流行,但由于GitHub的采用以及一些公司的采用,它的知名度正在不断提升。

GraphQL的主要特点有两个:客户端可以指定所需信息,一次请求可以获取多个(可选的)资源。

我们可以尝试使用Github api Explorer来测试一下使用GitHub API获取信息的代码。

{
  viewer {
    login,
    avatarUrl,
    company,
    name,
    url
  }
}

在点击链接后,当您写入此类查询时,将返回如下响应。


{
  "data": {
    "viewer": {
      "login": "takochuu",
      "avatarUrl": "https://avatars1.githubusercontent.com/u/207675?v=4",
      "company": "Japan",
      "name": "",
      "url": "https://github.com/takochuu"
    }
  }
}

嗯?很简单吧。

这次,我将使用go语言来创建一个使用GraphQL的API服务器的源代码。

使用GraphQL实现服务器

我们现在要开始实施了。作为介绍,我认为最好让大家进行这个教程。

我打算使用github.com/graphql-go/graphql作为本次的GraphQL库。虽然有点担心它已经有一个月没有提交过新的代码了,但目前在go语言中处理GraphQL时,它是star数最多的库,所以还是选择了它。

查询(读取)

首先,我们将尝试使用GraphQL来实现服务器端的读取处理。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/graphql-go/graphql"
)

var q graphql.ObjectConfig = graphql.ObjectConfig{
    Name: "query",
    Fields: graphql.Fields{
        "id": &graphql.Field{
            Type:    graphql.ID,
            Resolve: resolveID,
        },
        "name": &graphql.Field{
            Type:    graphql.String,
            Resolve: resolveName,
        },
    },
}

var schemaConfig graphql.SchemaConfig = graphql.SchemaConfig{
    Query: graphql.NewObject(q),
}
var schema, _ = graphql.NewSchema(schemaConfig)

func executeQuery(query string, schema graphql.Schema) *graphql.Result {
    r := graphql.Do(graphql.Params{
        Schema:        schema,
        RequestString: query,
    })

    if len(r.Errors) > 0 {
        fmt.Printf("エラーがあるよ: %v", r.Errors)
    }

    j, _ := json.Marshal(r)
    fmt.Printf("%s \n", j)

    return r
}

func handler(w http.ResponseWriter, r *http.Request) {
    bufBody := new(bytes.Buffer)
    bufBody.ReadFrom(r.Body)
    query := bufBody.String()

    result := executeQuery(query, schema)
    json.NewEncoder(w).Encode(result)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

func resolveID(p graphql.ResolveParams) (interface{}, error) {
    return 1, nil
}

func resolveName(p graphql.ResolveParams) (interface{}, error) {
    return "hoge", nil
}

在Go中实现GraphQL的服务器端实现中,我们可以通过定义schema并将其与查询一起传递给graphql.Do来进行处理。
我们可以通过运行go run main.go启动服务器,并使用curl来获取数据。

#!/bin/bash
curl -X POST -d '{ id }' http://localhost:8080/
curl -X POST -d '{ name }' http://localhost:8080/
curl -X POST -d '{ id, name }' http://localhost:8080/

从服务器返回的响应在这里。

{"data":{"id":"1"}}
{"data":{"name":"hoge"}}
{"data":{"id":"1","name":"hoge"}}

通过上述的curl命令,可以看到返回了与指定字段相对应的值。

"id": &graphql.Field{
            Type:    graphql.ID,
            Resolve: resolveID,
        },
},

对于指定的字段,value的生成与Resolve中定义的resolveID和resolveName相关,并返回该值。

此外,您还可以将参数传递给查询。
只需对之前的服务器端代码进行一些修改,如下所示。

        "id": &graphql.Field{
            Type: graphql.ID,
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type: graphql.Int,
                },
            },
            Resolve: resolveID,
        },
func resolveID(p graphql.ResolveParams) (interface{}, error) {
    return p.Args["id"], nil
}

在这种情况下,更改之前运行的curl命令,并发送这样的查询。

curl -X POST -d '{ id(id: 100), name }' http://localhost:8080/

根据响应,可以看到将参数传递为100时,它会作为id的值返回。

{"data":{"id":"100","name":"hoge"}}

虽然这次简化了,但在具体实施服务时,将使用传递给Resolve映射函数的参数来提取数据。

突变 (tū

在GraphQL中,读取数据时使用query操作,而在执行写操作(例如写入)时使用mutation操作。之前提到的源代码是使用query操作进行数据获取处理的,接下来我们将介绍使用mutation操作进行服务器实现的方法。

var m graphql.ObjectConfig = graphql.ObjectConfig{
    Name: "User",
    Fields: graphql.Fields{
        "user": &graphql.Field{
            Type: graphql.NewObject(graphql.ObjectConfig{
                Name: "Params",
                Fields: graphql.Fields{
                    "id": &graphql.Field{
                        Type: graphql.Int,
                    },
                    "address": &graphql.Field{
                        Type: graphql.NewObject(graphql.ObjectConfig{
                            Name: "state",
                            Fields: graphql.Fields{
                                "state": &graphql.Field{
                                    Type: graphql.String,
                                },
                                "city": &graphql.Field{
                                    Type: graphql.String,
                                },
                            },
                        }),
                    },
                },
            }),
            Args: graphql.FieldConfigArgument{
                "id": &graphql.ArgumentConfig{
                    Type: graphql.Int,
                },
            },
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                // ここで更新処理をする
                return User{
                    Id: 10000,
                    Address: Address{
                        State: "三宿",
                        City:  "世田谷区",
                    },
                }, nil
            },
        },
    },
}

type User struct {
    Id      int64 `json:"id"`
    Address Address `json:"address"`
}

type Address struct {
    State string `json:"state"`
    City  string `json:"city"`
}

var schemaConfig graphql.SchemaConfig = graphql.SchemaConfig{
    Query:    graphql.NewObject(q),
    Mutation: graphql.NewObject(m),
}

先前的源代码进行修改,为graphql.SchemaConfig定义Mutation字段。
然后,同样地定义graphql.ObjectConfig。

我们可以像上述源代码那样定义结构体,并使用标签进行映射。
现在就来尝试查询吧。

    リクエスト
curl -X POST -d 'mutation { user(id: 100){ id, address{ state, city }}}' http://localhost:8080/
    レスポンス
{"data":{"user":{"address":{"city":"世田谷区","state":"三宿"},"id":10000}}}

最后

由於我很少在國內聽到GraphQL受到熱烈討論的話題,所以我希望國內能增加更多引入GraphQL的案例。

這只是簡單地介紹GraphQL,就到這裡吧。請期待明天的內容!

广告
将在 10 秒后关闭
bannerAds