1. ent ツールのインストール

ent コード生成ツールをインストールするには、次の手順に従う必要があります:

まず、システムに Go 言語環境がインストールされていることを確認してください。次に、次のコマンドを実行して ent ツールを取得します:

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

このコマンドは ent ツールのコードをダウンロードしますが、コンパイルして直ちにインストールするわけではありません。どこからでも使用できるように ent$GOPATH/bin ディレクトリにインストールする場合は、次のコマンドも実行する必要があります:

go install entgo.io/ent/cmd/ent

インストールが完了したら、ent -h を実行して、ent ツールが正しくインストールされているかどうかを確認し、利用可能なコマンドや手順を表示できます。

2. スキーマの初期化

2.1 ent init を使用したテンプレートの初期化

ent をコード生成に使用するための最初のステップは、新しいスキーマファイルを作成することです。次のコマンドを実行することで、スキーマテンプレートを初期化できます:

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

このコマンドにより、user.go および pet.go の2つの新しいスキーマファイルが作成され、それらは ent/schema ディレクトリに配置されます。ent ディレクトリが存在しない場合、このコマンドは自動的に作成します。

プロジェクトのルートディレクトリで ent init コマンドを実行することは、プロジェクトディレクトリの構造と整合性を保つための良い方法です。

2.2 スキーマファイルの構造

ent/schema ディレクトリでは、各スキーマが Go 言語のソースファイルに対応しています。スキーマファイルは、フィールドやエッジ(関係)を含むデータベースモデルを定義する場所です。

例えば、user.go ファイルでは、ユーザーモデルを定義し、ユーザ名や年齢などのフィールドを定義し、ユーザとペットの関係を定義するかもしれません。同様に、pet.go ファイルでは、ペットモデルやペット名、タイプ、ユーザとペットの関係などを定義します。

これらのファイルは、ent ツールによって、データベース操作や CRUD(作成、読み取り、更新、削除)操作のためのクライアントコードを含む対応する Go コードを生成するために使用されます。

// ent/schema/user.go
package schema

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

// User はユーザーエンティティのスキーマを定義します。
type User struct {
    ent.Schema
}

// Fields メソッドはエンティティのフィールドを定義するために使用されます。
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").Unique(),
        field.Int("age").Positive(),
    }
}

// Edges メソッドはエンティティの関連を定義するために使用されます。
func (User) Edges() []ent.Edge {
    // 関連については、次のセクションで詳しく説明します。
}

ent のスキーマファイルでは、Go 言語の型や関数を使用してデータベースモデルの構造を宣言し、ent フレームワークが提供する API を使用してこれらの構造を定義します。このアプローチにより、ent は非常に直感的で拡張しやすくなります。さらに、Go 言語の型による強力なタイピングを活用しています。

3.1 コード生成の実行

ent フレームワークでのコード生成を実行することは重要なステップです。このコマンドにより、定義されたスキーマに基づいて対応する Go コードファイルが生成され、その後の開発作業が容易になります。

go generate ./ent

上記のコマンドは、プロジェクトのルートディレクトリで実行する必要があります。go generate が呼び出されると、特定の注釈を含むすべての Go ファイルを検索し、注釈で指定されたコマンドを実行します。この例では、このコマンドは ent のコードジェネレータを指定しています。

実行前にスキーマの初期化と必要なフィールドの追加が完了していることを確認してください。その後でなければ、生成されたコードにすべての必要なパーツが含まれません。

3.2 生成されたコードアセットの理解

生成されたコードアセットには複数のコンポーネントが含まれており、それぞれ異なる機能を持っています:

  • クライアントとTxオブジェクト: データグラフとやり取りするために使用されます。クライアントはトランザクション(Tx)を作成したり、直接データベース操作を実行するためのメソッドを提供します。

  • CRUDビルダー: 各スキーマタイプに対して、対応するエンティティの操作ロジックを簡素化するために、作成、読み取り、更新、削除のためのビルダーを生成します。

  • エンティティオブジェクト (Go構造体): スキーマ内の各タイプに対応するGo構造体を生成し、これらの構造体をデータベースのテーブルにマッピングします。

  • 定数と述語パッケージ: ビルダーとのやり取りのための定数や述語を含んでいます。

  • マイグレーションパッケージ: SQL方言に適したデータベースマイグレーションのためのパッケージです。

  • フックパッケージ: 変更ミドルウェアを追加する機能を提供し、エンティティの作成、更新、削除の前後にカスタムロジックを実行することができます。

生成されたコードを調査することで、entフレームワークがスキーマのためのデータアクセスコードを自動化する仕組みをより深く理解することができます。

4. コード生成オプション

ent generateコマンドは、コード生成プロセスをカスタマイズするためのさまざまなオプションをサポートしています。以下のコマンドを使用して、サポートされるすべての生成オプションを確認できます:

ent generate -h

以下は一般的に使用されるコマンドラインオプションのいくつかです:

  • --feature strings: 追加の機能を提供するためのコード生成の拡張。
  • --header string: コード生成ヘッダーファイルを上書きします。
  • --storage string: コード生成でサポートされるストレージドライバを指定し、デフォルトは "sql" です。
  • --target string: コード生成の対象ディレクトリを指定します。
  • --template strings: 追加のGoテンプレートを実行します。ファイル、ディレクトリ、ワイルドカードパスをサポートしており、例えば --template file="path/to/file.tmpl" です。

これらのオプションにより、開発者は異なるニーズや好みに応じて、自身のコード生成プロセスをカスタマイズすることができます。

5. ストレージオプションの構成

entは、SQLおよびGremlin方言の両方に対応したコードアセットの生成をサポートしており、デフォルトはSQL方言です。プロジェクトがGremlinデータベースに接続する必要がある場合、対応するデータベース方言を設定する必要があります。以下にストレージオプションの指定方法を示します:

ent generate --storage gremlin ./ent/schema

上記のコマンドは、生成されたアセットが特定のグラフデータベースとの互換性を確保するために、Gremlin方言を使用するようにentに指示します。

6. 上級者向けの使用法: entcパッケージ

6.1 プロジェクト内でのentcパッケージとしての使用

entcentフレームワークでコード生成に使用されるコアパッケージです。コマンドラインツールに加えて、entcはプロジェクト内でパッケージとして統合することもできます。これにより、コード自体でコード生成プロセスを制御およびカスタマイズすることができます。

プロジェクト内でentcをパッケージとして使用するには、entc.goという名前のファイルを作成し、次の内容をファイルに追加する必要があります:

// +build ignore

package main

import (
    "log"
    "entgo.io/ent/entc"
    "entgo.io/ent/entc/gen"
)

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

この方法を使用すると、必要に応じてmain関数内でgen.Config構造体を変更し、異なる構成オプションを適用することができます。entc.Generate関数を必要に応じて呼び出すことで、柔軟にコード生成プロセスを制御することができます。

6.2 entcの詳細な構成

entcは、生成されるコードをカスタマイズするための広範な構成オプションを提供しています。たとえば、カスタムフックを構成して生成されたコードを検査または変更したり、依存関係の注入により外部の依存関係を追加することができます。

以下の例は、entc.Generate関数にカスタムフックを提供する方法を示しています。

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            HookFunction,
        },
    })
    if err != nil {
        log.Fatalf("entコード生成の実行中にエラーが発生しました: %v", err)
    }
}

// HookFunctionはカスタムフック関数です
func HookFunction(next gen.Generator) gen.Generator {
    return gen.GenerateFunc(func(g *gen.Graph) error {
        // ここで g で表されるグラフモードを扱うことができます
        // 例えば、フィールドの存在を検証したり、構造を変更したりすることができます
        return next.Generate(g)
    })
}

さらに、entc.Dependencyを使用して外部依存関係を追加することもできます。

func main() {
    opts := []entc.Option{
        entc.Dependency(
            entc.DependencyType(&http.Client{}),
        ),
        entc.Dependency(
            entc.DependencyName("Writer"),
            entc.DependencyTypeInfo(&field.TypeInfo{
                Ident:   "io.Writer",
                PkgPath: "io",
            }),
        ),
    }
    if err := entc.Generate("./schema", &gen.Config{}, opts...); err != nil {
        log.Fatalf("entコード生成の実行中にエラーが発生しました: %v", err)
    }
}

この例では、生成されたクライアントオブジェクトにhttp.Clientio.Writerを依存関係として注入しています。

7. スキーマ記述の出力

entフレームワークでは、ent describeコマンドを使用してスキーマの説明をグラフィカルな形式で出力することができます。これにより、既存のエンティティや関係を素早く理解することができます。

以下のコマンドを実行して、スキーマの説明を取得します:

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

上記のコマンドは、各エンティティのフィールド、タイプ、関係などの情報を含む表形式の出力を行います。

8. コード生成フック

8.1 フックの概念

フックは、entコード生成プロセスに挿入されるミドルウェア関数であり、生成コードの前後にカスタムロジックを挿入することができます。フックは、生成されたコードの抽象構文木(AST)を操作したり、検証を行ったり、追加のコード片を追加するために使用することができます。

8.2 フックの使用例

以下は、すべてのフィールドに特定の構造タグ(例: json)が含まれていることを保証するためにフックを使用する例です。

func main() {
    err := entc.Generate("./schema", &gen.Config{
        Hooks: []gen.Hook{
            EnsureStructTag("json"),
        },
    })
    if err != nil {
        log.Fatalf("entコード生成の実行中にエラーが発生しました: %v", err)
    }
}

// EnsureStructTagは、グラフ内のすべてのフィールドに特定の構造タグが含まれていることを保証します
func EnsureStructTag(name string) gen.Hook {
    return func(next gen.Generator) gen.Generator {
        return gen.GenerateFunc(func(g *gen.Graph) error {
            for _, node := range g.Nodes {
                for _, field := range node.Fields {
                    tag := reflect.StructTag(field.StructTag)
                    if _, ok := tag.Lookup(name); !ok {
                        return fmt.Errorf("%s.%sのフィールドに構造タグ%sがありません", node.Name, field.Name, name)
                    }
                }
            }
            return next.Generate(g)
        })
    }
}

この例では、コードを生成する前に、EnsureStructTag関数が各フィールドにjsonタグがあるかどうかをチェックします。フィールドにこのタグがない場合、コード生成は中止されてエラーが返されます。これはコードの清潔さと一貫性を保つ効果的な方法です。