MixとOTP 07: 依存関係とアンブレラプロジェクト
Tags: elixir, webdev, tutorial, programming
Published: 2019-05-09 10:10:48

---
title: MixとOTP 07: 依存関係とアンブレラプロジェクト
published: true
description: Elixir公式サイトの許諾を得て「Dependencies and umbrella projects」の解説にもとづき、加筆補正を加えて、Mixで依存関係をどう管理するかについてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Dependencies and umbrella projects](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html)」の解説にもとづき、加筆補正を加えて、Mixで依存関係をどう管理するかについてご説明します。

これから実装するのは、`kv`アプリケーションのリクエストを処理するサーバーです。けれど、アプリケーションにコードを書き加えるのではありません。`kv`アプリケーションのクライアントとなるTCPサーバーを、別のアプリケーションとして構築します。ランタイムとElixirのエコシステムは全体として複数のアプリケーションが扱えます。したがって、プロジェクトを大きなひとつのアプリケーションにしなくて構いません。小分けしたアプリケーションがまとまって動くようにつくればよいのです。

新たなアプリケーションをつくり始める前に、Mixが依存関係をどのように扱うのかご説明しましょう。普段実際に扱う依存関係はふたつあります。内部的な依存と外部的な依存です。Mixはこれら両方に対応するメカニズムを備えています。


# 外部依存関係
外部依存というのは、ビジネスドメインに結びつかない関係です。たとえば、分散アプリケーションにHTTP APIが必要な場合は、外部依存関係として[Plug](https://github.com/elixir-lang/plug)プロジェクトが使えます。

外部依存関係のインストールは簡単にできます。もっとも一般的なのは、[Hexパッケージマネージャ](https://hex.pm/)を使うことです。依存関係は`mix.exs`ファイルの`deps`関数の中にリストして加えます。

つぎの依存関係は、HexにプッシュされたPlug 1.0以降の最新バージョンを表します。バージョン番号1.0の前に添えられた`~>`が「以上」の意味です。バージョン要件の定め方について詳しくは、「[Version](https://hexdocs.pm/elixir/Version.html)」モジュールのドキュメントをご覧ください。

```elixir
def deps do
  [{:plug, "~> 1.0"}]
end
```

通常は、Hexにプッシュされるのは安定版です。開発中の外部関係に依存したい場合、MixはGitの依存関係も管理できます。

```elixir
def deps do
  [{:plug, git: "git://github.com/elixir-lang/plug.git"}]
end
```

プロジェクトに依存関係を加えると、Mixは繰り返しビルドができるように`mix.lock`ファイルをつくります。`lock`ファイルはバージョンコントロールシステムの管理下に追加しなければなりません。すると、そのプロジェクトを使う人はすべて同じ依存バージョンを用いることが保証されるのです。

Mixには依存関係を扱う多くのタスクが備わっています。`mix help`で示されるリストのうち[`mix deps`](https://hexdocs.pm/mix/Mix.Tasks.Deps.html)で始まるのがそれらのタスクです。

```terminal
$ mix help
# mix depsのみ抜粋
mix deps              # Lists dependencies and their status
mix deps.clean        # Deletes the given dependencies' files
mix deps.compile      # Compiles dependencies
mix deps.get          # Gets all out of date dependencies
mix deps.tree         # Prints the dependency tree
mix deps.unlock       # Unlocks the given dependencies
mix deps.update       # Updates the given dependencies
```

もっともよく使われるタスクは、`mix deps.get`と`mix deps.update`です。取得された依存関係は、自動的にコンパイルされます。`deps`については、`mix help deps`で説明が示されます。さらに詳しくは、`Mix.Task`モジュールのドキュメントで「[mix deps](https://hexdocs.pm/mix/Mix.Tasks.Deps.html#content)」をお読みください。


# 内部依存関係
内部依存は、プロジェクト固有の関係です。プロジェクト/会社/組織の範囲外では意味がありません。多くの場合、技術や経済あるいは経営的な理由により非公開とされます。

内部依存関係がある場合、Mixには対応方法がふたつあります。Gitリポジトリとアンブレラプロジェクトです。

たとえば、`kv`プロジェクトをGitリポジトリにプッシュするときは、`deps`のコードのリストに加えて使わなければなりません。

```elixir
def deps do
  [{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end
```

リポジトリが非公開の場合は、プライベートURLの`git@github.com:YOUR_ACCOUNT/kv.git`を指定してください。いずれであれ、適切な権限さえあれば、Mixは依存関係を取得できます。

内部依存関係にGitリポジトリを用いることは、Elixirではあまりお勧めできません。ランタイムとElixirエコシステムが、すでにアプリケーションの考え方を示していました。たとえひとつのプロジェクトでも、コードはたびたびアプリケーションに小分けされ、それらを論理的にまとめてゆくのです。

ところが、アプリケーションごとにGitリポジトリを別プロジェクトとしてプッシュすると、メンテナンスが難しくなり、コードを書くことよりGitリポジトリの管理に多くの時間が割かれてしまいます。

このため、Mixは「アンブレラプロジェクト」をサポートしているのです。アンブレラプロジェクトは、まとまって動く複数のアプリケーションをひとつのリポジトリで構築するために用いられます。アンブレラプロジェクトが目指すスタイルについてはつぎの項でご説明しましょう。


# アンブレラプロジェクト
新しいMixプロジェクトをつくりましょう。名前は`kv_umbrella`です。このプロジェクトはすでにつくったアプリケーション`kv`と新たな`kv_server`のふたつをもちます。でき上がるディレクトリはつぎのとおりです。

```terminal
+ kv_umbrella
  + apps
    + kv
    + kv_server
```

`kv_umbrella`は`kv`とは別のプロジェクトです。`kv`プロジェクトの中につくらないようお気をつけください。アンブレラプロジェクトをつくるときは、[`mix new`コマンド](https://hexdocs.pm/mix/Mix.Tasks.New.html#content)に、`--umbrella`オプションを渡します。

```
$ mix new kv_umbrella --umbrella
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs

Your umbrella project was created successfully.
Inside your project, you will find an apps/ directory
where you can create and host many apps:

    cd kv_umbrella
    cd apps
    mix new my_app

Commands like "mix compile" and "mix test" when executed
in the umbrella project root will automatically run
for each application in the apps/ directory.
```

通常のプロジェクトと比べて、つくられるファィルは少ないです(図001)。`mix.exs`ファイルの中身も、つぎのように異なっています(コメントは除きました)。

```elixir
defmodule KvUmbrella.MixProject do
  use Mix.Project

  def project do
    [
      apps_path: "apps",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  defp deps do
    []
  end
end
```

#### 図001■アンブレラプロジェクトでつくられるファイルとフォルダ
![mix_otp_07_001.png](https://thepracticaldev.s3.amazonaws.com/i/6w93p0l7pct4zl9sutk6.png)

アンブレラプロジェクトを特徴づけるのは、`project`に定められた`apps_path: "apps"`です。アンプレラプロジェクトには、ソースファイルもテストもありません。けれど、それぞれに依存関係がもてます。子のアプリケーションは、ディレクトリapps内につくらなければなりません。

では、appディレクトリに移って、アプリケーション`kv_server`を構築しましょう。子アプリケーションは手動でつくらず、`mix new`コマンドに`--sup`オプションを添えて生成してください。すると、Mixにより自動的に監視ツリーができ上がります。

```
$ cd kv_umbrella/apps
$ mix new kv_server --module KVServer --sup
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/kv_server.ex
* creating lib/kv_server/application.ex
* creating test
* creating test/test_helper.exs
* creating test/kv_server_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd kv_server
    mix test

Run "mix help" for more commands.
```

つくられた子アプリケーションのファイルは通常のMixプロジェクトと似ています(図002)。

#### 図002■つくられた子アプリケーションのファイル
![mix_otp_07_002.png](https://thepracticaldev.s3.amazonaws.com/i/p6un6be7b43ny08mb3m0.png)

`mix.exs`を開くと、アンブレラの子アプリケーションには追加がふたつあります。ひとつは`project`関数に加えられた親プロジェクトの構造を示す4つのパスです。これは`kv_umbrella/deps`にチェックアウトし、ビルドはまとめられ、`config.exs`と`mix.lock`が共有されることを示します。依存関係の取得とコンパイルは、アプリケーションごとでなく、アンブレラの構造全体で行われるのです。

```elixir
def project do
  [

    build_path: "../../_build",
    config_path: "../../config/config.exs",
    deps_path: "../../deps",
    lockfile: "../../mix.lock",

  ]
end
```

もうひとつの追加は、`application`関数の`mod: {KVServer.Application, []}`です。`mix new`コマンドに`--sup`オプションを渡したことにより、`KVServer.Application`がアプリケーションのコールバックモジュールに定められました。このモジュールが、アプリケーションの監視ツリーを開始するのです。

```elixir
def application do
  [

    mod: {KVServer.Application, []}
  ]
end
```

`apps/kv_server/lib/kv_server/application.ex`の`KVServer.Application`モジュールの記述はつぎのとおりです。アプリケーションコールバックモジュールは、`use Application`により[`Application`ビヘイビア](https://hexdocs.pm/elixir/Application.html#content)を実装しなければなりません。必要とされるのは、コールバック関数`start/2`です(「[The application callback module](https://hexdocs.pm/elixir/Application.html#module-the-application-callback-module)」参照)。この関数は、`KVServer.Supervisor`という名前でスーパーバイザーを定めています。なお、スーパーバイザーについて詳しくは「[Supervisor behaviour](https://hexdocs.pm/elixir/Supervisor.html)」をご参照ください。

```elixir
defmodule KVServer.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      # Starts a worker by calling: KVServer.Worker.start_link(arg)
      # {KVServer.Worker, arg},
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: KVServer.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

この子アプリケーションは`apps/kv_server`ディレクトリからテストできます。ここでは、アンブレラプロジェクト全体をテストするために、ルートの`kv_umbrella`に移って`mix test`を実行しましょう。

```terminal
$ mix test
==> kv_server
Compiling 2 files (.ex)
Generated kv_server app
==> kv_server
..

Finished in 0.04 seconds
1 doctest, 1 test, 0 failures

Randomized with seed 930276
```


# アンブレラプロジェクトの中の依存関係
`kv_server`には、`kv`に定めた機能を使うつもりです。そのために、アプリケーションの依存関係に`kv`を加えなければなりません。アンブレラプロジェクトでは、Mixがアプリケーション間の依存関係をつくれるように明示的な定義が求められます。`apps/kv_server/mix.exs`の`deps/0`関数に、以下のように書き加えてください。

これで`:kv`は`:kv_server`内の依存関係として使えるようになり、サーバーが起動する前に`kv`アプリケーションは自動的に起ち上がります。

```elixir
defp deps do
  [
    {:kv, in_umbrella: true}
  ]
end
```

そして、アプリケーション`kv`をアンブレラプロジェクトのディレクトリ`apps`にコピーしてください(図003)。

#### 図003■アンブレラプロジェクトのappsに依存アプリケーションをコピーする
![mix_otp_07_003.png](https://thepracticaldev.s3.amazonaws.com/i/8wxcd8ro0cu8bdp177c2.png)

さらに、`apps/kv/mix.exs`には、`apps/kv_server/mix.exs`と同じように、アンブレラプロジェクトの構成パス4つが必要です。`apps/kv/mix.exs`の`project/0`関数に、つぎのように書き加えてください。

```elixir
def project do
  [

    build_path: "../../_build",
    config_path: "../../config/config.exs",
    deps_path: "../../deps",
    lockfile: "../../mix.lock",

  ]
end
```

これでアンブレラプロジェクトのルートから、`mix test`で両方のプロジェクトのテストが走らせられます。

```terminal
$ mix test
==> kv
.....

Finished in 2.0 seconds
5 tests, 0 failures

Randomized with seed 857698
==> kv_server
..

Finished in 0.03 seconds
1 doctest, 1 test, 0 failures

Randomized with seed 857698
```


# 服従はしなくてよい
アンブレラプロジェクトは、複数のアプリケーションを整理して管理するのに便利です。アプリケーション間の分離の程度があるものの、まったく切り離される訳ではありません。同じ設定と依存関係を共有するとみなされるからです。

同じリポジトリに複数のアプリケーションを含めるパターンは「モノレポ」(mono-repo)と呼ばれます。アンブレラプロジェクトはこのパターンを最大限に活かして、複数のアプリケーションを一度にコンパイルしてテストし、実行することができるのです。

同じ依存関係のアプリケーションごとに設定を変えたいとか、あるいは異なる依存関係のバージョンを使いたいといった状況になったときは、コードベースがアンブレラプロジェクトの扱える範囲を超えて拡大している可能性があります。

そういうときも、アンブレラを分けるのは難しくありません。アプリケーションをアンブレラプロジェクトの`apps`ディレクリの外に出すだけでよいからです。最悪の場合は、アンブレラプロジェクトとそれに関連する設定(`(build_path`や`config_path`、`deps_path`、`lockfile`)を破棄することになるかもしれません。それでも、すべてのアプリケーションをそのまま同じリポジトリにまとめておけば、「モノレポ」パターンが引き続き活用できます。アプリケーションは、それぞれの依存関係と設定をもつことになります。アプリケーション間の依存関係は、`:path`オプションを用いて明示的にリストすることもできるのです(`:git`とは対照的です)。


# まとめ
`kv`はサーバーがなくても動くのに対して、`kv_server`は`kv`に直接依存します。それぞれを別のアプリケーションとして分けることで、開発やテストはより進めやすくなるのです。

アンブレラアプリケーションを使うとき、切り分けをはっきりさせることが大切です。開発する`kv_server`は、`kv`の定めた公開APIにのみアクセスしなければなりません。アンブレラアプリケーションにその他の依存関係は、Elixirそのものも含めて存在しないのです。アクセスできるのは、公開されドキュメント化されたものにかぎられます。依存関係の中の非公開の機能に触れることは避けるべきです。バージョンが改められたとき、コードが破綻してしまう原因となります。

アンブレラアプリケーションは、最終的にコードベースから切り出すアプリケーションの手始めとして利用することもできます。たとえば、ユーザーに「プッシュ通知」を送らなければならないwebアプリケーションがあるとします。「プッシュ通知システム」全体は、独自の監視ツリーとAPIにもとづき、アンブレラの中で別アプリケーションとして開発できるのです。すると、他のプロジェクトでプッシュ通知システムが求められた場合、システムをプライベートリポジトリやHexパッケージに移せるでしょう。

アンブレラプロジェクトは、開発者が広いビジネス領域を分けるために用いることもできます。確かめておかなければならないのは、領域が互いに依存しないことです(循環依存とも呼ばれます)。そうなってしまった場合には、アプリケーションが互いに分離されていないことを意味します。構造や設計を再検討しなければなりません。

アンブレラプロジェクトのアプリケーションで大切なのは、すべてが同じ設定と依存関係を共有することです。アンブレラの中のアプリケーションの間で、依存関係の設定が大きく異なったり、違うバージョンを用いなければならない場合は、おそらくアンブレラがもたらす利点を超えて大きくなりすぎたのでしょう。そういうとき、アンブレラを止めても、「モノレポ」にもとづく利益は得られます。


#### MixとOTPもくじ
- [MixとOTP 01: Mixを使ってみる](https://dev.to/gumi/mixotp-01-mix-dg1)
- [MixとOTP 02: Agent](https://dev.to/gumi/mixotp-02-agent-3n9g)
- [MixとOTP 03: GenServer](https://dev.to/gumi/mixotp-03-genserver-2jhn)
- [MixとOTP 04: スーパーバイザーとアプリケーション](https://dev.to/gumi/mix-otp-04-bj0)
- [MixとOTP 05: ダイナミックスーパーバイザー](https://dev.to/gumi/mix-otp-05-l3p)
- [MixとOTP 06: ETS](https://dev.to/gumi/mix-otp-06-ets-ol3)
- MixとOTP 07: 依存関係とアンブレラプロジェクト
- [MixとOTP 08: Taskとgen_tcp](https://dev.to/gumi/mix-otp-08-gentcp-5120)
- [MixとOTP 09: DocTestとwithのパターンマッチング](https://dev.to/gumi/mix-otp-09-doctest-with-533g)
- [MixとOTP 10: 分散処理のTaskと設定](https://dev.to/gumi/mix-otp-10-3i31)