技術メモ

プログラミングとか電子工作とか

Elixir入門(Mix and OTP編 第7章 Dependencies and umbrella projects)

f:id:ysmn_deus:20190122112104p:plain

どうも、靖宗です。
この章も引き続きMix and OTP編なので、通読が必要かもしれません。
Elixir入門は一度再編集したほうが分かりやすいし自分の為にもなるかもしれないなぁ。

今回は依存関係という感じでしょうか?
そういえばmix.exsに依存関係を記述するような箇所があったのであの辺でしょう。

TCP周りは一から作って行くのは車輪の再発明なのでTCPの理解をしたいときでなければ辞めておきましょう。
ということでおそらくエコシステム(PythonでいうpipとかRubyでいうgem?)の使い方とかです。

External dependencies

Elixirには外部依存と内部依存という考え方があるそうです。とりあえず外部から。
ErlangとElixirの共通のエコシステムとしてHex Package Managerなるものがあるそうです。なんかどっかで見たことあるなと思ったら、Elixirのドキュメントとかこのページの一部な気がします。

今回はHTTPのAPIが欲しいので、Plugというプロジェクトをとってきます。予想通りmix.exsに依存関係を記述していきます。

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

mix.exsに記載するということはこのエコシステムからの取得もmixがやってくれるんでしょうか。デキる奴・・・
~> 1.0という書き方はVer1.0系列の最新版を取得してくる書き方のようです。
基本的にHexに登録されてるのはstable版なので、開発版を使いたい場合はgithubから直接取得することもできるそうです。

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

mixで依存関係の取得などをするにはmix deps.getを利用するようです。

PS > mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
[32m  mime 1.3.1[0m
[32m  plug 1.7.2[0m
[32m  plug_crypto 1.0.0[0m
* Getting plug (Hex package)
* Getting mime (Hex package)
* Getting plug_crypto (Hex package)

とりあえず問題無さそう。
なんにも機能使ってないけどコンパイルできるかやってみる。

PS > mix compile
==> mime
Compiling 2 files (.ex)
Generated mime app
==> plug_crypto
Compiling 4 files (.ex)
Generated plug_crypto app
==> plug
Compiling 1 file (.erl)
Compiling 38 files (.ex)
warning: System.stacktrace/0 outside of rescue/catch clauses is deprecated. If you want to support only Elixir v1.7+, you must access __STACKTRACE__ inside a rescue/catch. If you want to support earlier Elixir versions, move System.stacktrace/0 inside a rescue/catch
  lib/plug/conn/wrapper_error.ex:23

Generated plug app
==> kv
Compiling 4 files (.ex)
Generated kv app

なんか怒られたけどとりあえず通ってる。
依存関係を取り扱うmixのコマンドはmix helpのなかで参照できるので困ったらそれかドキュメントをみよう。
たぶん大体mix deps.getmix deps.updateぐらいかな?

また、mix.lockというファイルが作成されるが、これは現在のバージョンなどの依存関係を記録するファイルらしく、開発環境の再現性の為にはわりと重要になってくるかも。
たぶんそこまで意識しなくてもmixがよしなにしてくれる。

Internal dependencies

内部依存に関しては、イメージ的に外部に公開したくない様なライブラリ?モジュール?プロジェクト的なものを指しているそうです。
External/InternalというよりはPublic/Privateというイメージの方がしっくりくるかも。

mixでの対応は主に2種類で、githubのポジトリを使用するか、umbrella projectsを使用するかだそうです。umbrella projectsに関しては次の項目で。

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

たぶん参照先さえ間違ってなければgitlabでもいけそう。
もしリポジトリがプライベートであれば、アドレスはgit@github.com:YOUR_ACCOUNT/kv.gitと書く必要があるそうです。勿論暗号鍵通信などの認証関係の設定はお忘れ無く。

ただ、なんでもかんでもgithubなどに置くというのはメンテナンス性にしろあまり嬉しくないのも事実。そこでプロジェクトがネストしたような構造?のumbrella projectsがあるそうです。
たぶんkvのプロジェクトディレクトリに細かいモジュール群的なモノを入れていく感じなんでしょう。

イメージするディレクトリ構造はこのようになってるみたいです。

+ kv_umbrella
  + apps
    + kv
    + kv_server

いままで作ってきたKVモジュールがappsディレクトリ以下に来てるので、新しくプロジェクトを始めるイメージです。
とりあえず進みましょう。

Umbrella projects

umbrella projectsとしてプロジェクトを追加していくのにもmixを使うようです。
KVを追加したときと同じ様にmix newを使うそうですが、--umbrellaオプションが必須だそうです。

PS > pwd

Path
----
D:\hogehoge\kv\


PS D:\hogehoge\kv> cd ..
PS D:\hogehoge> 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.

なんかできた。
生成されたディレクトリにもmix.exsがあるが、プロジェクトディレクトリにある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

project/0関数のところで既に違う。apps_path: "apps"なるオプションがありますが、これがumbrellaとして動作するようです。
とりあえずサンプルを進めて行きます。

まずはkv_umbrella/apps/にモジュールを追加していきます。

PS > cd .\kv_umbrella\apps
PS > 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 new kv_server --module KVServer --supとしていますが、--supはsupervision treeを自動的に組み込むオプションだそうです。
mix.exsapplication/0mod: {KVServer.Application, []}が追加されており、その参照してるモジュールのKVServer.ApplicationKVでsupervisorを起動するコードを書いた箇所などを全て自動で用意してくれます。

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

childrenにモジュール名と名前を記載するのではなくoptsに直接名前を定義しにいってます。

さて、基本的にmix newコマンドは--supを除いて最初のKVを作成したときと同じですが、mix.exsの中身が違います。

defmodule KVServer.MixProject do
  use Mix.Project

  def project do
    [
      app: :kv_server,
      version: "0.1.0",
      build_path: "../../_build",
      config_path: "../../config/config.exs",
      deps_path: "../../deps",
      lockfile: "../../mix.lock",
      elixir: "~> 1.7-dev",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications
  def application do
    [
      extra_applications: [:logger],
      mod: {KVServer.Application, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
      # {:sibling_app_in_umbrella, in_umbrella: true},
    ]
  end
end

kv_umbrella/apps下で実行したことにより、mixがよしなに下記の項目を追加してくれました。

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

ビルドパスなどが全てkv_umbrellaディレクトリを参照しています。
ここに作成するモジュールは全てkv_umbrellaコンパイルされるようです。依存関係もkv_umbrellaディレクトリを参照するようです。

一応追加したKVServerモジュールが問題無いかテスとしてみます。とはいってもテストはまだ編集してないのでmixなどが正常に動作するかのチェック程度です。

PS kv\kv_umbrella\apps\kv_server> mix test
Compiling 2 files (.ex)
Generated kv_server app
..

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

Randomized with seed 932000

おけまる!
ここまで問題無いのでKVServerにKVモジュールの依存関係を記載していきます。

Dependencies within an umbrella project

apps/kv_server/mix.exsに依存関係を追記します。

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

これはKVServerがKVモジュールに依存している、つまりKVServerが起動する前にKVモジュールが起動してる必要性があることを意味しています。

依存関係が書けたので、KVモジュールをコピーしてきます。
以下のディレクトリ構造になるようにkvフォルダごとappsの中に放り込みます。

+ kv_umbrella
  + apps
    + kv # これ
    + kv_server

ビルドパスなどをkv_server同様に変更します。
apps/kv/mix.exsapps/kv_server/mix.exsと同様に修正。

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

一応mix testすると、問題無く通ります。

Don’t drink the kool aid(盲信するな)

umbrella projectはモジュール?プロジェクト?の分離管理に訳に立ちそうですが、依存関係などは完全に切り離せている訳ではありません。
完全に切り離したいならappsディレクトリから別のプロジェクトフォルダに移してしまってbuild_pathなどを削除してしまうのが良さそうです。
そうした後に依存関係の箇所で書いた{:kv, git: "https://github.com/YOUR_ACCOUNT/kv.git"}:git:pathに変更してパスを指定すれば使えそうな記述があります。
需要が出てきたらやってみたいとは思いますが基本はumbrella projectかそもそもプロジェクト一個で書いてしまうのが良さそうです。

この辺も色々経験してみてから再考した方が良さそうです(´ε`;)