entを使ってみる
公式
インストール方法
以下でインストールする。
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
entc は ent
で生成されたコードをコンパイルするためのコマンド。
以下のように、プログラムとして実行することもできる。
// +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},
})
}
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を実行する。
package ent
//go:generate go run -mod=mod entc.go
./graph/schema/ent.graphql のパスにGraphQLのスキーマが作成される。
参考
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 の型マッピングが設定されている。 |
参考
モデルに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を参照いただくと分かりやすい。
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に移す。
package todo
//go:generate go run -mod=mod ./ent/entc.go
//go:generate go run -mod=mod github.com/99designs/gqlgen
go generate .
参考
取得方法
通常の取得方法 - 公式参考
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の機構にのせることができた。
参考
追加できる関数群
関数名 | 説明 |
---|---|
Fields() |
エンティティのフィールドを定義します。各フィールドの型、制約、デフォルト値などを指定します。 |
Edges() |
エンティティ間の関係(エッジ)を定義します。一対一、一対多、多対多の関係を指定できます。 |
Mixin() |
再利用可能なスキーマ部分を追加します。共通のフィールドや振る舞いを複数のエンティティで共有するのに使用します。 |
Indexes() |
データベースのインデックスを定義します。クエリのパフォーマンスを向上させるために使用します。 |
Hooks() |
エンティティの操作(作成、更新、削除など)の前後に実行されるカスタムロジックを定義します。 |
Policy() |
エンティティに対するアクセス制御ポリシーを定義します。特定の操作を許可または拒否するルールを指定できます。 |
Annotations() |
スキーマに追加のメタデータを付加します。コード生成やサードパーティツールとの統合に使用されます。 |
以下は、サンプルコードで使用されている特殊な関数の説明です:
関数名 | 説明 |
---|---|
RelayMixin.Fields() |
Relay互換性のための追加フィールド(この場合は id )を定義します。 |
RelayMixin.Interfaces() |
エンティティが実装するインターフェースを定義します。この場合、Relay の Node インターフェースを実装しています。 |