用Go学习GraphQL服务器端(3)- 使用自定义的模式创建GraphQL服务器

你好。

第三部分是关于“使用自定义模式创建GraphQL服务器”的内容。

关于这一章

在Part2中,我们运行了gqlgen的示例应用程序,但在实际开发场景中,我们将根据自定义的GraphQL模式创建服务器的内容。
因此,本次我们将介绍从一个具有非TODO应用程序的原始模式文件的状态开始,创建服务器端代码的步骤。

本次主题 – 简化版本的GitHub API v4

我认为在实际存在的GraphQL API中,最著名的应该是GitHub的API。
因此,这次我们的目标是模仿GitHub API v4的内容。

 

由于时间和篇幅的限制,很遗憾无法完整地实现GitHub API v4的所有内容。因此,本次我们只选择了一些关键点进行简化并进行演示。

这次再现的GraphQL模式

directive @isAuthenticated on FIELD_DEFINITION

scalar DateTime

scalar URI

interface Node {
  id: ID!
}

type PageInfo {
  endCursor: String
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
}

type Repository implements Node {
  id: ID!
  owner: User!
  name: String!
  createdAt: DateTime!
  issue(
    number: Int!
  ): Issue
  issues(
    after: String
    before: String
    first: Int
    last: Int
  ): IssueConnection!
  pullRequest(
    number: Int!
  ): PullRequest
  pullRequests(
    after: String
    before: String
    first: Int
    last: Int
  ): PullRequestConnection!
}

type User implements Node {
  id: ID!
  name: String!
  projectV2(
    number: Int!
  ): ProjectV2
  projectV2s(
    after: String
    before: String
    first: Int
    last: Int
  ): ProjectV2Connection!
}

type Issue implements Node {
  id: ID!
  url: URI!
  title: String!
  closed: Boolean!
  number: Int!
  author: User!
  repository: Repository!
  projectItems(
    after: String
    before: String
    first: Int
    last: Int
  ): ProjectV2ItemConnection!
}

type IssueConnection {
  edges: [IssueEdge]
  nodes: [Issue]
  pageInfo: PageInfo!
  totalCount: Int!
}

type IssueEdge {
  cursor: String!
  node: Issue
}

type PullRequest implements Node {
  id: ID!
  baseRefName: String!
  closed: Boolean!
  headRefName: String!
  url: URI!
  number: Int!
  repository: Repository!
  projectItems(
    after: String
    before: String
    first: Int
    last: Int
  ): ProjectV2ItemConnection!
}

type PullRequestConnection {
  edges: [PullRequestEdge]
  nodes: [PullRequest]
  pageInfo: PageInfo!
  totalCount: Int!
}

type PullRequestEdge {
  cursor: String!
  node: PullRequest
}

type ProjectV2 implements Node {
  id: ID!
  title: String!
  url: URI!
  number: Int!
  items(
    after: String
    before: String
    first: Int
    last: Int
  ): ProjectV2ItemConnection!
  owner: User!
}

type ProjectV2Connection {
  edges: [ProjectV2Edge]
  nodes: [ProjectV2]
  pageInfo: PageInfo!
  totalCount: Int!
}

type ProjectV2Edge {
  cursor: String!
  node: ProjectV2
}

union ProjectV2ItemContent = Issue | PullRequest

type ProjectV2Item implements Node {
  id: ID!
  project: ProjectV2!
  content: ProjectV2ItemContent
}

type ProjectV2ItemConnection {
  edges: [ProjectV2ItemEdge]
  nodes: [ProjectV2Item]
  pageInfo: PageInfo!
  totalCount: Int!
}

type ProjectV2ItemEdge {
  cursor: String!
  node: ProjectV2Item
}

type Query {
  repository(
    name: String!
    owner: String!
  ): Repository

  user(
    name: String!
  ): User @isAuthenticated

  node(
    id: ID!
  ): Node

}

input AddProjectV2ItemByIdInput {
  contentId: ID!
  projectId: ID!
}

type AddProjectV2ItemByIdPayload {
  item: ProjectV2Item
}

type Mutation {
  addProjectV2ItemById(
    input: AddProjectV2ItemByIdInput!
  ): AddProjectV2ItemByIdPayload
}

本次再现的GraphQL模式

这次准备的物品在这里。

    • User: GitHubにアカウント登録しているユーザー

そのユーザーが作成したレポジトリ一覧が取得できる

Repository

レポジトリの所有者であるユーザーの情報が取得できる
レポジトリが持つIssue/PR一覧が取得できる
Issue/PR番号を指定することでそのIssue/PRの詳細が取得できる

Issue

Issue作成ユーザーの情報が取得できる
そのIssueが紐づいているレポジトリの情報が取得できる
そのIssueが紐づいているProjectV2Item一覧が取得できる

PullRequest

そのPRが紐づいているレポジトリの情報が取得できる
そのPRが紐づいているProjectV2のカードが取得できる

ProjectV2: IssueやPRをカードにしてかんばんを作ることができる実在の機能

そのかんばんの作者であるユーザーの情報が取得できる
かんばんのカード一覧が取得できる

ProjectV2Item: かんばんのカード

そのカードがどこのかんばんのものなのか情報を取得できる
そのカードの実態となっているIssueもしくはPRの情報を取得できる

这次要实现的查询/变更操作

我們需要在實際的GitHub API v4中進行各種操作,因此我們打算創建以下四個查詢和變異。

    • クエリ

node: 各オブジェクト(=ノード)に割り当てられた一意のIDからオブジェクト情報を取得する

user: ユーザー名からユーザーの情報を取得する

repository: 作成者とレポジトリ名からレポジトリの情報を取得する

ミューテーション

addProjectV2ItemById: Issue/PRをかんばんのカードとして追加する

准备自己的GraphQL服务器仓库。

我已经准备好了架构,然后准备了一个开发仓库,并希望在那里生成与我自己的架构相符的服务器代码。

使用go mod init来准备Go项目

在创建一个新目录后执行`go mod init`命令,并将gqlgen通过`go get`命令放置在这个目录中,与前一章节的操作相同。

$ go mod init github.com/saki-engineering/graphql-sample
$ go get -u github.com/99designs/gqlgen

自定义gqlgen生成的目录结构。

既然如此,让我们尝试自定义gqlgen生成的代码结构,以符合个人喜好。
这次我们的最终目标是下面这样的结构。差异显示了与默认设置的区别。

 .
+├─ internal
+│   └─ generated.go # このファイルの中身は編集しない
 ├─ graph
-│   ├─ generated.go # このファイルの中身は編集しない
 │   ├─ model
 │   │   └─ models_gen.go # 定義した型が構造体として定義される
 │   ├─ resolver.go
-│   ├─ schema.graphqls # スキーマ定義
 │   └─ schema.resolvers.go # この中に、各queryやmutationのビジネスロジックを書く
+├─ schema.graphqls # スキーマ定義
 ├─ gqlgen.yml # gqlgenの設定ファイル
 ├─ server.go # エントリポイント
 ├─ go.mod
 └─ go.sum

为了生成这个结构,我们需要将gqlgen.yml的内容放置在存储库的根目录下,并设置如下内容。(差异是指与gqlgen init生成的默认文件的区别)

# 自動生成コードの元となるGraphQLスキーマがどこに配置してあるか
schema:
-  - graph/*.graphqls
+  - ./*.graphqls

# 自動生成されるgeneated.goの置き場所
exec:
-  filename: graph/generated.go
-  package: graph
+  filename: internal/generated.go
+  package: internal

# スキーマオブジェクトに対応するGo構造体の置き場所
model:
  filename: graph/model/models_gen.go
  package: model

# リゾルバコードの置き場所
resolver:
  layout: follow-schema
  dir: graph
  package: graph

使用gqlgen generate进行代码生成

目前目录的内容应该如下所示。

.
 ├─ schema.graphqls # 自作GraphQLスキーマ
 ├─ gqlgen.yml # gqlgenの設定ファイル
 ├─ go.mod
 └─ go.sum

为了生成符合schema.graphqls模式的Go代码,我们执行gqlgen generate命令。

$ gqlgen generate

然后,generated.go和resolver code将在目录中生成,并处于以下状态。

 .
+├─ internal
+│   └─ generated.go # このファイルの中身は編集しない
+├─ graph
+│   ├─ model
+│   │   └─ models_gen.go # 定義した型が構造体として定義される
+│   ├─ resolver.go
+│   └─ schema.resolvers.go # この中に、各queryやmutationのビジネスロジックを書く
 ├─ schema.graphqls # スキーマ定義
 ├─ gqlgen.yml # gqlgenの設定ファイル
 ├─ go.mod
 └─ go.sum

服务器入口点的布置

在gqlgen generate命令中,与gqlgen init不同的是不会生成作为服务器入口点的server.go文件。
因此,需要参考通过gqlgen init生成的server.go文件,自己手动创建入口点。

服务器.go

import (
	// (一部抜粋)
	"github.com/saki-engineering/graphql-sample/graph"
+	"github.com/saki-engineering/graphql-sample/internal"
)

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

-	srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))
+	srv := handler.NewDefaultServer(internal.NewExecutableSchema(internal.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))
}

现在,我们已经建立了使用自定义模式创建GraphQL服务器的框架。

下一个部分 (xià

我打算在以后使用gqlgen命令对自动生成的解析器模板进行编辑,然后创建对实际请求返回简单应用的部分。

以上是。
謝謝。
請多關照。