尝试使用Go + MongoDB来使用GraphQL
GraphQL是一种查询语言。
由于已经有相关文章介绍了,您可以参考那些内容。
GraphQL入门- 令人想使用GraphQL
对比REST,探究“GraphQL”如何改变应用开发流程
GraphQL并非取代REST
GraphQL适用于什么场景
GraphQL的概念并不难,但实际上如何运行可能很难理解。像我自己一样…
我认为日语文本并不是很多,所以如果您能参考一下我会很高兴。
用Go语言实现GraphQL。
这次我们将使用Golang来使用GraphQL。
我想尝试一些Go的东西,所以我研究了一下GraphQL的用法。但是,关于GraphQL的解释大多不是用日语解释的(大多是使用graphql-go的页面),所以我不得不参考一些英文文章来学习。
由于 Golang 和 GraphQL 都是新来者,所以可能存在一些不准确或需要改进的地方,希望您在那些时候能毫不留情地指出。
使用的物品
Golang
go version go1.11.5 darwin/amd64
GraphQL
v0.7.7
MongoDB
MongoDB shell version v4.0.3
macOS
Mojave version 10.14.3
安装方法之类的就不需要特别说明了,所以省略。
实施步骤 (shí shī bù
-
- 连接到MongoDB并创建用于演示的数据
-
- 引入GraphQL
- 使用GraphQL获取数据
由于使用GraphQL,我们不使用关系型数据库管理系统(RDBMS),而是使用NoSQL来处理数据。
虽然GraphQL并不意味着不能与RDBMS一起使用,但据我听说,这样的使用示例较少,基本上较常见的是与NoSQL一起使用。
因此,在这里我们使用了面向文档型的NoSQL数据库MongoDB。
顺便提一下,据说在Hasura这个服务中可以使用PostgreSQL来使用GraphQL。
连接到MongoDB并创建用于演示的数据。
すでにMongoDBがインストールされていると言うことを前提として、作業でディレクトリで$ sudo mongod –dbpath ./を実行します。
するとディレクトリ内に様々なファイルやフォルダが作成され、MongoDBが起動します。
以当前状态下运行$ mongo命令,可以开始与MongoDB建立连接。
请参考以下指南以了解可在 MongoDB 中使用的命令。
MongoDB的基本用法请参考。
请在这里使用基本的方法。
> db ・・・ データベース参照
> show collections ・・・ コレクションのリストを表示
> db.collection_name.find() ・・・ 指定したコレクションの全件取得
> db.collection_name.drop() ・・・ 指定したコレクションの削除
您将使用这些命令。
在像MongoDB这样的文档导向的NoSQL中,您可以将集合=表格,文档=记录这样来考虑。
引入GraphQL
如果要在Golang中使用GraphQL,可以选择一些包,如graphql-go和GQLgen,但在这里我们将使用graphql-go。
$ go get github.com/graphql-go/graphql
import (
"github.com/graphql-go/graphql"
)
只需一种选择:使用Go来使用GraphQL
// localのデータベースtestに接続
func main() {
session, _ := mgo.Dial("mongodb://localhost/test")
defer session.Close()
db := session.DB("test")
}
在MongoDB中进行插入和查找
// insert into users
newUser := &User{
Id: bson.NewObjectId(),
Name: "名前",
Email: "アドレス",
Password: "パス"
}
col := db.C("users")
if err := col.Insert(newUser); err != nil {
log.Fatalln(err)
}
// fetch all users
var AllUsers []User
query := db.C("users").Find(bson.M{})
query.All(&AllUsers)
// AllUsersは構造体なので、JSONにする場合はjson.Marshal()を。
请随意创建数据。
关于更新和删除操作,在此不予以解释。
最快捷方便的方法是查阅官方文档。
MongoDB 文档。
利用 GraphQL 获取数据。
我們將創建一個名為 User 的結構體,將其用作GraphQL的定義的示例。
type User struct {
Id bson.ObjectId `bson:"_id"` // MongoDBで自動生成される_idとUser.Idを同一のものとする
Name string `bson:"name"`
Email string `bson:"email"`
Password string `bson:"password"`
}
bson是什么?就类似于json,可以认为没有问题。MongoDB超入门。
次に、スキーマとリクエストを用意します(この2つの正式な名称ではありませんが、わかりやすいと思ったのでこう呼ばせていただきます)。
GraphQLでデータを取って来るためには、これら2つの要素が必要になります。
最終的には以下のようなコードにスキーマとリクエストを突っ込みます。
params := graphql.Params{
Schema: schema, // スキーマ
RequestString: request, // リクエスト
}
r := graphql.Do(params) // GraphQLの実行し、rに結果を格納
if len(r.Errors) > 0 { // エラーがあれば
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r) // 構造体をJSONに変換して、
fmt.Printf("%s \n", rJSON) // プリントする。
这个词意味着架构或结构。
下方代码的第四行就是模式。
模式是指GraphQL投递请求的目标数据集以及该数据集的设置,类似于规范文件。
我们可以从数据库中提取数据,还可以设置每个数据的类型和通过请求传递的参数,如id等。
在GraphQL中,首先需要预先从数据库中提取出所有相关的数据。
因此GraphQL并不直接与数据库交互。
GraphQL所要求的是已经提取出的数据集,可以说是将这个数据集过滤,取出所需的数据。
// MongoDBと接続してAllUsers(構造体)を用意する
session, _ := mgo.Dial("mongodb://localhost/test")
defer session.Close()
db := session.DB("test")
var AllUsers []User // フィールド定義時にこれを使う
query := db.C("users").Find(bson.M{})
query.All(&AllUsers)
var userType = graphql.NewObject( // GraphQL用の型定義
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
"email": &graphql.Field{
Type: graphql.String,
},
"password": &graphql.Field{
Type: graphql.String,
},
},
},
)
fields := graphql.Fields{ // フィールド(リクエストで使われるデータの扱い方に関する設定)
"user": &graphql.Field{
Type: userType,
Description: "Fetch user by Id",
Args: graphql.FieldConfigArgument{ // リクエストに渡す引数についての設定
"id": &graphql.ArgumentConfig{
Type: graphql.String,
},
},
Resolve: func(param graphql.ResolveParams) (interface{}, error) { // 帰って来るデータの設定
id, ok := param.Args["id"].(string)
if ok {
for _, user := range AllUsers { // AllUsersはmonngoDBから取ってきた全てUserのデータ
if user.Id.Hex() == id { // Hex()でMongoDBのobjectIdをstringに変換する
return user, nil
}
}
}
return nil, nil
},
},
"list": &graphql.Field{
Type: graphql.NewList(userType),
Description: "Fetch users list",
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
return AllUsers, nil // AllUsersはmonngoDBから取ってきた全てUserのデータ
},
},
}
rootQuery := graphql.ObjectConfig{
Name: "RootQuery",
Fields: fields,
}
schemaConfig := graphql.SchemaConfig{
Query: graphql.NewObject(rootQuery),
}
schema, err := graphql.NewSchema(schemaConfig) // スキーマ
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
作为一个概念,
-
- 在GraphQL中定义类型
-
- 定义字段(设置返回请求数据时的设置。在这里定义了”user”和”list”)
- 将其作为Schema配置整理(大约在ObjectConfig和SchemaConfig中),使用NewSchema创建模式
比较粗略地概括一下,大致就是这个样子。
关于字段,”user”用于获取特定用户的请求,”list”用于获取所有用户的请求。
因此,在”用户”的Resolve选项中,我们返回与传递的请求参数”id”相匹配的user.Id的所有者。
Resolve: func(param graphql.ResolveParams) (interface{}, error) {
id, ok := param.Args["id"].(string) // リクエストで渡されることになる引数の"id"
if ok {
for _, user := range AllUsers { // MongoDBから引っ張って来る全てのuserのデータ
if user.Id.Hex() == id { // 引数の"id"と合致すれば通る
return user, nil
}
}
}
return nil, nil
},
“list”ではAllUser全てをreturnしていることも確認しておいてください。
また、”list”でTypeの定義時にgraphql.NewList(userType)としているのは、この”list”が配列であるためです。
単体であれば”user”と同様にuserTypeのみを設定すれば良いですが、あくまでリストなのでこのように設定しています。
请求
请求是GraphQL中的查询表达式(请注意,请求这个称呼并不是官方称呼,只是我在这里使用的一个便于解释的词)。
request := `
{
user(id: "5c94f4d7e803694b2d09da75") {
id
name
email
password
}
}
`
userの後に()で指定している”id”こそ、先ほどスキーマで説明した「リクエストで渡されることになる引数」そのものです。このように設定された引数が、先ほどのResolve内の関数の中で処理されます。
上記のリクエストでは、idに合致するドキュメント(レコード)のid, name, email, passwordを返します。
なので、nameだけを指定して書くと、もちろん該当のドキュメントのnameだけを返します。
また、このリクエストでは、”user”の情報(user1人の情報)しか引き出せません。
全てのユーザーの情報を取得するためには、先ほどスキーマで作成した”list”を指定する必要があります。
request := `
{
list {
id
name
email
password
}
}
`
このリクエストによって全てのユーザーが配列で返って来るようになります。
那么,我们将使用这些模式和请求来执行GraphQL。
使用GraphQL执行。
我会回到先前的代码。
params := graphql.Params{
Schema: schema, // スキーマ
RequestString: request, // リクエスト
}
r := graphql.Do(params) // GraphQLの実行
if len(r.Errors) > 0 { // エラーがあれば
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r) // 構造体をJSONに変換して、
fmt.Printf("%s \n", rJSON) // プリントする。
schemaをSchemaに、requestをRequestStringに設定して、graphql.DoでGraphQLを実行します。
すると、返ってきた結果が構造体として変数に格納されるので、ここではjsonに変換してからプリントしています。
ファイルを実行すると、
$ go run main.go
{"data":{"user":{"email":"ai@kizuna","id":"ObjectIdHex(\"5c94f4d7e803694b2d09da75\")","name":"キズナアイ","password":"kizunAI"}}}
こんな感じで帰って来ると思います(ここではクエリーにuser(id: “id_string”)で指定して一つだけ取ってきています。listなら配列になります)。
发布并使用
GraphQL有一个优点,就是不需要像RESTfulAPI那样准备多个端点。
APIを一つ作っておいて、POSTして要求を渡すようにすれば、都度そのようきゅうに応じた結果を得ることができるようになります。
そうすることで、ユーザーを取って来るにしても、GET:usersやらGET:users/:idやらとエンドポイントを分ける手間が省けるようになります。
嗯,如果有兴趣的话,我打算写到服务器架设和使用的部分,现在就先写到这里吧。
嗯,你只是在获取数据吧?
在这里介绍的GraphQL的使用方法中,只解释了如何获取数据。
但是,除了获取数据之外,GraphQL还可以进行插入(Insert)和更新(Update)等操作(尽管如前所述,GraphQL并不直接执行插入等操作,所以严格来说这种表述可能是不正确的)。而且,这一切都可以在不增加端点的情况下实现。
关于如何处理这个问题,考虑到页面过于冗长可能并不理想,我打算另设一个页面。一旦完成,我会在这里提供链接,你也可以参考那个页面。
这并不是很长,而且代码的内容可能比文字多。
在此需要解释一点,刚才提到的“要求”一词在请求和模式中包含了查询和变更的概念。
查询…数据获取请求
变更…数据更新请求
在GraphQL中,我们进行了处理的分配。
在这里所描述的请求适用于该查询,并且在执行Insert等操作时需要使用这个Mutation。
我写了一篇有关实现变异的文章!
我使用Go来实现GraphQL的变异。
请参考下面的官方页面,其中有关于查询和变更的解释。
样本代码
在这个示例代码中没有使用MongoDB。
只需复制粘贴并运行,即可执行GraphQL。
由于将用户的ID更改为int类型,并且字段的定义也稍有不同,因此请中止操作。
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/graphql-go/graphql"
)
type User struct {
Id int
Name string
Email string
Password string
}
var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.Int,
},
"name": &graphql.Field{
Type: graphql.String,
},
"email": &graphql.Field{
Type: graphql.String,
},
"password": &graphql.Field{
Type: graphql.String,
},
},
},
)
func generateUsers() []User {
user := User{
Id: 1,
Name: "キズナアイ",
Email: "ai@kizuna",
Password: "kizunAI",
}
var users []User
users = append(users, user)
return users
}
func main() {
users := generateUsers()
fields := graphql.Fields{
"user": &graphql.Field{
Type: userType,
Description: "Fetch user by Id",
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.Int,
},
},
Resolve: func(param graphql.ResolveParams) (interface{}, error) {
id, ok := param.Args["id"].(int)
if ok {
for _, user := range users {
if int(user.Id) == id {
return user, nil
}
}
}
return nil, nil
},
},
"list": &graphql.Field{
Type: graphql.NewList(userType),
Description: "Fetch users list",
Resolve: func(params graphql.ResolveParams) (interface{}, error) {
return users, nil
},
},
}
rootQuery := graphql.ObjectConfig{
Name: "RootQuery",
Fields: fields,
}
schemaConfig := graphql.SchemaConfig{
Query: graphql.NewObject(rootQuery),
}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
request := `
{
user(id: 1) {
id
name
email
password
}
}
`
params := graphql.Params{
Schema: schema,
RequestString: request,
}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON)
}
这个Go GraphQL初学者教程很有参考价值。