Techtouch Developers Blog

テックタッチ株式会社の開発チームによるテックブログです

ent を利用している project の migration に atlas を使ってみる

f:id:techtouch:20220317110806p:plain

このページについて

ent の migration に atlas を使ってみた所感や、そもそも atlas ってどんなものかということを紹介しています。

そもそも ent / atlas ってなに

ent

OSS のデータアクセスフレームワーク。Goコードで任意のデータモデルまたはグラフ構造を簡単に定義でき、データアクセス周りの CRUD 処理などが自動生成可能。
本記事では深く踏み込まないのでこのくらいの紹介にさせてください。

atlas

OSS のデータベーススキーマ管理ツール。CLI と GUI が提供されている。

特徴

  • AtlasDDL でデータベーススキーマを管理する
    • HCL 構文で記述できるので SQL よりかわかりやすく感じる
      • 将来的には TypeScript や JSON など対応構文を増やす予定とのこと
  • データベーススキーマを Go 構造体にマッピングできる。以下 blog より一部引用
&schema.Table{
    Name:    "users",
    Schema:  &schema.Schema{(CYCLIC REFERENCE)},
    Columns: {
        &schema.Column{
            Name: "id",
            Type: &schema.ColumnType{
                Type: &schema.IntegerType{
                    T:        "int",
                    Unsigned: false,
                },
                Null: false,
            },
        },
    },
    PrimaryKey: &schema.Index{
        Unique: false,
        Table:  &schema.Table{(CYCLIC REFERENCE)},
        Attrs:  nil,
        Parts:  {
            &schema.IndexPart{
                SeqNo: 0,
                Desc:  false,
                C:     &schema.Column{(CYCLIC REFERENCE)},
            },
        },
    },
}
  • 上記のようにスキーマを構造体へ変換できるので API として現在の状態を返却することができる。GUI で利用されている。
    • 以下は users table に test カラムを追加する例。ER図がないときにサクッと関連を見たいときや alter 文を生成するときにも便利。

f:id:techtouch:20220309093501g:plain

docsblog にも最新の情報があるのでご覧になってみてください。特に blog は docs よりも思想の部分に多く触れているので面白かったです。

実行例

前提

$ atlas version
atlas CLI version v0.3.5-f8eaeba27107-canary
https://github.com/ariga/atlas/releases/latest

$ cat go.mod | grep ent
module github.com/smith-30/goparco/ent_playground
        entgo.io/ent v0.10.1

初期 schema セットアップ

getting start より

schema 元のファイル生成

$ ent init User 

schema 定義追加(Field 定義)

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.Int("age").
            Positive(),
        field.String("name").
            Default("unknown"),
    }
}

// Edges of the User.
func (User) Edges() []ent.Edge {
    return nil
}

モデル操作など関連ファイルの生成を実行。

$ go generate ./ent

初回 migrate 実行。ここで初めて users テーブルが作られます。

package main

import (
    "context"
    "log"

    _ "github.com/mattn/go-sqlite3"
    "github.com/smith-30/goparco/ent_playground/ent"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent.db?_fk=1")
    if err != nil {
        log.Fatalf("failed opening connection to sqlite: %v", err)
    }
    defer client.Close()

    if err := client.Schema.Create(context.Background()); err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

atlas を利用して schema 変更管理

以降、atlas 使った migration お試し。getting start のレールからは外れます。

install@Mac

$ brew install ariga/tap/atlas

atlas で現在の schema を確認してみます
HCL で表示されるようになってます。(SQL を表示する機能は v0.3.5-f8eaeba27107-canary 時点ではありません)
SQL を見たい場合は dbmate あたり使うと簡単に出力できるのでおすすめです。

$ atlas schema inspect -d "sqlite://file:ent.db?_fk=1"
table "users" {
  schema = schema.main
  column "age" {
    null = false
    type = integer
  }
  column "name" {
    null    = false
    type    = varchar(255)
    default = "unknown"
  }
  column "id" {
    null           = false
    type           = integer
    auto_increment = true
  }
  primary_key {
    columns = [column.id]
  }
}
schema "main" {
}

atlas は GUI でも schema 確認可能なので見ておきましょう

$ atlas schema inspect -d "sqlite://file:ent.db?_fk=1" -w
Atlas UI available at: http://127.0.0.1:5800/projects/30064771073/schemas/1
Press Ctrl+C to stop

こんな画面が立ち上り、users テーブルの存在が確認できました。

f:id:techtouch:20220307211849p:plain

現状を GUI で簡単に確認できるのは便利ですよね
前述のとおり、schema を Go 構造体で読み込んで API として返しているので Relation があれば表示するようになってます。
今度は schema 変更をして、migration を実行してみたいと思います。
こちらの tutorial を参考にしています。

その前準備として、./ent/generate.go を下記のように変更します。 --feature sql/versioned-migration のフラグの追加です。

package ent

//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration ./schema

その後、$ go generate ./ent で追加したフラグ分の自動生成を反映させます。
実行後は、./ent/migrate/migrate.go にメソッドが追加されます

// Diff creates a migration file containing the statements to resolve the diff
// between the Ent schema and the connected database.
func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error {
    migrate, err := schema.NewMigrate(s.drv, opts...)
    if err != nil {
        return fmt.Errorf("ent/migrate: %w", err)
    }
    return migrate.Diff(ctx, Tables...)
}

このメソッドを使って、ent 配下の schema と現在の DB の状態を比較することができるようになります。 以下を実行すると、migration file が生成できます。

package main

import (
    "context"
    "log"

    "ariga.io/atlas/sql/migrate"
    "entgo.io/ent/dialect/sql/schema"
    _ "github.com/mattn/go-sqlite3"
    "github.com/smith-30/goparco/ent_playground/ent"
)

func main() {
    client, err := ent.Open("sqlite3", "file:ent.db?_fk=1")
    if err != nil {
        log.Fatalf("failed opening connection to sqlite: %v", err)
    }
    defer client.Close()

    //
    // migration
    //
    ctx := context.Background()
    // Create a local migration directory.
    dir, err := migrate.NewLocalDir("migrations")
    if err != nil {
        log.Fatalf("failed creating atlas migration directory: %v", err)
    }
    // Write migration diff.
    err = client.Schema.Diff(ctx, schema.WithDir(dir))
    if err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

差分を作りたいので、新しいテーブルを追加してみます。

$ go run entgo.io/ent/cmd/ent init Car

ent/schema/car.go にフィールド定義を追加します。

// Fields of the Car.
func (Car) Fields() []ent.Field {
    return []ent.Field{
        field.String("model"),
        field.Time("registered_at"),
    }
}

追加した定義を schema に反映させます。

$ go generate ./ent

実行後は、ent/migrate/schema.go が更新されます f:id:techtouch:20220308143705p:plain

先程の main 関数での migrate 処理はここを見に行くようです
main.go を実行すると migration file が生成されます

{timestamp}.up.sql

CREATE TABLE `cars` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `model` text NOT NULL, `registered_at` datetime NOT NULL);

{timestamp}.down.sql

DROP TABLE `cars`;

table 定義と対応した go file から sql 文が生成できるのは便利ですね
普段は追加・変更したい sql を書いて実行してコードを変更するというフローでしたが、その手間が自動化されてよりコードに集中できるようになったと思います

ただし、atlas 自体には migrate の実行管理する機能が現時点でないので実行管理まで行いたい場合は、別のツールを使う必要があります。対応予定はあるようです。
この生成方法だと特定機能毎に migration file はできるかと思うので、他のツール使わないで手動実行するときの運用方法はうまく考えないといけないかなと思います。
リリース頻度が少なく、実行する migration file が多い場合はおとなしくツールに sql 実行を任せるのがよさそうです。

まとめ

ent を運用していく際の schema 変更を atlas で運用するとどんな感じなのか実際に動かして試してみました。
既存の project で利用している migration tool から乗り換えるにはまだ検討の余地があるかと思います。
しかし、新規 project で ent を使ってみるとなった場合は素直に使ってみてもいいのかなと思いました(ent の自動化のフローに乗っかれるため)。

ent に atlas が組み込まれるようになったのは 最近 なので、ent の機能拡張とともにさらなる進化が見込めそうです。version 管理は早く提供してほしいですよね。(GUI 操作で見る限りオンメモリでは持てるようになっているのでもうすぐかも)今後も watch してまたブログでコメントしていきます。