在Go语言中学习GraphQL服务器端(2)-尝试运行GraphQL服务器

你好。

第二部分介绍如何“运行GraphQL服务器”。

关于这一章

让我们立即来看一下GraphQL服务器端代码。

这次我们将使用一个名为gqlgen的Go语言GraphQL服务器端库。
gqlgen命令具有生成TODO管理API代码的示例应用的功能,因此我们将生成这个代码并将其实际运行起来。

gqlgen 命令

gqlgen命令是一个用于从GraphQL模式信息自动生成使用Go编写的服务器端代码的命令。

安装

由于gqlgen命令是使用Go语言编写的命令,因此可以使用go install命令进行安装。

$ go install github.com/99designs/gqlgen@latest
$ gqlgen version
v0.17.22

生成样本代码

在中文中,可以这样表述:
生成TODO管理API的样例应用程序代码的命令是gqlgen init。
在为放置样例代码而准备的目录中执行go mod init后,尝试执行gqlgen init。

$ go mod init my_gql_server
$ go get -u github.com/99designs/gqlgen
$ gqlgen init
Creating gqlgen.yml
Creating graph/schema.graphqls
Creating server.go
Generating...

Exec "go run ./server.go" to start GraphQL server

自动生成代码的说明

从这里开始,我们将解释由gqlgen init生成的代码。

目录结构

以下是生成的代码列表。

.
├─ graph
│   ├─ generated.go # リゾルバをサーバーで稼働させるためのコアロジック部分
│   ├─ model
│   │   └─ models_gen.go # GraphQLのスキーマオブジェクトがGoの構造体として定義される
│   ├─ resolver.go # ルートリゾルバ構造体の定義
│   ├─ schema.graphqls # GraphQLスキーマ定義
│   └─ schema.resolvers.go # ビジネスロジックを実装するリゾルバコードが配置
├─ gqlgen.yml # gqlgenの設定ファイル
├─ server.go # サーバーエントリポイント
├─ go.mod
└─ go.sum

图/模式.graphqls – GraphQL模式定义

GraphQL存在着一个被称为模式的概念。

    • どのようなオブジェクト型が用意されているのか

 

    どのようなクエリ・ミューテーションがあるのか

我会将这些信息写入一个拓展名为.graphqls的文件中。

这里放置的是使用gqlgen init命令生成的示例应用程序代码,用于管理TODO事项。所以,这里放置了用于TODO管理的模式。

生成的模式内容

# GraphQL schema example
#
# https://gqlgen.com/getting-started/

type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
}

type Query {
  todos: [Todo!]!
}

input NewTodo {
  text: String!
  userId: String!
}

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

graph/model/models_gen.go中定义了对象结构体。

以下の機能があります。
– タスクの追加と削除
– 期限の設定
– タスクの優先順位の設定
– タスクの分類とタグの追加
– 完了したタスクのチェックマークの表示

    • 追加されたTODO

 

    TODOタスクのオーナーとなるユーザー

有两种类型的对象存在,每种对象都在graph/schema.graphqls中定义了自己的模式。

在models_gen.go文件中,定义了与这个TODO对象和用户对象对应的Go结构体类型。

type Todo struct {
	ID   string `json:"id"`
	Text string `json:"text"`
	Done bool   `json:"done"`
	User *User  `json:"user"`
}

type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

图形/解析器.go·图形/模式.resolvers.go – 解析器代码

这里生成了服务器端GraphQL的核心部分,称为”解析器”的模板。在resolver.go中,定义了一个名为Resolver的解析器结构类型;在schema.resolvers.go中定义了一些方法。

// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.

type Resolver struct{}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
	panic(fmt.Errorf("not implemented: CreateTodo - createTodo"))
}

// Todos is the resolver for the todos field.
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
	panic(fmt.Errorf("not implemented: Todos - todos"))
}

以下是GraphQL模式中定义的查询和变更,以及生成的解析器方法之间的对应关系。

    • ミューテーションcreateTodo(input: NewTodo!): Todo!が呼ばれたときにCreateTodoメソッドが呼ばれる

 

    クエリtodos: [Todo!]!が呼ばれたときにはTodosメソッドが呼ばれる

在GraphQL的模式中定义的变异和查询是再次引用的。

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

type Query {
  todos: [Todo!]!
}

在服务器端实现GraphQL的过程中,需要重写这一部分来创建业务逻辑,并在接收请求时包含panic。

// ミューテーションcreateTodoが呼ばれた際に実行されるコード
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
	// TODO:
	// ユーザーから受け取ったリクエスト情報inputを使ってTODOを登録し、
	// その登録されたTODOの情報をmodel.TODO型の戻り値に入れて返却
}

// クエリtodosが呼ばれた際に実行されるコード
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
	// TODO:
	// レスポンスに含めるTODO一覧を、戻り値[]*model.Todoに入れて返却
}

graph/generated.go- 主要逻辑部分用于在服务器上运行解析器。

從開發者的角度來看,只需填寫在schema.resolvers.go中生成的解析方法的內容即可,但本質上講

    • GraphQLのリクエストがサーバーに届く

 

    • リクエストに含まれているクエリを解釈し「呼ばれているのはスキーマに定義されたtodosクエリだ」と判断する

 

    リゾルバ構造体のTodosメソッドを呼ぶ

需要一系列的判断逻辑。

在todos查询的情况下,开发者编写的解析方法的返回值为[]model.Todo类型。然而,为了使API服务器正常运行,生成将该[]model.Todo类型转化为用户返回的JSON响应体的部分也是必不可少的逻辑。

这样

    • ユーザーから受け取ったHTTPリクエストボディに含まれているクエリから、適切なリゾルバを呼び出す

 

    リゾルバが返した結果をHTTPレスポンスに変換して、ユーザーに返却する

生成的代码generated.go实现了作为HTTP服务器运行的解析器的桥接部分。

生成的代码存在于generated.go文件中,是由gqlgen根据GraphQL模式自动生成的。因此,开发者不需要手动编辑或修改这个文件的内容。

server.go – 服务器入口点

这是GraphQL服务器的入口点部分。

    • デフォルトでは8080番ポートで稼働し、

/queryにGraphQLリクエストを送ったら結果が返ってきて、
/をブラウザで開くとクエリを実行するためのPlaygroundが使える

已实施了这项功能。

生成的入口点的内容

server.go
package main

import (
	"log"
	"my_gql_server/graph"
	"net/http"
	"os"

	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
)

const defaultPort = "8080"

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}

	srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

gqlgen.yml

gqlgen.yml是一个用于编写在使用gqlgen命令生成代码时的配置的yaml文件。
例如,可以配置以下项目。

    • スキーマに定義されたオブジェクトをGoの構造体にしたものはどこに置くのか(デフォルトでgraph/model直下のmodelパッケージ)

generated.goをどこに生成するのか(デフォルトでgraph直下)
GraphQLのスキーマファイルの置き場所(デフォルトでgraph直下にある拡張子.graphqlsファイルを使用)

生成的 gqlgen.yml 文件内容(摘录)

# Where are all the schema files located? globs are supported eg  src/**/*.graphqls
schema:
  - graph/*.graphqls

# Where should the generated server code go?
exec:
  filename: graph/generated.go
  package: graphs

# Where should any generated models go?
model:
  filename: graph/model/models_gen.go
  package: model

试着运行生成的样例TODO管理API服务器

现在我们已经介绍了生成的代码,那就让我们立刻来运行它吧。

填写解析器的内容

首先,让我们填充在graph/schema.resolvers.go中生成的解析器的内容。
理论上应该连接到数据库并执行数据的插入和查询语句,但由于我们只想确认操作是否正常,所以我们将只返回一个适当的TODO结构。

// CreateTodo is the resolver for the createTodo field.
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
-	panic(fmt.Errorf("not implemented: CreateTodo - createTodo"))
+	return &model.Todo{
+		ID:   "TODO-3",
+		Text: input.Text,
+		User: &model.User{
+			ID:   input.UserID,
+			Name: "name",
+		},
+	}, nil
}

// Todos is the resolver for the todos field.
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
-	panic(fmt.Errorf("not implemented: Todos - todos"))
+	return []*model.Todo{
+		{
+			ID:   "TODO-1",
+			Text: "My Todo 1",
+			User: &model.User{
+				ID:   "User-1",
+				Name: "テスト",
+			},
+			Done: true,
+		},
+		{
+			ID:   "TODO-2",
+			Text: "My Todo 2",
+			User: &model.User{
+				ID:   "User-1",
+				Name: "テスト",
+			},
+			Done: false,
+		},
+	}, nil
}

启动服务器

通过运行server.go作为入口点,可以启动GraphQL服务器。默认情况下,会使用8080端口。

$ go run ./server.go
2023/10/17 01:07:34 connect to http://localhost:8080/ for GraphQL playground

在Playground上执行查询

当在浏览器中打开http://localhost:8080/时,您可以访问以下的Playground来执行查询。

playground.png

执行Todos查询

我尝试执行todos查询。

query {
  todos {
    id
    text
    done
    user {
      name
    }
  }
}
result-1.png

經過驗證,可以確認在解析器內靜態地返回的TODO結構體內容已經被獲取。

执行createTodo变更。

接下来,让我们尝试执行 createTodo 变异。

mutation {
  createTodo(input: {
    text: "test-create-todo"
    userId: "test-user-id"
  }){
    id
    text
    done
    user {
      id
      name
    }
  }
}
result-2.png

这里也显示了通过解析器返回的内容。经过测试,操作成功。

接下来的部分 (Cì de

我这次尝试了运行gqlgen提供的示例应用程序,但从下次开始,我想用自己的内容来创建GraphQL服务器。

就是这样。
谢谢。
请多关照。

广告
将在 10 秒后关闭
bannerAds