Elixir: Ectoのマイグレーションを自動化したり水平分割するためのライブラリYacto
Tags: elixir, webdev, tutorial, programming
Published: 2018-11-27 01:35:27

---
title: Elixir: Ectoのマイグレーションを自動化したり水平分割するためのライブラリYacto
published: true
description: Ectoは、データベースを扱うとても便利なライブラリです。その使い勝手をさらによくするライブラリとしてYactoはつくられました。本稿は、Qiitaに公開されたドキュメント「[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った」にもとづいてご紹介します。
tags: #elixir, #webdev, #tutorial, #programming
---

[Ecto](https://hexdocs.pm/ecto/Ecto.html)は、データベースを扱うとても便利なライブラリです。その使い勝手をさらによくするライブラリとして[Yacto](https://hex.pm/packages/yacto)はつくられました。本稿は、Qiitaに公開されたドキュメント「[[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った](https://qiita.com/melpon/items/5c9b0645d5240cd22d0f)」にもとづいてご紹介します。

なお、ドキュメントは適宜更新されますので、本稿執筆時の情報であることを申し添えておきます。詳しくは、ドキュメントをご参照ください。


# Yacto とは
Yactoは、Ectoに足りなかった部分をサポートするライブラリです。おもに、つぎの4つの機能があります。

- マイグレーションファイルの自動生成
- 別アプリケーションからのマイグレーションの利用
- 水平分割したデータベースへのマイグレーション
- 複数データベースにまたがるトランザクション（XAトランザクション）


# マイグレーションファイルの自動生成
Yactoは、とくにマイグレーション周りがEctoと異なります。Yactoを使えば、Ectoのようにマイグレーションをわざわざ定義しなくても、スキーマからマイグレーションファイルが自動的に出力されるのです。

#### EctoとYactoのマイグレーションの違い
| ライブラリ | マイグレーション |
|:---------|:-----------|
| Ecto     | スキーマとマイグレーションを別に定義 |
| Yacto    | スキーマからマイグレーションファイルを自動的に出力 |

たとえば、`lib/my_app/player.ex`につぎのようなスキーマを定義したとしましょう。

```elixir
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`が出力されるのです。

```elixir
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を使っているアプリケーションでは、すべて同じ方法でマイグレーションができるということです。

```terminal
mix yacto.migrate --app my_app
```


# 水平分割したデータベースへのマイグレーション
たとえば、`MyApp.Player`スキーマを水平分割した場合、このスキーマのマイグレーションファイルを複数のRepoに適用しなければなりません。これは、設定ファイル`config/config.exs`につぎのように書くだけで済みます。

```elixir
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のグループ名です。

```elixir
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が取得できます。

```elixir
repo = Yacto.DB.repo(:player, player_id)
MyApp.Player |> repo.all()
```


# 複数データベースにまたがるトランザクション（XAトランザクション）
`Yacto.transaction/2`を使うと、複数のデータベースを指定してトランザクションが発行できます。

```elixir
# ２つ以上の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)
```

つぎの３つのRepoに対してトランザクションが行われます。

- `:default`の`RepoMyApp.Repo.Default`
- `player_id1`でシャーディングされたRepo
- `player_id2`でシャーディングされたRepo

あとのふたつは、シャードキーによっては同じRepoになる可能性があるので、利用するRepoはふたつか３つのどちらかです。ふたつ以上のRepoを利用してトランザクションを開始する場合は、自動的にXAトランザクションになります。


# その他
Yactoのおもな機能について、かいつまんでにご説明しました。さらに詳しくは、前出「[[Yacto] Ecto のマイグレーションを自動化したり水平分割するためのライブラリを作った](https://qiita.com/melpon/items/5c9b0645d5240cd22d0f)」をお読みください。Yactoのスキーマの定め方や使い方、および便利関数についても解説されています。

- [Yactoのスキーマ](https://qiita.com/melpon/items/5c9b0645d5240cd22d0f#yacto-%E3%81%AE%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%9E)
- [便利関数](https://qiita.com/melpon/items/5c9b0645d5240cd22d0f#%E4%BE%BF%E5%88%A9%E9%96%A2%E6%95%B0)