すごいE本 第19-20章 on Elixir （OTPアプリケーション）

# 環境

```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)
```

# 19.1 私のもう一台の車はプールです

前章の `p_pool` ディレクトリを任意の別名に変更し、
新たにプロジェクトを `--sup` 付きで作成する。
「このアプリケーションをスーパーバイザで管理する」と指示している。

続いて、プロジェクトの `lib` 以下に古い方のファイルをコピーする。

```shell-session:sh
$ ls -F
p_pool/
$ mv p_pool p_pool_old
$ mix new p_pool --module PPool --sup
...
$ cp -r p_pool_old/lib/* p_pool/lib/
$ cd p_pool
$ tree lib
lib
├── nagger.ex
├── p_pool
│   ├── application.ex
│   ├── poolvisor.ex
│   ├── stash.ex
│   ├── supervisor.ex
│   └── workervisor.ex
└── p_pool.ex

1 directory, 7 files
```

`application.ex` が追加されていることが確認できる。

# 19.2 アプリケーションリソースファイル

プロジェクトをコンパイルしてみる。

```shell-session:sh
$ mix compile
Compiling 7 files (.ex)
Generated p_pool app

$ tree _build
_build
└── dev
    └── lib
        └── p_pool
            ├── consolidated
            │   ├── Elixir.Collectable.beam
            │   ├── Elixir.Enumerable.beam
            │   ├── Elixir.IEx.Info.beam
            │   ├── Elixir.Inspect.beam
            │   ├── Elixir.List.Chars.beam
            │   └── Elixir.String.Chars.beam
            └── ebin
                ├── Elixir.Nagger.beam
                ├── 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

5 directories, 14 files

$ cat _build/dev/lib/p_pool/ebin/p_pool.app
{application,p_pool,
             [{applications,[kernel,stdlib,elixir,logger]},
              {description,"p_pool"},
              {modules,['Elixir.Nagger','Elixir.PPool',
                        'Elixir.PPool.Application','Elixir.PPool.Poolvisor',
                        'Elixir.PPool.Stash','Elixir.PPool.Supervisor',
                        'Elixir.PPool.Workervisor']},
              {registered,[]},
              {vsn,"0.1.0"},
              {mod,{'Elixir.PPool.Application',[]}}]}.
```

おっと `Nagger` までアプリケーションに入れてしまった。
はてさて、どうしたものか。

とりあえず `lib` 以下の掃除をし、
`test` ディレクトリに `nagger.ex` を退避しておこう。

```shell-session:sh
$ mix clean
$ tree _build
_build
└── dev
    └── lib

2 directories, 0 files

$ mv lib/nagger.ex test/
```

# 19.3 プールを変換する

E本のアプリケーション・リソース・ファイルに合わせるには、
`Mix.Project` の `application/0` に `registered` を追加する。

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

  def project do
    [
      app: :p_pool,
      version: "0.1.0",
      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
$ mix compile
Compiling 6 files (.ex)
Generated p_pool app

$ cat _build/dev/lib/p_pool/ebin/p_pool.app
{application,p_pool,
             [{applications,[kernel,stdlib,elixir,logger]},
              {description,"p_pool"},
              {modules,['Elixir.PPool','Elixir.PPool.Application',
                        'Elixir.PPool.Poolvisor','Elixir.PPool.Stash',
                        'Elixir.PPool.Supervisor','Elixir.PPool.Workervisor']},
              {vsn,"0.1.0"},
              {mod,{'Elixir.PPool.Application',[]}},
              {registered,['Elixir.PPool.Supervisor']}]}.
```

# 19.4 アプリケーションビヘイビア

Mix が作った雛形はこうなっている。

```elixir:p_pool/lib/p_pool/application.ex
defmodule PPool.Application do
  @moduledoc false
  use Application

  # PPool.MixProject の application/0 の mod で呼ばれる
  def start(_type, _args) do
    # 簡易なスーパーバイザが書かれている
    children = []
    opts = [strategy: :one_for_one, name: PPool.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

# 19.5 カオスからアプリケーションへ

雛形を変更する。

```elixir:p_pool/lib/p_pool/application.ex
defmodule PPool.Application do
  @moduledoc false
  use Application

  def start(:normal, _args) do
    {:ok, _pid} = PPool.Supervisor.start_link()
  end
end
```

上記に合わせて `PPool`(`p_pool.ex`) から `start_link/1` `stop/1` を削除しておく。

```shell-session:sh
$ mix format
$ iex -S mix
```
```elixir:iex
iex(1)> c "test/nagger.ex"            
[Nagger]
iex(2)> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.189.0>}
iex(3)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
{:ok, #PID<0.193.0>}
iex(4)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
{:ok, #PID<0.195.0>}
iex(5)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
:no_alloc
iex(6)> Received down message.
Received down message.
flush()
{#PID<0.193.0>, #Reference<0.368367721.402391043.109350>}
{#PID<0.193.0>, #Reference<0.368367721.402391043.109350>}
...
:ok
```

あれれ、標準出力がズレてら。
アプリケーションに包む前はこうだった。

```elixir:iex
...
:no_alloc
Received down message.
Received down message.
iex(6)> flush()
...
```

アプリケーションに包んで、非同期なコールバック中で標準出力するとズレるみたい。
どうしてでしょう？まぁ、副作用なんで深く考えるのはやめておこう。

ちなみに、E本のソースコードは著者の GitHub に公開されている。

[GitHub - FranklinChen/learn-you-some-erlang: Code from the book "Learn You Some Erlang For Great Good!"](https://github.com/FranklinChen/learn-you-some-erlang)

話が長くなるのでテストについては今回はパスで。

```elixir:iex
iex(7)> :application.which_applications()
[
  {:p_pool, 'p_pool', '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(8)> Application.stop(:p_pool)
:ok

06:55:28.837 [info]  Application p_pool exited: :stopped
```

「私たちのアプリケーションが temporary になっているのはなぜでしょう」と、
`:application.start/2`(`Application.start/2`) について説明が続くが、
Elixir の場合も規定値は `:temporary` だ。

`PPool.MixProject` の `project` に
雛形で `start_permanent: Mix.env() == :prod` と書いてある。
「もし本番環境ならば permanent で起動してね」という意味だ。
`$ MIX_ENV=prod iex -S mix` とやれば本番環境になる。
このプロジェクトの現段階では `dev` と `prod` に違いは無い。

# 19.6 ライブラリアプリケーション

プロジェクトを作るとき `--sup` が無ければライブラリアプリケーション。

# 20.2 アプリケーションよ、走れ

もう20章なんか実装する気にならんわ。

例えば、19章の `PPool` プロジェクトのパスが `~/src/p_pool` だとすると、

```shell-session:sh
$ ERL_LIBS='~/src/p_pool/_build/dev/lib/p_pool' iex
```
```elixir:iex
iex(1)> Application.load(:p_pool)
:ok
iex(2)> Application.start(:p_pool)
:ok
iex(3)> PPool.start_pool(Foo, :start, [])    
{:ok, #PID<0.113.0>}
```

やっと普通の言語っぽく動き出した、なげーよ。
やっぱり Erlang/Elixir はサーバを書くための DSL なんだね。

# 20.4 複雑な終了

下記の定義で `iex -S mix` する。

```elixir:p_pool/lib/p_pool/application.ex
defmodule PPool.Application do
  @moduledoc false
  use Application

  def start(:normal, _args) do
    {:ok, _pid} = PPool.Supervisor.start_link()
  end

  def prep_stop(state) do
    IO.puts("prep!")
    state
  end

  def stop(_state) do
    IO.puts("stop!")
    :ok
  end
end
```
```elixir:iex
iex(1)> Application.stop(:p_pool)
prep!
stop!

17:26:06.675 [info]  Application p_pool exited: :stopped
:ok
```

次章はリリース。
うーん、まだやるかぁ、しんどいな・・・。