すごいE本 第21章 on Elixir （アンブレラ＆リリース）

# 環境

```shell-session:sh
$ lsb_release -d
Description:    Ubuntu 18.04.2 LTS

$ elixir -v
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Elixir 1.8.1 (compiled with Erlang/OTP 20)
```

# 21.1 水漏れパイプを直す
## アプリケーションファイルを更新する

E本では `ppool.app` `erlcount.app` の２つのアプリを持っているが、
私は `erlcount.app` を作っていない。
辻褄合わせということで、テスト用に使った `nagger.ex` をライブラリにしてしまおう。

[アンブレラプロジェクト · Elixir School](https://elixirschool.com/ja/lessons/advanced/umbrella-projects/)

```shell-session:sh
$ mix new pool --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 pool
    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.

$ cd pool
$ tree
.
├── README.md
├── apps
├── config
│   └── config.exs
└── mix.exs

2 directories, 3 files
```

`apps` ディレクトリに `PPool` `Nagger` を作る。

```shell-session:sh
$ cd apps
$ mix new p_pool --module PPool --sup
...
$ mix new nagger
...
$ tree
.
├── nagger
│   ├── README.md
│   ├── config
│   │   └── config.exs
│   ├── lib
│   │   └── nagger.ex
│   ├── mix.exs
│   └── test
│       ├── nagger_test.exs
│       └── test_helper.exs
└── p_pool
    ├── README.md
    ├── config
    │   └── config.exs
    ├── lib
    │   ├── p_pool
    │   │   └── application.ex
    │   └── p_pool.ex
    ├── mix.exs
    └── test
        ├── p_pool_test.exs
        └── test_helper.exs

9 directories, 13 files
```

そして、前回までのソースをコピーする。

```shell-session:sh
$ ls -F ../../
p_pool/ pool/

$ cp -r ../../p_pool/lib/* p_pool/lib/
$ cp ../../p_pool/test/nagger.ex nagger/lib/
$ tree
.
├── nagger
│   ├── README.md
│   ├── config
│   │   └── config.exs
│   ├── lib
│   │   └── nagger.ex
│   ├── mix.exs
│   └── test
│       ├── nagger_test.exs
│       └── test_helper.exs
└── p_pool
    ├── README.md
    ├── config
    │   └── config.exs
    ├── lib
    │   ├── p_pool
    │   │   ├── application.ex
    │   │   ├── poolvisor.ex
    │   │   ├── stash.ex
    │   │   ├── supervisor.ex
    │   │   └── workervisor.ex
    │   └── p_pool.ex
    ├── mix.exs
    └── test
        ├── p_pool_test.exs
        └── test_helper.exs

9 directories, 17 files
```

## アプリケーションをコンパイルする

```shell-session:sh
$ cd ..  # アンブレラ pool のルートに戻る
$ ls -F
README.md  apps/  config/  mix.exs

$ iex -S mix
```

`$ mix compile` でもいいね。

```elixir:iex
iex(1)> :application.which_applications()
[
  {:p_pool, 'p_pool', '0.1.0'},
  {:nagger, 'nagger', '0.1.0'},
  {:logger, 'logger', '1.8.1'},
  {:mix, 'mix', '1.8.1'},
  {:iex, 'iex', '1.8.1'},
  {:elixir, 'elixir', '1.8.1'},
  {:compiler, 'ERTS  CXC 138 10', '7.3.2'},
  {:stdlib, 'ERTS  CXC 138 10', '3.8.1'},
  {:kernel, 'ERTS  CXC 138 10', '6.3.1'}
]
iex(2)> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.151.0>}
iex(3)> PPool.run(Nagger, [self(), :msg, 500, 3])
{:ok, #PID<0.155.0>}
iex(4)> Received down message.
flush()
{#PID<0.155.0>, :msg}
{#PID<0.155.0>, :msg}
{#PID<0.155.0>, :msg}
:ok
```

おお、いぇー。
`PPool` `Nagger` 共に `mix.exs` が今までとは少し違う。

```elixir:pool/apps/p_pool/mix.exs
defmodule PPool.MixProject do
  use Mix.Project

  def project do
    [
      app: :p_pool,
      version: "0.1.0",

      # E本の内容に合わせるならば以下を追加
      description: "Run and enqueue different concurrent tasks",

      # Mix によって追加されてる
      build_path: "../../_build",
      config_path: "../../config/config.exs",
      deps_path: "../../deps",
      lockfile: "../../mix.lock",

      elixir: "~> 1.8",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    [
      extra_applications: [:logger],
      mod: {PPool.Application, []},
      registered: [PPool.Supervisor]  # お忘れなく
    ]
  end

  defp deps do
    []
  end
end
```
```shell-session:sh
$ tree _build
_build
└── dev
    ├── consolidated
    │   ├── Elixir.Collectable.beam
    │   ├── Elixir.Enumerable.beam
    │   ├── Elixir.IEx.Info.beam
    │   ├── Elixir.Inspect.beam
    │   ├── Elixir.List.Chars.beam
    │   └── Elixir.String.Chars.beam
    └── lib
        ├── nagger
        │   └── ebin
        │       ├── Elixir.Nagger.beam
        │       └── nagger.app
        └── p_pool
            └── ebin
                ├── Elixir.PPool.Application.beam
                ├── Elixir.PPool.Poolvisor.beam
                ├── Elixir.PPool.Stash.beam
                ├── Elixir.PPool.Supervisor.beam
                ├── Elixir.PPool.Workervisor.beam
                ├── Elixir.PPool.beam
                └── p_pool.app

7 directories, 15 files
```

なるほどね。（日経電子ry

## ※ アンブレラ内部の依存関係

今回の場合には関係のない話なのだが、
例えば `PPool` が起動時に `Nagger` に依存していたとする。

```elixir:pool/apps/p_pool/mix.exs
defmodule PPool.MixProject do
  use Mix.Project
  ...
  defp deps do
    [{:nagger, in_umbrella: true}]
  end
end
```

なるほどね。（にっry

# ※ Distillery でのリリース

Elixir では Distillery を使う。

- [Home - Distillery Documentation](https://hexdocs.pm/distillery/home.html)
- [Distillery (基本) · Elixir School](https://elixirschool.com/ja/lessons/libraries/distillery/)

```elixir:pool/apps/p_pool/mix.exs
defmodule Pool.MixProject do
  use Mix.Project

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

  defp deps do
    [
      distillery: "~> 2.0"  # 追加
    ]
  end
end
```
```shell-session:sh
$ mix do deps.get, deps.compile
...
$ mix release.init

An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix release` to build the release
```

`rel/config.exs` が生成されたらしいので見てみよう。

```elixir:pool/rel/config.exs
# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))

use Mix.Releases.Config,
    # This sets the default release built by `mix release`
    default_release: :default,
    # This sets the default environment used by `mix release`
    default_environment: Mix.env()

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/config/distillery.html


# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  # If you are running Phoenix, you should make sure that
  # server: true is set and the code reloader is disabled,
  # even in dev mode.
  # It is recommended that you build with MIX_ENV=prod and pass
  # the --env flag to Distillery explicitly if you want to use
  # dev mode.
  set dev_mode: true
  set include_erts: false
  set cookie: :"Qt.(TQmtgz<olih:/ihSj$V2{`NFP&m3~CL>?eyRz9}Au1T|m|{dF|mrCeC&9`r<"
end

environment :prod do
  set include_erts: true
  set include_src: false
  set cookie: :",Xf:!buFeY`>~kG1yw)kHjD2AYRqF61lBiu,XFB)mD^{TgbdWeL8sugf&hONdAx5"
  set vm_args: "rel/vm.args"
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default

release :pool do
  set version: "0.1.0"
  set applications: [
    :runtime_tools,
    nagger: :permanent,
    p_pool: :permanent
  ]
end
```

アンブレラ・プロジェクトだと問題あるかもと思ったけど、いけてるみたい。
この設定だと本番環境でリリースした場合に Erlang ランタイムが含まれそうだね。
それでは開発版のリリースといこう。

```shell-session:sh
$ mix release
==> Assembling release..
==> Building release pool:0.1.0 using environment dev
==> You have set dev_mode to true, skipping archival phase
Release successfully built!
To start the release you have built, you can use one of the following tasks:

    # start a shell, like 'iex -S mix'
    > _build/dev/rel/pool/bin/pool console

    # start in the foreground, like 'mix run --no-halt'
    > _build/dev/rel/pool/bin/pool foreground

    # start in the background, must be stopped with the 'stop' command
    > _build/dev/rel/pool/bin/pool start

If you started a release elsewhere, and wish to connect to it:

    # connects a local shell to the running node
    > _build/dev/rel/pool/bin/pool remote_console

    # connects directly to the running node's console
    > _build/dev/rel/pool/bin/pool attach

For a complete listing of commands and their use:

    > _build/dev/rel/pool/bin/pool help
```

`iex -S mix` ライクに使うなら `console` だよって書いてあるね。

```shell-session:sh
$ _build/dev/rel/pool/bin/pool console
```
```elixir:iex
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(pool@127.0.0.1)1> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.790.0>}
iex(pool@127.0.0.1)2> PPool.run(Nagger, [self(), make_ref(), 500, 3])
{:ok, #PID<0.794.0>}
iex(pool@127.0.0.1)3> Received down message.
flush()
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
:ok
```

おお、やったぁ。

## デプロイ

本番環境でリリース。

```shell-session:sh
$ MIX_ENV=prod mix release
...
$ ll _build/prod/rel/pool/releases/0.1.0/合計 12892
drwxrwxr-x  5 feelinguy feelinguy     4096  5月 10 04:11 ./
drwxrwxr-x  3 feelinguy feelinguy     4096  5月 10 04:11 ../
drwxrwxr-x  2 feelinguy feelinguy     4096  5月 10 04:11 commands/
-rw-rw-r--  1 feelinguy feelinguy    34974  5月 10 04:11 config.boot
-rw-rw-r--  1 feelinguy feelinguy    47582  5月 10 04:11 config.script
drwxrwxr-x 10 feelinguy feelinguy     4096  5月 10 04:11 hooks/
drwxrwxr-x  4 feelinguy feelinguy     4096  5月 10 04:11 libexec/
-rw-rw-r--  1 feelinguy feelinguy    34910  5月 10 04:11 no_dot_erlang.boot
-rw-rw-r--  1 feelinguy feelinguy    47518  5月 10 04:11 no_dot_erlang.script
-rw-rw-r--  1 feelinguy feelinguy    35616  5月 10 04:11 pool.boot
-rwxrwxrwx  1 feelinguy feelinguy     5768  5月 10 04:11 pool.ps1*
-rw-rw-r--  1 feelinguy feelinguy      448  5月 10 04:11 pool.rel
-rw-rw-r--  1 feelinguy feelinguy    48166  5月 10 04:11 pool.script
-rwxrwxrwx  1 feelinguy feelinguy     4195  5月 10 04:11 pool.sh*
-rw-rw-r--  1 feelinguy feelinguy 12719279  5月 10 04:11 pool.tar.gz  # これだよー
-rw-rw-r--  1 feelinguy feelinguy    35616  5月 10 04:11 start.boot
-rw-rw-r--  1 feelinguy feelinguy    48166  5月 10 04:11 start.script
-rw-rw-r--  1 feelinguy feelinguy    34910  5月 10 04:11 start_clean.boot
-rw-rw-r--  1 feelinguy feelinguy    47518  5月 10 04:11 start_clean.script
-rw-rw-r--  1 feelinguy feelinguy       97  5月 10 04:11 sys.config
-rw-rw-r--  1 feelinguy feelinguy      849  5月 10 04:11 vm.args
```

一番大きなサイズの tarball がある、12MBぐらい。
この `pool.tar.gz` を本番環境に展開すればデプロイ完了。

```shell-session:sh
$ mkdir ~/deploy
$ cp _build/prod/rel/pool/releases/0.1.0/pool.tar.gz ~/deploy/
$ cd ~/deploy
$ tar -zxf pool.tar.gz
$ ls -F
bin/  erts-10.3.4/  lib/  pool.tar.gz  releases/

$ cd bin
$ ./pool start
$ ./pool ping
pong

$ ./pool remote_console
...
# iex セッション Ctrl+C
...
$ ./pool stop
ok
```

できたねぇ。
例えばこれを別アーキテクチャの環境に持っていっても動かない。
今時なんかだと Docker とかでやるんでしょうね。覚えること多すぎ。

# おわりに

次の第22章はやりません、たぶん。
ホット・コード・ロードの話になるんだけど、これまたきつい。
これについては、書籍 [プログラミング Elixir](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0Elixir-Dave-Thomas/dp/4274219151) の第18章が参考になる。
ちょっと古いので Distillery ではなく EXRM というのを使ってるんだけど、
どっちのツールも開発者は同じなのでなんとかなる。

英語も読めなければ、メンターもいない私。がんばったよ。