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
...
:player
Repoグループは、設定ファイルで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)