mmag

ハマったことメモなど

Ectoで複数のRepoを使う

なにがしたい

例えば、レガシーなシステムを徐々にモダンにしていきたいので、古いDBと新しいDBを 共存させたいとします。要は、異なる接続先の2つ3つのDBを使いたい。

どうする

Repoを2つ作ります。

以下、Phoenixのディレクトリ構成に合わせて説明します。

普通にmix phoenix.newすると、lib/my_app/repo.exが作られます。

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app
end

このRepoはEctoの各種Mixタスクでデフォルトとして使われるもので、 接続先はconfig/dev.exsなどに書かれてます。例えばこんな。

# Configure your database
config :my_app, MyApp.Repo,
  adapter:  Ecto.Adapters.MySQL,
  username: "joe",
  password: "hogehoge",
  database: "my_app_dev",
  hostname: "192.168.33.10",
  size:     10

EctoのRepoはデータの保存場所を抽象化してくれるので、どこに保存するのか、 どんな形で保存されているのか、何のRDBMSが使われているのか、などは意識しなくてよく、 単にRepo.insert!とかすればよいというわけですね。便利便利。

ではこれを複数持つにはどうするかというと、素直に増やせばいいです。lib/my_appanother_repo.exを作ります。

# lib/my_app/another_repo.ex

defmodule MyApp.AnotherRepo do
  use Ecto.Repo, otp_app: :my_app
end

config/dev.exsに、AnotherRepoの接続先設定を追加します。適宜config/test.exsなどにも書いといてください。

# config/dev.exs

# 中略

# Configure your database
config :my_app, MyApp.Repo,
  adapter:  Ecto.Adapters.MySQL,
  username: "joe",
  password: "hogehoge",
  database: "my_app_dev",
  hostname: "192.168.33.10",
  size:     10

config :my_app, MyApp.AnotherRepo,
  adapter:  Ecto.Adapters.MySQL,
  username: "joe",
  password: "ebimayo",
  database: "my_app_another_dev",
  hostname: "192.168.33.11",
  size:     10

これで準備できました。migrateなどを実行するとき、

$ mix ecto.gen.migration add_user_table -r MyApp.AnotherRepo
$ mix ecto.migrate -r MyApp.AnotherRepo

のようにrオプションでどっちを使うか指定できます。無指定だとMyApp.Repoが使われます。

あとは、モデルの中などでRepo.insert!AnotherRepo.insert!かを使い分けたりすれば、 「このモデルはあっちのDBへ〜」とか「この条件を満たしたらこっち〜」とかできます。

便利便利。

追記

lib/my_app.exに書くの忘れてました。start/2childrenworker(MyApp.AnotherRepo, [])を足しましょう。

children = [
  # Start the endpoint when the application starts
  supervisor(MyApp.Endpoint, []),

  # Start the Ecto repository
  worker(MyApp.Repo, []),
  worker(MyApp.AnotherRepo, [])

  # Here you could define other workers and supervisors as children
  # worker(MyApp.Worker, [arg1, arg2, arg3]),
]

これ忘れると、mix testとかでこんなエラー出ます。

** (exit) exited in: GenServer.call(MyApp.AnotherRepo.Pool, {:query, :begin, &MyApp.AnotherRepo.log/1, [timeout: 5000]}, 5000)
    ** (EXIT) no process

テーマはFB matteをベースにしてます。作者さんに感謝を込めて。