miyazi888の覚え書き日記

学習したことを書き留めてます。

entを試してみた

仕事で使いそうだったのでORマッパーのentを試してみた。 公式はこちら。

https://entgo.io/ja

検証用のディレクトリ作成

mkdir test
cd test
go mod init test

検証用のDB(postgres)を作成

検証用のDBを作成する

touch docker-compose.yml

以下のようにDBを起動するだけの定義を作成する。

docker-compose.yml

version: "3.8"

services:
  db:
    image: postgres:15.1-alpine
    ports:
      - 5432:5432
    volumes:
      - ./.data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: testdb

DBを起動する。

docker-compose up -d

ライブラリのインストール

entのインストール

go get -d [entgo.io/ent/cmd/ent](http://entgo.io/ent/cmd/ent)

postgresのドライバのインストール

go get github.com/lib/pq

スキーマを追加

以下のコマンドでUserスキーマの元になるファイルが生成することができる。

今回はuserテーブルを生成するので、Userを指定。

スキーマファイルはent配下に作成される。

ent new User

ent/schema/user.goなどが作成されている。

生成されたファイルを修正してスキーマを定義

ent/schema.user.goを以下のように修正し、userテーブルにnameとageの項目を追加する。

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
      field.String("name").Default("unknown"),
        field.Int("age").Positive(),
    }
}

定義したスキーマから各テーブル毎の実装を生成する

go generate ./ent

上記のコマンドでgenerate.goやuser_create.goなどの実装ファイルが生成される。

マイグレーション用のプログラムを実装

entはスキーママイグレーションする機能があるので、マイグレーション用の処理を記述

DB接続を取得する実装

db/db.go

package db

import (
    "fmt"
    "test/ent"
)

func NewDBClient() *ent.Client {
    user := "user"
    password := "pass"
    port := "5432"
    host := "localhost"
    dbName := "testdb"
    url := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", user, password, host, port, dbName)
    client, err := ent.Open("postgres", url)
    if err != nil {
        fmt.Printf("failed connecting to postgres: %v", err)
    }
    return client
}

func CloseDB(client *ent.Client) {
    err := client.Close()
    if err != nil {
        fmt.Printf("failed close to db: %v", err)
    }
}

マイグレーション処理の実装

go runコマンドで実行する予定なので、packageをmainにしています。

migrate/migrate.go

package main

import (
    "context"
    "fmt"
    "test/db"
    "test/ent/migrate"

    _ "github.com/lib/pq"
)

func main() {
    client := db.NewDBClient()
    ctx := context.Background()

    err := client.Schema.Create(
        ctx,
        migrate.WithDropIndex(true),
        migrate.WithDropColumn(true),
    )
    if err != nil {
        fmt.Printf("failed creating schema resources: %v", err)
    }

    db.CloseDB(client)
}

スキーママイグレーション実行

以下のようにしてマイグレーションプログラムを実行

go run migrate/migrate.go

実際にマイグレーションされたか確認する

マイグレーションを下のように確認

docker-compose exec db /bin/sh
# psql -h localhost -U user -d testdb
testdb=# \d users;

Table "public.users"
 Column |       Type        | Collation | Nullable |             Default              
--------+-------------------+-----------+----------+----------------------------------
 id     | bigint            |           | not null | generated by default as identity
 name   | character varying |           | not null | 'unknown'::character varying
 age    | bigint            |           | not null | 
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

CRUD操作

下記のようなプログラムで基本的なCRUDを確認。

main.go

package main

import (
    "context"
    "fmt"
    "log"
    "test/db"
    "test/ent/user"

    _ "github.com/lib/pq"
)

func main() {
    client := db.NewDBClient()
    ctx := context.Background()

    // 1件追加
    usr, err := client.Debug().User.
        Create().
        SetName("user1").
        SetAge(33).
        Save(ctx)
    if err != nil {
        fmt.Printf("failed creating user: %v", err)
        return
    }

    // 1件更新
    updatedUser, err := client.Debug().User.Update().Where(user.ID(usr.ID)).SetAge(29).Save(ctx)
    if err != nil {
        fmt.Printf("failed updating user: %v", err)
        return
    }
    log.Printf("user: %+v", updatedUser)

    // 名前がuser1のユーザーを取得
    users, err := client.Debug().User.Query().Where(user.Name("user1")).All(ctx)
    if err != nil {
        fmt.Printf("failed getting users: %v", err)
        return
    }

    for _, usr := range users {
        fmt.Printf("user: %+v", usr)
    }

    // 1件削除
    _, err = client.Debug().User.Delete().Where(user.Name("user1")).Exec(ctx)
    if err != nil {
        fmt.Printf("failed deleting user: %v", err)
        return
    }

    // DB接続を閉じる
    db.CloseDB(client)
}

上記を実行するとログとして各処理で発行されるクエリを確認して意図したクエリが発行されたことが確認できる。 最後にデータを削除しているのでDB上にはデータは残らないことに注意。

>> go run main.go
2023/04/28 22:32:37 driver.Query: query=INSERT INTO "users" ("name", "age") VALUES ($1, $2) RETURNING "id" args=[user1 33]
2023/04/28 22:32:37 driver.Exec: query=UPDATE "users" SET "age" = $1 WHERE "users"."id" = $2 args=[29 1]
2023/04/28 22:32:37 user: 1
2023/04/28 22:32:37 driver.Query: query=SELECT "users"."id", "users"."name", "users"."age" FROM "users" WHERE "users"."name" = $1 args=[user1]
user: User(id=1, name=user1, age=29)2023/04/28 22:32:37 driver.Exec: query=DELETE FROM "users" WHERE "users"."name" = $1 args=[user1]

次に

次の記事でテーブル同士を関連させて動作確認してみた。 https://miyazi888.hatenablog.com/entry/2023/05/07/221705

参考

GolangにORM導入(ent migration編) - Qiita

100%型安全なgolangORM「ent」を使ってみた | フューチャー技術ブログ