この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
GoでGremlin
用のORMがなくてつらかったのですが、ent
というオープンソースプロジェクトをみつけたので試してみました。
FacebookでGo用のグラフベースのORMとして利用されていたフレームワークEnt
が元になっています。
グラフ構造のデータをスキーマとして定義することで、スキーマからのGoのコードを自動生成してくれます。
MySQL/MariaDB/PostgreSQL/SQLite/Gremlim(considered experimental) などのデータベースのデータベースに対応しており、GraphQLのバックエンドとしてしても利用できます。
執筆時点で、最新はv0.8.0
ながらGitHubの更新も頻繁で期待できそうです。
v1までのロードマップも公開されています。
今回はこちらのent
がGremlin
のORMとして利用できそうか調査してみました。
はじめに結論
本番利用は少し待ったほうが良さそうという印象です。
Gremlin
の対応が実験的と書いているとおり、不具合っぽいところが見つかりました。
ただ、問い合わせると即日でPR出して修正してくれました。( ゚Д゚)
コミュニティが活発なので、今後GremlinないしAmazon NeptuneのORMとしての利用が期待できそうです。
せっかちな人へ
GitHubを参照してください。
やってみる
公式のQuick Introductionを参考にして、Gremlinように少し修正を加えています。
セットアップ
- 初期化
go mod init gremlin-orm-sample
- モジュールの追加
# ※masterの最新が必要なため
go get entgo.io/ent/cmd/ent/@master
go get github.com/google/uuid
スキーマを作成してみる
- スキーマの追加
go run entgo.io/ent/cmd/ent init User
- スキーマの修正
schema/user.go
// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("id").
NotEmpty().
Unique().
Immutable(),
field.Int("age").
Positive(),
field.String("name").
Default("unknown"),
}
}
ent/generate.go
の末尾に下記を追加
--storage gremlin --idtype string
- コード自動生成
go generate ./ent
エンティティを作成してみる
main.go
を作成
package main
import (
"context"
"fmt"
"gremlin-orm-sample/ent"
"gremlin-orm-sample/ent/user"
"log"
"entgo.io/ent/dialect"
"github.com/google/uuid"
)
func main() {
var err error
client, err := ent.Open(dialect.Gremlin, "http://localhost:8182")
if err != nil {
log.Fatalf("creating client: %v", err)
}
defer client.Close()
ctx := context.Background()
user, err := CreateUser(ctx, client, uuid.New().String(), 32, "seiichi")
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("created user: ", user)
}
users, err := SearchUsersById(ctx, client, user.ID)
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("searched users by id: ", users)
}
}
func CreateUser(ctx context.Context, client *ent.Client, id string, age int, name string) (*ent.User, error) {
user, err := client.User.
Create().
SetID(id).
SetAge(age).
SetName(name).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed creating user: %w", err)
}
return user, nil
}
func SearchUsersById(ctx context.Context, client *ent.Client, id string) ([]*ent.User, error) {
users, err := client.User.
Query().
Where(user.IDEQ(id)).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed querying user: %w", err)
}
return users, nil
}
- コード実行
# For Go 1.16
$ go get -t .
$ go run main.go
created user: User(id=e6b49139-9067-4b90-aaed-aedae723d73a, age=32, name=seiichi)
searched users by id: [User(id=e6b49139-9067-4b90-aaed-aedae723d73a, age=32, name=seiichi)]
※事前にGremlin Serverを立ち上げておく必要があります。詳細はGitHubを参照してください。
エッジを追加してみる
- スキーマの追加
go run entgo.io/ent/cmd/ent init Car
- スキーマの修正
schema/user.go
edgeを追加しています。
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("cars", Car.Type),
}
}
- スキーマの修正
schema/car.go
// Fields of the Car.
func (Car) Fields() []ent.Field {
return []ent.Field{
field.String("id").
NotEmpty().
Unique().
Immutable(),
field.String("model"),
field.Time("registered_at"),
}
}
- コード再生成
go generate ./ent
main.go
の修正
ユーザーにエッジを追加しています。
package main
import (
"context"
"fmt"
"gremlin-orm-sample/ent"
"gremlin-orm-sample/ent/car"
"gremlin-orm-sample/ent/user"
"log"
"time"
"entgo.io/ent/dialect"
"github.com/google/uuid"
)
func main() {
var err error
client, err := ent.Open(dialect.Gremlin, "http://localhost:8182")
if err != nil {
log.Fatalf("creating client: %v", err)
}
defer client.Close()
ctx := context.Background()
user, err := CreateUser(ctx, client, uuid.New().String(), 32, "seiichi")
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("created user: ", user)
}
users, err := SearchUsersById(ctx, client, user.ID)
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("searched users by id: ", users)
}
car, err := CreateCar(ctx, client, uuid.New().String(), "Tesla")
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("created car: ", car)
}
newUser, err := AddCarToUser(ctx, client, user, car)
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("add car to user: ", newUser)
}
newUsers, err := SearchUsersByCarModel(ctx, client, "Tesla")
if err != nil {
log.Fatalf(err.Error())
} else {
log.Println("searched user by car model: ", newUsers)
}
}
func CreateUser(ctx context.Context, client *ent.Client, id string, age int, name string) (*ent.User, error) {
user, err := client.User.
Create().
SetID(id).
SetAge(age).
SetName(name).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed creating user: %w", err)
}
return user, nil
}
func SearchUsersById(ctx context.Context, client *ent.Client, id string) ([]*ent.User, error) {
users, err := client.User.
Query().
Where(user.IDEQ(id)).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed querying user: %w", err)
}
return users, nil
}
func CreateCar(ctx context.Context, client *ent.Client, id string, name string) (*ent.Car, error) {
car, err := client.Car.
Create().
SetID(id).
SetModel(name).
SetRegisteredAt(time.Now()).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed creating car: %w", err)
}
return car, nil
}
func AddCarToUser(ctx context.Context, client *ent.Client, user*ent.User, car *ent.Car) (*ent.User, error) {
user, err := user.Update().
AddCars(car).
Save(ctx)
if err != nil {
return nil, fmt.Errorf("failed add car to user: %w", err)
}
return user, nil
}
func SearchUsersByCarModel(ctx context.Context, client *ent.Client, carModel string) ([]*ent.User, error) {
users, err := client.User.
Query().
Where(user.HasCarsWith(car.Model(carModel))).
All(ctx)
if err != nil {
return nil, fmt.Errorf("failed querying user cars: %w", err)
}
return users, nil
}
- コード実行
$ go run main.go
created user: User(id=7ba08139-edb6-4202-8d3f-dacecf1678b0, age=32, name=seiichi)
searched users by id: [User(id=7ba08139-edb6-4202-8d3f-dacecf1678b0, age=32, name=seiichi)]
created car: Car(id=39aff5fe-e9a2-4c0c-98e5-883b202818a7, model=Tesla, registered_at=Wed Jun 23 11:55:58 2021)
add car to user: User(id=7ba08139-edb6-4202-8d3f-dacecf1678b0, age=32, name=seiichi)
searched user by car model: [User(id=7ba08139-edb6-4202-8d3f-dacecf1678b0, age=32, name=seiichi)]
まとめ
いかがでしょうか。 まだ実験段階のため本番利用は早い気がしますが、今後の選択肢としては十分ありかと思います。