Go製のORM である Facebook / ent を使ってみたらなかなかイケてた話
はじめに
Facebookが開発しているGo製のORMであるentを業務で使い始めたので、基本的な構文などをまとめます。
導入
-
CRUD操作についての確認
https://entgo.io/docs/crud -
validatorの定義方法の確認
https://entgo.io/docs/schema-fields/#validators -
migrationの実行方法及びversioned migrationの調査
https://entgo.io/docs/migrate#auto-migration
https://entgo.io/docs/versioned-migrations#generating-versioned-migration-files
https://entgo.io/docs/versioned-migrations#apply-migrations -
association(entだとedge)の定義方法の確認
https://entgo.io/docs/schema-edges/
datetimeを更新しようとした際に発生したエラー
failed scanning rows: sql: Scan error on column index 8, name "updated_on": unsupported Scan, storing driver.Value type []uint8 into type *time.Time
client, err := ent.Open("mysql", "root:pass@tcp(localhost:3307)/test?parseTime=true")
リレーションを定義した別テーブルのカラムを取得する方法
まず、 Edgesの部分を以下の様に定義して、
func (ArchiveFile) Edges() []ent.Edge {
return []ent.Edge{
edge.From("organization", OperatorCoMaster.Type).
Ref("archiveFiles").
Unique().
Required().
Field("operator_co_id"),
}
}
func (OperatorCoMaster) Edges() []ent.Edge {
return []ent.Edge{
edge.To("archiveFiles", ArchiveFile.Type),
}
}
archivefileの全レコードを取得した後に、for文でoperatorCoMasterを一つずつ取り出せばいけた。
func toProtoArchive(af *ent.ArchiveFile, ocm *ent.OperatorCoMaster) *genproto.Archive {
v := &genproto.Archive{
Id: int32(af.ID),
FileName: af.ArchiveFileName,
FileTitle: af.ArchiveFileTitle,
OperatorCoName: ocm.OperatorCoName,
}
return v
}
func (svc *ArchiveService) List(ctx context.Context, req *genproto.ListArchiveRequest) (*genproto.ListArchiveResponse, error) {
archives, err := svc.client.ArchiveFile.Query().All(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "internal: %s", err)
}
var keys []*genproto.Archive
for _, v := range archives {
operatorcomaster, _ := v.QueryOrganization().Only(ctx)
po := toProtoArchive(v, operatorcomaster)
keys = append(keys, po)
}
return &genproto.ListArchiveResponse{ArchiveList: keys}, nil
}
feature flug
go run entgo.io/ent/cmd/ent generate --feature sql/upsert ./ent/schema
migration
entのschemaファイルから生成されたマイグレーションファイルをDBに適用するためには golang-migrate
を使用する必要があります。
apply migration
./migrations
以下に用意されたファイル内のクエリが実行されます。
- upファイルのSQLがDBに実行される。
migrate -source file://migrations -database 'mysql://root:pass@tcp(localhost:3307)/test' up
- downファイルのSQLがDBに実行される。
migrate -source file://migrations -database 'mysql://root:pass@tcp(localhost:3307)/test' down
force
特定のバージョンにまで戻す場合
migrate -source file://migrations -database 'mysql://root:pass@tcp(localhost:3307)/test' force {$VERSION}
db pull
既存DBにて既に定義ずみのテーブルを引っ張ってきたい場合にはentimport
というパッケージを使用する必要があります。
パッケージのインストール
go get ariga.io/entimport/cmd/entimport
既存テーブルを持ってくる
go run ariga.io/entimport/cmd/entimport -dsn "mysql://root:pass@tcp(localhost:3307)/test" -tables client_account_master
fields
MySQLのカラムにマッピングする
ent側で用意されている型定義とMySQL側の型定義が若干違っている場合、以下の様にMySQL側の型定義に対してマッピングしてあげることができます。
field.String("condition1").Optional().SchemaType(map[string]string{
dialect.MySQL: "varchar(2)",
}),
index
uniqeuキー制約をindex名称を明示した上で定義する
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("asp_id", "relation_unique_id", "client_site_id", "start_datetime").
StorageKey("UNIQUE").
Unique(),
}
}
annotation
文字コードを明示する
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{
Collation: "utf8_general_ci",
Charset: "utf8",
},
}
}
テーブル名の指定
デフォルトではテーブル名が複数形になってしまうので、既にテーブルが作成済みでテーブル名を既存のもので指定したい場合には、以下の様にしてテーブル名を明示する必要があります。
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{
Table: "user",
},
}
}
残念なところ
entでは複合primary keyを定義することができません。
entのschemaからER図を作成する
entのschemaファイルからER図を生成するためのツールが公開されています。
パッケージのインストール
go get github.com/hedwigz/entviz/cmd/entviz
ER図を生成する
go run github.com/hedwigz/entviz/cmd/entviz ./ent/schema
上記コマンドを実行すると以下のようなER図が自動生成されます。
シンプルなER図になっていてエンティティ間のリレーションが直感的にわかりやすいですね。
https://entgo.io/ja/blog/2021/08/26/visualizing-your-data-graph-using-entviz/
終わりに
公式ドキュメントに最低限の使用方法しか書かれておらず、開発を始めて間もない頃には、「なかなか開発速度が出ないなぁ」、という印象を持っていましたが、慣れてくるとサクサクと開発が進み、非常に良くできたフレームワークであることを実感しました。
今後開発を進めていく中で、他のユースケースも追記していきたいと思います。
Discussion