DEV Community

gumi TECH for gumi TECH Blog

Posted on • Edited on

MixとOTP 07: 依存関係とアンブレラプロジェクト

本稿はElixir公式サイトの許諾を得て「Dependencies and umbrella projects」の解説にもとづき、加筆補正を加えて、Mixで依存関係をどう管理するかについてご説明します。

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

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

外部依存関係

外部依存というのは、ビジネスドメインに結びつかない関係です。たとえば、分散アプリケーションにHTTP APIが必要な場合は、外部依存関係としてPlugプロジェクトが使えます。

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

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

def deps do
  [{:plug, "~> 1.0"}]
end
Enter fullscreen mode Exit fullscreen mode

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

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

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

Mixには依存関係を扱う多くのタスクが備わっています。mix helpで示されるリストのうちmix depsで始まるのがそれらのタスクです。

$ 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
Enter fullscreen mode Exit fullscreen mode

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

内部依存関係

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

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

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

def deps do
  [{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}]
end
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

アンブレラプロジェクト

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

+ kv_umbrella
  + apps
    + kv
    + kv_server
Enter fullscreen mode Exit fullscreen mode

kv_umbrellakvとは別のプロジェクトです。kvプロジェクトの中につくらないようお気をつけください。アンブレラプロジェクトをつくるときは、mix newコマンドに、--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.
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

図001■アンブレラプロジェクトでつくられるファイルとフォルダ

mix_otp_07_001.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.
Enter fullscreen mode Exit fullscreen mode

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

図002■つくられた子アプリケーションのファイル

mix_otp_07_002.png

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

def project do
  [

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

  ]
end
Enter fullscreen mode Exit fullscreen mode

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

def application do
  [

    mod: {KVServer.Application, []}
  ]
end
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

$ 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
Enter fullscreen mode Exit fullscreen mode

アンブレラプロジェクトの中の依存関係

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

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

defp deps do
  [
    {:kv, in_umbrella: true}
  ]
end
Enter fullscreen mode Exit fullscreen mode

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

図003■アンブレラプロジェクトのappsに依存アプリケーションをコピーする

mix_otp_07_003.png

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

def project do
  [

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

  ]
end
Enter fullscreen mode Exit fullscreen mode

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

$ 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
Enter fullscreen mode Exit fullscreen mode

服従はしなくてよい

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

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

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

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

まとめ

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

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

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

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

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

MixとOTPもくじ

Top comments (0)