🚀

Go ORM "Ent"を活用したAPI構築~ToDoアプリでの実践~

に公開

📋 この記事について

対象読者: Entに興味のあるGoの初心者から中級者開発者

記事の概要:

  • GoのORMである「Ent」のコアコンセプトとスキーマ定義の理解
  • デモ用のToDoアプリケーションのためのシンプルなEntスキーマを定義
  • Entの生成されたコードを使用して、基本的なCRUD(作成、読み取り、更新、削除)操作のAPI実装
  • 各操作でEntによって生成される基盤となるSQLクエリの観察

検証環境:

  • OS: Linux/macOS
  • 言語/フレームワーク: Go 1.22+, Ent
  • その他: SQLite (インメモリ), シンプルなフロントエンドのためのWebブラウザ

🎯 背景・課題

データ駆動型アプリケーションを構築する際には、データベースとの相互作用に関連する反復的でエラーが発生しやすいタスクがしばしば伴います。オブジェクトリレーショナルマッパー(ORM)は、プログラミング言語のオブジェクトを使用して開発者がデータベースと対話できるようにすることで、これを簡素化することを目指しています [1]。Pythonで考えれば、SQLAlchemy、JavascriptではPrismaがORMとしてよく認知されていると思います。Entは言わば、GoのORMです。その他のGo ORMとの比較検証は、「Go ORM徹底比較:GORM vs Ent vs SQLC vs SQLBoiler」の記事にて紹介しています。

しかし、多くのORMは基盤となるSQLを隠蔽するため、パフォーマンスを理解したり問題をデバッグしたりすることが困難となる可能性があります。GoのORMであるEnt [2] は、スキーマから型安全なAPIを生成しつつ、データベース操作への透明性も提供します [3]。

この記事では、シンプルなToDoアプリケーション上で、実際のEntの動作や構造を理解することを目的とします。Entを基本的なCRUDに「どのように」使用するかを示すだけでなく、Entが「どのような」SQLを生成するかを明示的に視覚化していきます。

🔍 調査・検証内容

I. Entフレームワークの紹介

Ent [2] は、Meta(旧Facebook)によって開発された、Goのためのエンティティフレームワークで、データのモデリングとクエリを容易に生成してくれます [4]。Goでは、gRPCで使用される.protoなど、コードの自動生成をサポートする機能が多い印象ですね。リフレクションやランタイムマジックに依存する多くのORMとは異なり、Entはコンパイル時に必要なすべてのコードを生成するため、パフォーマンスが向上し、ランタイムエラーの減少が期待されます [5]。

Entの主な機能:

  • 型安全なAPI: すべてのデータベース操作はコンパイル時に型チェック [3]
  • スキーマ・アズ・コード: データベーススキーマをGoコードで直接定義 [4]
  • コード生成: Entは、CRUD操作、クエリ、マイグレーションを含む、スキーマ用のクライアントを生成 [6]
  • グラフ指向: データを相互接続されたエンティティのグラフとしてモデル化することを推奨 [3]
  • 拡張可能: カスタムロジックと統合のためのフックとアノテーションを提供 [7]

II. Entを用いたToDoアプリケーションスキーマの定義

Ent開発の3ステップ・ワークフロー; スキーマ定義、コード生成、実装・実行

今回のToDoアプリケーションは、UserTodoという2つのシンプルなエンティティを前提とします。各コードのコミット履歴へのリンクを添付してあるので、Github上で確認したい方は参照ください。

  • Usernameemailを持つ
  • Todotext(説明)、status(例:PENDINGIN_PROGRESSCOMPLETED)、そしてUserに関連付けられる。

A. Userスキーマ (ent/schema/user.go)

package schema

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

// 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.String("name").Unique(),
		field.String("email").Unique(),
	}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("todos", Todo.Type),
	}
}

Githubコミット履歴

B. Todoスキーマ (ent/schema/todo.go)

package schema

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

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

// Fields of the Todo.
func (Todo) Fields() []ent.Field {
	return []ent.Field{
		field.Text("text").NotEmpty(),
		field.Enum("status").Values("PENDING", "IN_PROGRESS", "COMPLETED").Default("PENDING"),
		field.Int("owner_id").Optional(), // Mark owner_id as optional
	}
}

// Edges of the Todo.
func (Todo) Edges() []ent.Edge {
	return []ent.Edge{
		edge.From("owner", User.Type).
			Ref("todos").
			Field("owner_id").
			Unique(),
	}
}

Githubコミット履歴

C. Entクライアントコードの生成

スキーマを定義したら、Entクライアントコードを生成する必要があります。これは、ent/generate.goファイルがあるディレクトリでgo generateを実行することで行われます。

// ent/generate.go
package ent

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

GitHubコミット履歴

coding-test/ent-todo-app/backendディレクトリからコマンドを実行します。

go generate ./ent

このコマンドは、UserTodoのモデル、クエリ、ミューテーションを含む、生成されたすべてのクライアントコードを含むentディレクトリを作成します [6]。

D. Entの自動生成ファイルについて理解する

Entによって自動生成されるファイル構造

重要: Entでは、どのファイルが自動生成されるか、どのファイルを手動で編集するかを理解することが重要です。

私たちが編集するファイル:

  • ent/schema/フォルダ内のファイルのみ(例:user.gotodo.go
  • これらがエンティティの定義(フィールド、エッジ、バリデーション等)を含むファイル

Entによって自動生成されるファイル:

  • ent/ディレクトリ内のschema/フォルダ以外のすべてのファイルとフォルダ
  • 例:client.goent.gomigrate/user/todo/、その他すべて
ent/
├── schema/         # ← 編集するファイル(エンティティ定義)
│   ├── user.go
│   └── todo.go
├── client.go       # ← 自動生成(編集禁止)
├── ent.go          # ← 自動生成(編集禁止)
├── migrate/        # ← 自動生成(編集禁止)
├── user/           # ← 自動生成(編集禁止)
├── todo/           # ← 自動生成(編集禁止)
└── ...             # ← その他すべて自動生成(編集禁止)

Githubコミット履歴

注意点:

  • 自動生成されたファイルは手動で編集しないこと
  • go generate ./entを実行するたびに、これらのファイルは上書きされます
  • スキーマを変更した後は、必ずgo generate ./entを実行してコードを再生成してください

III. 基本的なCRUD操作の実装

次に、ToDoアプリケーションのコアCRUD操作を実装します。これらの操作をデモンストレーションし、SQLクエリをログに記録するために、シンプルなmain.goファイルを使用します。

A. Entクライアントとデータベースのセットアップ

シンプルにするために、インメモリSQLiteデータベースを使用します。_fk_foreign_keys=onは、外部キー制約が強制されることを保証します。
main.goファイル詳細は、Githubのコミット履歴を参照ください。

B. アプリケーションの実行とSQLの観察

アプリケーションを実行するには、coding-test/ent-todo-app/backendディレクトリに移動し、以下を実行します。

go run main.go

Webブラウザでhttp://localhost:8080を開くと、ToDoアプリケーションと対話するためのシンプルなインターフェースが表示されます。操作(例:ユーザーの作成、ToDoの追加)を実行すると、go runコマンドが実行されているターミナルを観察してみましょう。EntのDebug()モードは、SQLiteデータベースに送信される正確なSQLクエリをログに記録します[8]。

SQL出力例(ターミナルから):

-- ユーザーの作成
INSERT INTO `users` (`name`, `email`) VALUES (?, ?)

-- ToDoの作成
INSERT INTO `todos` (`text`, `status`, `owner_id`) VALUES (?, ?, ?)

-- ユーザーのリスト表示
SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users`

-- ToDoのリスト表示(オーナーのEager Loading付き)
SELECT `todos`.`id`, `todos`.`text`, `todos`.`status`, `todos`.`owner_id` FROM `todos`
SELECT `t2`.`id`, `t2`.`name`, `t2`.`email` FROM `users` AS `t2` WHERE `t2`.`id` IN (?, ?)

📊 主要な学習と洞察

  • スキーマ・アズ・コードのシンプルさ: Goコードでスキーマを定義することは直感的であり、Goの型システムを活用できます。
  • 型安全性: Entの生成されたクライアントは、すべてのデータベース操作が型安全であることを保証し、ランタイムではなくコンパイル時にエラーを捕捉します。
  • SQLの透明性: Debug()モードは、Entが生成する正確なSQLクエリを理解する上で非常に貴重であり、パフォーマンスチューニングとデバッグに不可欠です。

🎉 まとめ

この記事では、簡単なToDoアプリケーションを構築し、実践的に近い環境でEntを使用してAPIの実装を行いました。スキーマの定義方法、型安全なクライアントの生成方法、および基盤となるSQLを観察しながら基本的なCRUD操作を実際に体験しました。この透明性はEntの主要な強みであり、開発者がGoで堅牢で高性能なデータ層を構築できるようにします。

主要なポイント:

  • Entはスキーマから型安全なGoコードを生成し、データベース操作を簡素化
  • Entの流暢なAPIにより、基本的なCRUD操作は簡単
  • Entは生成されたSQLへの透明性を提供し、理解とデバッグに便

📚 参考文献

[1] Overview of Entity Framework Core - EF Core. Microsoft Learn. Retrieved from https://learn.microsoft.com/en-us/ef/core/
[2] Ent. (n.d.). Entgo.io. Retrieved from https://entgo.io
[3] ent. Quick Introduction. Retrieved from https://entgo.io/docs/getting-started
[4] ELI5: Ent - Schema as Code in Go. Meta for Developers. Retrieved from https://developers.facebook.com/blog/post/2021/04/26/eli5-ent-schema-as-code-go/
[5] An introduction to ent. LogRocket Blog. Retrieved from https://blog.logrocket.com/an-introduction-to-ent/
[6] Introduction - ent. Code Generation. Retrieved from https://entgo.io/docs/code-gen/
[7] A beginner's guide to creating a web-app in Go using Ent. ent Blog. Retrieved from https://entgo.io/blog/2023/02/23/simple-cms-with-ent/
[8] Debug - ent. Retrieved from https://pkg.go.dev/entgo.io/ent/examples/o2mrecur/ent#Client.Debug

Discussion