Ectoは、データベースを扱うとても便利なライブラリです。その使い勝手をさらによくするライブラリとしてYactoはつくられました。本稿は、Qiitaに公開されたドキュメント「[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った」にもとづいてご紹介します。
なお、ドキュメントは適宜更新されますので、本稿執筆時の情報であることを申し添えておきます。詳しくは、ドキュメントをご参照ください。
Yacto とは
Yactoは、Ectoに足りなかった部分をサポートするライブラリです。おもに、つぎの4つの機能があります。
- マイグレーションファイルの自動生成
- 別アプリケーションからのマイグレーションの利用
- 水平分割したデータベースへのマイグレーション
- 複数データベースにまたがるトランザクション(XAトランザクション)
マイグレーションファイルの自動生成
Yactoは、とくにマイグレーション周りがEctoと異なります。Yactoを使えば、Ectoのようにマイグレーションをわざわざ定義しなくても、スキーマからマイグレーションファイルが自動的に出力されるのです。
EctoとYactoのマイグレーションの違い
| ライブラリ | マイグレーション |
|---|---|
| Ecto | スキーマとマイグレーションを別に定義 |
| Yacto | スキーマからマイグレーションファイルを自動的に出力 |
たとえば、lib/my_app/player.exにつぎのようなスキーマを定義したとしましょう。
defmodule MyApp.Player do
use Yacto.Schema, dbname: :player
schema @auto_source do
# sharding key
field :player_id, :string, meta: [null: false, size: 64]
field :hp, :integer, default: 0, meta: [null: false]
index :player_id, unique: true
end
end
ここでYactoにより、mix yacto.gen.migrationを実行します。すると、以下のようなマイグレーションファイルpriv/migrations/2017-11-22T045225_my_app.exsが出力されるのです。
defmodule MyApp.Migration20171122045225 do
use Ecto.Migration
def change(MyApp.Player) do
create table("my_app_player")
alter table("my_app_player") do
add(:hp, :integer, [null: false, size: 64])
add(:player_id, :string, [null: false])
end
create index("my_app_player", [:player_id], [name: "player_id_index", unique: true])
end
def change(_other) do
:ok
end
def __migration_structures__() do
[
{MyApp.Player, %Yacto.Migration.Structure{fields: [:id, :player_id, :hp], meta: %{attrs: %{hp: %{null: false}, player_id: %{null: false, size: 64}}, indices: %{{[:player_id], [unique: true]} => true}}, source: "my_app_player", types: %{hp: :integer, id: :id, player_id: :string}}},
]
end
def __migration_version__() do
20171122045225
end
end
あとはmix yacto.migrateを実行すれば、このマイグレーションファイルがデータベースに反映されます。もうマイグレーションファイルをいちいち書く必要はありません。
さらに、スキーマにフィールドが追加された場合は、差分だけをマイグレーションファイルに出力して、データベースに反映する機能も備わっています。
別アプリケーションからのマイグレーションの利用
先ほどの例のアプリケーションmy_appを、別のアプリケーションが利用することになったとします。すると、my_appはデータベースを使っているので、そのアプリケーション上でmy_appのためのマイグレーションが必要です。
Ectoでは、他のアプリケーションが必要としているマイグレーションを自分で書くか、それぞれのアプリケーションが指定するばらばらな方法でマイグレーションしなければなりません。
けれど、Yactoを使えば、config/config.exsを適切に書いて、my_appを利用するアプリケーションでつぎのコマンドさえ実行すればよいのです。これで、my_appのマイグレーションができます。つまり、Yactoを使っているアプリケーションでは、すべて同じ方法でマイグレーションができるということです。
mix yacto.migrate --app my_app
水平分割したデータベースへのマイグレーション
たとえば、MyApp.Playerスキーマを水平分割した場合、このスキーマのマイグレーションファイルを複数のRepoに適用しなければなりません。これは、設定ファイルconfig/config.exsにつぎのように書くだけで済みます。
config :yacto, :databases,
%{
default: %{
module: Yacto.DB.Single,
repo: MyApp.Repo.Default,
},
player: %{
module: Yacto.DB.Shard,
repos: [MyApp.Repo.Player0, MyApp.Repo.Player1],
},
}
MyApp.PlayerのYacto.Schemaを使うuse/2には、つぎのようにdbname: :playerのオプションが定めてありました。この:playerが、MyApp.Playerの所属するRepoのグループ名です。
defmodule MyApp.Player do
use Yacto.Schema, dbname: :player
...
:playerRepoグループは、設定ファイルでMyApp.Repo.Player0とMyApp.Repo.Player1のRepoを紐づけています。あとはmix yacto.migrateを実行すれば、MyApp.PlayerのマイグレーションファイルがMyApp.Repo.Player0とMyApp.Repo.Player1に適用されるのです。
水平分割したデータベースを利用するときは、Yacto.DB.repo/2(あるいはschema.repo/1)を使ってRepoが取得できます。
repo = Yacto.DB.repo(:player, player_id)
MyApp.Player |> repo.all()
複数データベースにまたがるトランザクション(XAトランザクション)
Yacto.transaction/2を使うと、複数のデータベースを指定してトランザクションが発行できます。
# 2つ以上のRepoが指定されているので XA トランザクションを発行する
Yacto.transaction([:default,
{:player, player_id1},
{:player, player_id2}], fn ->
default_repo = Yacto.DB.repo(:default)
player1_repo = Yacto.DB.repo(:player, player_id1)
player2_repo = Yacto.DB.repo(:player, player_id2)
# このあたりでデータベースを操作する
...
# ここですべてのXAトランザクションがコミットされる
end)
つぎの3つのRepoに対してトランザクションが行われます。
-
:defaultのRepoMyApp.Repo.Default -
player_id1でシャーディングされたRepo -
player_id2でシャーディングされたRepo
あとのふたつは、シャードキーによっては同じRepoになる可能性があるので、利用するRepoはふたつか3つのどちらかです。ふたつ以上のRepoを利用してトランザクションを開始する場合は、自動的にXAトランザクションになります。
その他
Yactoのおもな機能について、かいつまんでにご説明しました。さらに詳しくは、前出「[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った」をお読みください。Yactoのスキーマの定め方や使い方、および便利関数についても解説されています。
Top comments (0)