Closed4

entを使ってみる

UgoUgo

公式

https://entgo.io/ja/

インストール方法

以下でインストールする。

go get -d entgo.io/ent/cmd/ent

以下でModelを作成する。
{PROJECT}/ent/schema に作成される。

go run -mod=mod entgo.io/ent/cmd/ent new User Pet

以下でgenerateしてファイルを生成する

go generate ./ent

生成されるファイルは多いので注意。

entc

entcent で生成されたコードをコンパイルするためのコマンド。

以下のように、プログラムとして実行することもできる。

ent/entc.go
// +build ignore

package main

import (
    "log"

    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
    "entgo.io/ent/schema/field"
)

func main() {
    if err := entc.Generate("./schema", &gen.Config{}); err != nil {
        log.Fatal("running ent codegen:", err)
    }
}

entgo.io/ent/cmd/entc コマンドの実行対象として、generate.goにgenerateする時のコマンドを書いて実行することもできる。

//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate ./schema

DBのテーブルSchemaをモデルに落とし込む

フィールドの指定

go run -mod=mod entgo.io/ent/cmd/ent new XXX

で作成されたモデルに対してFieldを指定していく。

以下のように指定する。

package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema/field"
)

// Todo holds the schema definition for the Todo entity.
type XXX struct {
	ent.Schema
}

// Fields of the Todo.
func (XXX) Fields() []ent.Field {
	return []ent.Field{
		field.Int("id").Unique(),
		field.String("name").Default("unknown"),
	}
}

// Edges of the Todo.
func (XXX) Edges() []ent.Edge {
	return nil
}
  • Fields: そのまま、DBのデータとプログラムのプロパティをマッピングする定義
  • Edges: モデル間の関係性を定義する

ClientをResolverにセットする

generateで生成されたファイルの中の resolver.go にClientを渡して使えるようにする必要がある。

以下のような感じでセットすることで、Modelからclientを利用することができる。

package graph

import (
	"github.com/[your-username]/ent-crud-api/ent"

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

type Resolver struct{ client *ent.Client }

func NewSchema(client *ent.Client) graphql.ExecutableSchema {
	return NewExecutableSchema(Config{
		Resolvers: &Resolver{client},
	})
}
UgoUgo

GraphQLを適用してSchemaを生成する

entcにスキーマの設定し、entgqlを利用することでGraphQLスキーマを生成することができる。

まずは、以下でentgqlをインストールする。

go get entgo.io/contrib/entgql@master

entc.goファイルを作成し、以下のように設定をする。

//go:build ignore
// +build ignore

package main

import (
	"log"

	"entgo.io/contrib/entgql"
	"entgo.io/ent/entc"
	"entgo.io/ent/entc/gen"
)

func main() {
	ex, err := entgql.NewExtension(
		entgql.WithWhereInputs(true),
		entgql.WithConfigPath("gqlgen.yml"),
		entgql.WithSchemaGenerator(),
		entgql.WithSchemaPath("./graph/schema/ent.graphql"),
	)
	if err != nil {
		log.Fatalf("creating entgql extension: %v", err)
	}
	opts := []entc.Option{
		entc.Extensions(ex),
	}
	if err := entc.Generate("./ent/schema", &gen.Config{}, opts...); err != nil {
		log.Fatalf("running ent codegen: %v", err)
	}
}

主なオプション

オプション 説明
WithConfigPath gqlで生成するための設定ファイルの場所(基本的にプロジェクトrootにあるので、そのままファイル名指定で良い)
WithSchemaPath schemaを生成する場所の設定
WithSchemaGenerator GraphQLスキーマの生成を行うようにするオプション

entc.Generate関数に、読み込ませるスキーマのパスを指定する。
基本的にこのスキーマがDBスキーマとの境界になる。
例えば、UsersやBooksなどのよくあるテーブルのDAOとなるオブジェクトがあり、そのスキーマに合わせてgraphqlスキーマを生成する。

この状態で、generate.goファイル作成・設定し、generateを実行する。

ent/generate.go
package ent

//go:generate go run -mod=mod entc.go

./graph/schema/ent.graphql のパスにGraphQLのスキーマが作成される。

参考

https://entgo.io/ja/docs/graphql/

UgoUgo

gqlgen.ymlの設定

entgqlでgenerateするために、読み込ませるための設定ファイルがある。

go run github.com/99designs/gqlgen init

によって生成することができる。

オプション

オプション 説明
schema GraphQLスキーマファイルの場所を指定。glob パターンをサポート。
exec 生成されるサーバーコードの出力先とパッケージを指定。
federation フェデレーション機能を有効にする設定。コメントアウトされている。
model 生成されるモデルコードの出力先とパッケージを指定。
resolver リゾルバの実装に関する設定。レイアウト、ディレクトリ、パッケージ、ファイル名テンプレートを指定。
struct_tag モデルに gqlgen:"fieldName" タグを使用するかどうかのオプション。コメントアウトされている。
omit_slice_element_pointers スライス要素にポインタを使用しないオプション。コメントアウトされている。
omit_interface_checks インターフェースとユニオンの Is<Name>() メソッド生成をスキップするオプション。コメントアウトされている。
omit_complexity ComplexityRoot 構造体の内容と Complexity 関数の生成をスキップするオプション。コメントアウトされている。
omit_gqlgen_file_notice 生成されたファイルにファイル通知コメントを生成しないオプション。コメントアウトされている。
omit_gqlgen_version_in_file_notice 生成されたファイル通知に gqlgen バージョンを含めないオプション。コメントアウトされている。
struct_fields_always_pointers 構造体フィールドにポインタを使用するかどうかのオプション。コメントアウトされている。
resolvers_always_return_pointers リゾルバが構造体に対してポインタを返すかどうかのオプション。コメントアウトされている。
return_pointers_in_unmarshalinput unmarshalInput でポインタを返すかどうかのオプション。コメントアウトされている。
nullable_input_omittable Nullable な入力フィールドを Omittable でラップするオプション。コメントアウトされている。
skip_validation 最終的な検証パスをスキップして生成時間を短縮するオプション。コメントアウトされている。
skip_mod_tidy サーバーコード生成時に go mod tidy の実行をスキップするオプション。コメントアウトされている。
autobind スキーマ内の型名を検索する Go パッケージを指定。
models GraphQL と Go の型システム間のマッピングを定義。ID と Int の型マッピングが設定されている。

参考

https://gqlgen.com/config/

UgoUgo

モデルにGraphQLの情報を渡していく

こちらで作成されたモデルに以下のような関数を追加する。

User Model
package schema

import (
	"entgo.io/ent"
	"entgo.io/ent/schema/field"
	"entgo.io/contrib/entgql"
)

// User holds the schema definition for the User entity.
type User struct {
	ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
	return []ent.Field{
		field.Int("id").
			StructTag(`json:"id,omitempty"`).
			Immutable().
			Annotations(
				entgql.Type("ID"),
			),
		field.String("name").
			NotEmpty().
			Annotations(
				entgql.OrderField("NAME"),
			),
		field.String("email").
			NotEmpty().
			Unique().
			Annotations(
				entgql.OrderField("EMAIL"),
			),
	}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
	return nil
}

// Annotations of the User.
func (User) Annotations() []ent.Annotation {
	return []ent.Annotation{
		entgql.RelayConnection(),
		entgql.QueryField(),
		entgql.Mutations(entgql.MutationCreate(), entgql.MutationUpdate()),
	}
}

Annotationsを追加し、RelayConnectionをその中で定義することで、
Relay形式での取得も自動で生成してくれるようになる。

この時、Relayの形式上、PageInfoなどが必要なため、Orderのスキーマも生成してもらう必要がある。
そのため、FieldにOrderも生成してもらうための設定も追加する。

その他、QueryやMutationsも追加することで、Inputのスキーマなども生成されるようになる。
この辺りは、CRUD APIを参照いただくと分かりやすい。

https://entgo.io/ja/docs/crud

Relay形式出なくて良いのであれば、特に追加しなくても良い。

gqlgen.ymlの修正

autobindにバインドするスキーマを指定し、modelの項目を削除する。
そうすることで、生成されたモデルを使わず、entに組み込まれた状態でRelay形式で取得することが可能になる。

公式の参考例

schema tells gqlgen where the GraphQL schema is located.

schema:

  • ent.graphql

resolver reports where the resolver implementations go.

resolver:
layout: follow-schema
dir: .

gqlgen will search for any type names in the schema in these go packages

if they match it will use them, otherwise it will generate them.

autobind tells gqngen to search for any type names in the GraphQL schema in the

provided package. If they match it will use them, otherwise it will generate new.

autobind:

  • todo/ent
  • todo/ent/todo

This section declares type mapping between the GraphQL and Go type systems.

models:

Defines the ID field as Go 'int'.

ID:
model:
- github.com/99designs/gqlgen/graphql.IntID
Node:
model:
- todo/ent.Noder

generate.goを移す

./ent/ にあるgenerate.goを削除し、rootに移す。

generate.go
package todo

//go:generate go run -mod=mod ./ent/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen

go generate .

参考

https://entgo.io/docs/tutorial-todo-gql

取得方法

通常の取得方法 - 公式参考
func (r *queryResolver) Todos(ctx context.Context) ([]*ent.Todo, error) {
   return r.client.Todo.Query().All(ctx)
}
Relay形式の取得方法 - 公式参考
func (r *queryResolver) Todos(ctx context.Context, after *ent.Cursor, first *int, before *ent.Cursor, last *int, orderBy *ent.TodoOrder) (*ent.TodoConnection, error) {
    return r.client.Todo.Query().
        Paginate(ctx, after, first, before, last,
            ent.WithTodoOrder(orderBy),
        )
}

Todosのfirstや、afterがCursor型になっていないと、entのPaginateにのせることができないので注意。
gqlgen.ymlにmodelがあると、生成されたモデルを元に関数を生成しようとするので、自前でRelay形式にしないといけない。
autobindで紐づけた上でmodelの生成をしなければ、entの機構にのせることができた。

参考

https://entgo.io/docs/graphql

追加できる関数群

関数名 説明
Fields() エンティティのフィールドを定義します。各フィールドの型、制約、デフォルト値などを指定します。
Edges() エンティティ間の関係(エッジ)を定義します。一対一、一対多、多対多の関係を指定できます。
Mixin() 再利用可能なスキーマ部分を追加します。共通のフィールドや振る舞いを複数のエンティティで共有するのに使用します。
Indexes() データベースのインデックスを定義します。クエリのパフォーマンスを向上させるために使用します。
Hooks() エンティティの操作(作成、更新、削除など)の前後に実行されるカスタムロジックを定義します。
Policy() エンティティに対するアクセス制御ポリシーを定義します。特定の操作を許可または拒否するルールを指定できます。
Annotations() スキーマに追加のメタデータを付加します。コード生成やサードパーティツールとの統合に使用されます。

以下は、サンプルコードで使用されている特殊な関数の説明です:

関数名 説明
RelayMixin.Fields() Relay互換性のための追加フィールド(この場合は id)を定義します。
RelayMixin.Interfaces() エンティティが実装するインターフェースを定義します。この場合、Relay の Node インターフェースを実装しています。
このスクラップは1ヶ月前にクローズされました