MixとOTP 04: スーパーバイザーとアプリケーション
Tags: elixir, webdev, tutorial, programming
Published: 2019-04-02 02:20:35

---
title: MixとOTP 04: スーパーバイザーとアプリケーション
published: true
description: Elixir公式サイトの許諾を得て「Supervisor and Application」の解説にもとづき、加筆補正を加えて、ElixirにおけるSupervisorの使い方とアプリケーションの扱い方についてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---

本稿はElixir公式サイトの許諾を得て「[Supervisor and Application](https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html)」の解説にもとづき、加筆補正を加えて、Elixirにおける`Supervisor`の使い方とアプリケーションの扱い方についてご説明します。

ソフトウェアに失敗が起こったとき、大抵はそれを「回復しよう」とするでしょう。けれど、Elixirでは例外を解消するような受け身のプログラミングはしません。むしろ、「落ちるに任せる」のです。プロセスがバグでクラッシュしても、心配には及びません。スーパーバイザーを定めて、プロセスの新しいコピーを代わりに動かせばよいからです。


# はじめてのスーパーバイザー
`Supervisor`のつくり方は、`GenServer`とほぼ変わりません。ただし、使うビヘイビアは[`Supervisor`](https://hexdocs.pm/elixir/Supervisor.html)です。新たなファイル`lib/kv/supervisor.ex`に、モジュール`KV.Supervisor`をつぎのように定めてください。


```elixir
defmodule KV.Supervisor do
  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, :ok, opts)
  end

  def init(:ok) do
    children = [
      KV.Registry
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
```

スーパーバイザーは、とりあえず`KV.Registry`をひとつ子にもちます。子のリストを定めたら、[`Supervisor.init/2`](https://hexdocs.pm/elixir/Supervisor.html#init/2)にリストと監視戦略を渡して呼び出します。監視戦略は、子のいずれかがクラッシュしたときどうするかという指示です。`:one_for_one`というのは、子どもがひとつ落ちたら、その子どもを、ひとつだけ再起動するということです。今のところ子どもはひとつですのでこれでよいでしょう。`Supervisor`ビヘイビアには、ほかにも多くの戦略が備わっています。

スーパーバイザーが動き出すと、リストに納められた子のすべてに対して、各モジュールの[`child_spec/1`関数](https://hexdocs.pm/elixir/Supervisor.html#module-child_spec-1)を呼び出します。戻り値は名前のとおり、子の仕様です。プロセスの始まり方や、プロセスがワーカーなのかスーパーバイザーなのか、プロセスが仮か一時的か永続的かといったことが示されます。たとえば、つぎのような情報です。`child_spec/1`関数は、`Agent`か`GenServer`あるいは`Supervisor`を使うと自動的に定められます。

```elixir
iex> KV.Registry.child_spec([])
%{
  id: KV.Registry,
  restart: :permanent,
  shutdown: 5000,
  start: {KV.Registry, :start_link, [[]]},
  type: :worker
}
```

スーパーバイザーが子の仕様をすべて得ると、子をひとつずつリストの順に開始します。始め方は仕様の`:start`キーに示されており、上の例は`KV.Registry.start_link([])`を呼び出すということです。

「[MixとOTP 03: GenServer](https://dev.to/gumi/mixotp-03-genserver-2jhn)」では、テストのときプロセスを`start_supervised！/2`関数により始めました。`start_supervised！/2`は内部的に、`ExUnit`フレームワークが定めたスーパーバイザーにもとづいてプロセスを開始しているのです。スーパーバイザーを独自に定義すれば、アプリケーションの初期化や終了、あるいは監視の登録などを構成して、最終的なコードやテストを最適に調整できます。

今のところ`start_link/1`は、オプションとしてつねに空のリストを受け取ります。


# プロセスに名前をつける
アプリケーションは多くのプロセスをつくります。けれど、それらを登録する`KV.Registry`はひとつです。そこで、`KV.Registry`はPIDで参照するのでなく、名前をつけましょう。そうすれば、つねに名前で参照が得られます。

つくられるプロセスは、ユーザーの入力にもとづいて動的に開始されました。ですから、プロセスを管理するのに、アトムの名前はつけるべきではありません。登録する`KV.Registry`は違います。アプリケーションが起動するとき、スーパーバイザーのもとでひとつだけ開始されるのです

では、前掲モジュール`KV.Supervisor`(`lib/kv/supervisor.ex`)で定めたスーパーバイザーの子は、タプルのリストになるように少し手直ししましょう。

```elixir
def init(:ok) do
  children = [
    # KV.Registry
    {KV.Registry, name: KV.Registry}
  ]

  Supervisor.init(children, strategy: :one_for_one)
end
```

これまでスーパーバイザーは子のプロセスに対して、`KV.Registry.start_link([])`を呼び出していました。それが修正によって、`KV.Registry.start_link([name: KV.Registry])`の呼び出しに変わったのです。`KV.Registry.start_link/1`の実装はつぎのとおりでした。この[`GenServer.start_link/3`](https://hexdocs.pm/elixir/GenServer.html#start_link/3)に渡される第3引数のオプションに、`:name`として上の手直しで加えた名前が渡されるのです(「[Name registration](https://hexdocs.pm/elixir/GenServer.html#module-name-registration)」参照)。

```elixir
def start_link(opts) do
  GenServer.start_link(__MODULE__, :ok, opts)
end
```

これでプロセスに名前が登録されます。コンパイルしたら`iex -S mix`のシェルで試してみましょう。`KV.Registry.lookup/2`の第1引数に、登録した名前が使えます。

```elixir
iex> KV.Supervisor.start_link([])
{:ok, #PID<0.130.0>}
iex> KV.Registry.create(KV.Registry, "shopping")
:ok
iex> KV.Registry.lookup(KV.Registry, "shopping")
{:ok, #PID<0.134.0>}
```

スーパーバイザーを起動すると、登録の`KV.Registry`は名前が与えられて自動的に始まります。登録するプロセスも、つくれば開始されるのです。

実際には、アプリケーションのスーパーバイザーを開始する処理は滅多に書きません。アプリケーションのコールバックの一部として起動するからです。


# アプリケーションを理解する
これまでアプリケーションのコードを書いて動かしてきました。コードに手を加えるたびに、ファイルはコンパイルしなければなりません。そのとき、`_build/dev/lib/kv/ebin/kv.app`が書き出され、開くとつぎのような記述が見られます。

```
{application,kv,
             [{applications,[kernel,stdlib,elixir,logger]},
              {description,"kv"},
              {modules,['Elixir.KV','Elixir.KV.Bucket','Elixir.KV.Registry',
                        'Elixir.KV.Supervisor']},
              {registered,[]},
              {vsn,"0.1.0"}]}.
```

Erlangの構文で設定が書かれています。Erlangを知らなくても、アプリケーションの仕様であることが推測できるでしょう。アプリケーションのバージョンや定められているモジュール、さらに依存するアプリケーション(Erlangの`kernel`と`stdlib`、`elixir`自身および`logger`)などが示されています。

新たなモジュールを加えるたびに、このファイルに書き加えるのは面倒です。代わりにMixがファイルをつくり、更新してくれます。また、`mix.exs`プロジェクトファイルに`application/0`を定めることにより、カスタマイズすることもできるのです(「[The application environment](https://hexdocs.pm/elixir/Application.html#module-the-application-environment)」参照)。


# アプリケーションを起動する
`.app`ファイルが定められると、その仕様にしたがってアプリケーションを全体として起動し、終了することができます。

1. Mixはアプリケーションを自動的に起動できます。
2. Mixが起動しなかったときは、アプリケーションは開始するまで何もしません。

試しに、Mixでアプリケーションを起動してみましょう。`iex -S mix`でプロジェクトコンソールを開いてください。

アプリケーションは[`Application.start/2`](https://hexdocs.pm/elixir/Application.html#start/2)で始まり、[`Application.stop/1`](https://hexdocs.pm/elixir/Application.html#stop/1)で終えられます。デフォルトでは、プロジェクトのmix.exsが定めるアプリケーション階層の全体を、Mixが自動的に開始しています。他に依存しているアプリケーションがあれば、それらについても同様です。

```elixir
iex> Application.start(:kv)
{:error, {:already_started, :kv}}
iex> Application.stop(:kv)

00:00:00.000 [info]  Application kv exited: :stopped
:ok
iex> Application.start(:kv)
:ok
```

Mixにオプションを渡して、アプリケーションは起ち上げないこともできます。`iex -S mix run --no-start`と打ち込んでコンソールを開いてください。

`Application.start/2`でアプリケーションが開始します。`:logger`はElixirがデフォルトで起動するアプリケーションです。依存するアプリケーションを停止すると、再起動するにはそれを先に起ち上げなければなりません。

```elixir
iex> Application.start(:kv)
:ok
iex> Application.stop(:kv)

00:00:00.000 [info]  Application kv exited: :stopped
:ok
iex> Application.stop(:logger)
=INFO REPORT==== 20-Aug-2018::00:00:00.000000 ===
    application: logger
    exited: stopped
    type: temporary
:ok
iex> Application.start(:kv)
{:error, {:not_started, :logger}}
iex> Application.start(:logger)
:ok
iex> Application.start(:kv)
:ok
```

あるいは、[`Application.ensure_all_started/2`](https://hexdocs.pm/elixir/Application.html#ensure_all_started/2)で依存しているアプリケーションとともに起動できます。

```elixir
iex> Application.stop(:kv)

00:00:00.000 [info]  Application kv exited: :stopped
:ok
iex> Application.stop(:logger)
=INFO REPORT==== 20-Aug-2018::00:00:00.000000 ===
    application: logger
    exited: stopped
    type: temporary
:ok
iex> Application.ensure_all_started(:kv)
{:ok, [:logger, :kv]}
```

> `iex -S mix`は`iex -S mix run`の省略記法です。オプションを加えるときは、`run`コマンドのあとに添えなければならず、省けません。`run`コマンドと使えるオプションについては`mix help run`で確かめられます。


# アプリケーションコールバック
アプリケーションの起動について知ると、何ができるでしょうか。ひとつ挙げられるのは、`application`コールバック関数を定めることです。コールバックはアプリケーションが起ち上がるときに呼び出されます。関数は`{:ok, pid}`を返さなければなりません。`pid`はスーパーバイザープロセスの識別子です。

アプリケーションコールバックは、ふたつの手順で組み立てられます。第1に、`mix.exs`の関数`application`に`:mod`オプションで、アプリケーションコールバックモジュールを加えます。タプルの第1要素がモジュール、第2要素はアプリケーション起動時に渡される引数です。アプリケーションコールバックモジュールは、[`Application`ビヘイビア](https://hexdocs.pm/elixir/Application.html)を実装していればどれでもかまいません。

```elixir
def application do
  [
    extra_applications: [:logger],
    mod: {KV, []}
  ]
end
```

第2に、アプリケーションコールバックモジュールにコールバック関数を定めます。アプリケーションの開始時に呼び出されるのが[`start/2`](https://hexdocs.pm/elixir/Application.html#start/2)です。終了時に呼び出したいコールバックは[`stop/1`](https://hexdocs.pm/elixir/Application.html#stop/1)として加えてください。ここでは、プロジェクトにデフォルトでつくられたモジュール`KV`の`lib/kv.ex`をつぎのように書き替えます。モジュールに`use Application`を忘れずに加えてください。

プロジェクトをコンパイルしたら、`iex -S mix`でコンソールを開きます。すると、`KV.Registry`はすでに動いていることが確かめられるでしょう。

```elixir
defmodule KV do
  use Application

  def start(_type, _args) do
    KV.Supervisor.start_link(name: KV.Supervisor)
  end
end
```

```elixir
iex> KV.Registry.create(KV.Registry, "shopping")
:ok
iex> KV.Registry.lookup(KV.Registry, "shopping")
{:ok, #PID<0.134.0>}
```

`KV.Registry`のプロセスは確かに使えました。けれど、`KV.Registry.create/2`は`GenServer.cast/2`を呼び出しています。つまり、メッセージのターゲットのあるなしにかかわらず、ただちに`:ok`が返されるということです。したがって、ここまでではスーパーバイザーやサーバーが働いて、プロセスがつくられたかどうかはわかりません。けれども、`KV.Registry.lookup/2`は`GenServer.call/3`を使っています。つまり、サーバーの応答を待つのです。レスポンスが返ったということは、正しく動いていることを示します。


# プロジェクトとアプリケーション
Mixはプロジェクトとアプリケーションを区別します。`mix.exs`の内容にもとづいてつくられるのが、アプリケーションの定められたMixプロジェクトです。けれど、アプリケーションが含まれないプロジェクトもあります。

プロジェクトはMixでつくります。Mixはプロジェクトを管理するツールです。プロジェクトをコンパイルしたり、テストすることなどができます。さらに、関連するアプリケーションのコンパイルや起動もできるのです。アプリケーションというとき考えるのはOTPです。アプリケーションは、ランタイムが起動し、終了するもの全体を指します。


#### 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: スーパーバイザーとアプリケーション
- [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: 依存関係とアンブレラプロジェクト](https://dev.to/gumi/mix-otp-07-1p5k)
- [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)