Elixir: child specの機能
Tags: elixir, webdev, tutorial, programming
Published: 2018-10-30 01:58:36

---
title: Elixir: child specの機能
published: true
description: スーパーバイザーから起動する子の仕様(child spec)を指定する方法は、Elixir v1.5から改められました。child specの機能とどのように使えばよいのかについてご説明します。
tags: #elixir, #webdev, #tutorial, #programming
---


本稿は「[Elixir 1.5 の合理化された child spec](https://qiita.com/melpon/items/bae4105c68f8da50e3c7)」をもとに加筆・補正し、文章を整えました。

スーパーバイザーから起動する子の仕様(child spec)を指定する方法は、Elixir v1.5から改められました。child specの機能とどのように使えばよいのかについてご説明します。


# Elixir v1.5以降のchild specの定め方
Elixir v1.5より前は、スーパーバイザーを起動するときのchild specは、つぎのように書いていました。スーパーバイザーは`Supervisor.Spec.supervisr/2`、ワーカーなら`Supervisor.Spec.worker/2`でそれぞれ子の仕様を定めたわけです。

```elixir
children = [
  Supervisor.Spec.supervisor(MyApp.Repo, []),
  Supervisor.Spec.worker(MyApp.MyServer, [:foo]),
]

Supervisor.start_link(children, strategy: :one_for_one)
```

これでは間違って記述してしまうかもしれません。スーパーバイザーの`MyApp.Repo`に`Supervisor.Spec.worker/2`を呼び出したり、ワーカーのはずの`MyApp.MyServer`を`Supervisor.Spec.supervisor/2`で起動するというミスが起こりやすいです。

どちらで使うかは、モジュールごとに予め決まっているのが通常でしょう。子を起動するときに考えるものではありません。

子のタイプを指定する`:type`オプションだけでなく、`:start`や`:restart`についてもおおむね同じです。多くの場合はモジュールを書くときに定めておくべきで、子を起動する段階で決めることではありません。つまり、child specはモジュールの構成に含まれるということです。

Elixir v1.5からこの考え方が採り入れられました。モジュールに`child_spec/1`という関数で子の仕様は決めておきます。そのため、子の起動はつぎのように書くだけで済むのです。

```elixir
children = [
  MyApp.Repo,
  {MyApp.MyServer, [:foo]},
]

Supervisor.start_link(children, strategy: :one_for_one)
```

スーパーバイザーが動き出すと、リストに納められた子のすべてに対して、各モジュールの`child_spec/1`関数が呼び出されます。この例では、`MyApp.Repo.child_spec([])`と`MyApp.MyServer.child_spec([:foo])`からchild specが得られ、それぞれのプロセスが起動するのです。指定したモジュールが`child_spec/1`を実装していない場合は、エラーになります。

このようにchild specが合理化されたため、[`Supervisor.Spec`](https://hexdocs.pm/elixir/Supervisor.Spec.html)は非推奨となりました。子の起動時に`child spec`をカスタマイズしたいなら、 [`Supervisor.child_spec/2`](https://hexdocs.pm/elixir/Supervisor.html#child_spec/2)を使うとよいでしょう。

```elixir
Supervisor.child_spec({MyApp.MyServer, [:foo]}, shutdown: 10_000)
```

モジュールが`child_spec/1`を実装していない場合には、マップ形式で`child spec`が定められます。タプル形式より省略ができるので楽になるでしょう。

```elixir
# MyApp.MyWorker.start_link([:foo]) でプロセスを起動する
%{
  id:       MyApp.MyWorker,                          # 必須
  start:    {MyApp.MyWorker, :start_link, [[:foo]]}, # 必須
  restart:  :permanent,       # 省略可
  shutdown: 5_000,            # 省略可
  type:     :worker,          # 省略可
  modules:  [MyApp.MyWorker], # 省略可
}
```

# `use GenServer`は`child_spec/1`を実装する
もっとも、毎回`child_spec/1`を定義するのは面倒です。そこで、Elixir v1.5からは、`use GenServer`で自動的に`child_spec/1`が実装されます。

```elixir
defmodule MyServer do
  use GenServer
end

IO.inspect MyServer.child_spec([:foo])
# %{
#   id: MyServer,
#   restart: :permanent,
#   shutdown: 5000,
#   start: {MyServer, :start_link, [[:foo]]},
#   type: :worker,
# }
```

`use GenServer`は必ず`type: :worker`に定めます。`child_spec/1`の定義をカスタマイズしたい場合は、`use GenServer`に引数を追加するだけです。

```elixir
defmodule MyServer do
  use GenServer, restart: :temporary, shutdown: 10_000
end

IO.inspect MyServer.child_spec([:foo])
# %{
#   id: MyServer,
#   restart: :temporary,
#   shutdown: 10_000,
#   start: {MyServer, :start_link, [[:foo]]},
#   type: :worker,
# }
```

`child_spec/1`関数は、GenServerだけでなく、`Supervisor`を使ったときも自動的に定められます。`use Supervisor`の場合は必ず`type: :supervisor`です。モジュールベースのスーパーバイザーを定義するときには便利でしょう。

```elixir
defmodule MySupervisor do
  use Supervisor
end

IO.inspect MySupervisor.child_spec([:foo])
# %{
#   id: MySupervisor,
#   restart: :permanent,
#   start: {MySupervisor, :start_link, [[:foo]]},
#   type: :supervisor,
# }
```


# 既存のモジュールも`child_spec/1`を実装する
既存の`Agent`や`Registry`、あるいは`Task`といったライブラリも`child_spec/1`を実装します。つまり、つぎのように書けるということです。わざわざ`Supervisor.Spec.worker/2`を呼ばなくて済みます。

```elixir
children = [
  {Registry, keys: :unique, name: MyApp.Registry},
  {Agent, fn -> :ok end},
]
Supervisor.start_link(children, strategy: :one_for_one)
```


# まとめ
Elixir v1.5でchild specが合理化され、モジュール側に`child_spec/1`で仕様が定められます。

必要な場合には、モジュールに`child_spec/1`を定義しておくのがいいでしょう。`use GenServer`など`child_spec/1`が自動的に定義されるときは、適切に引数を渡すだけで構いません。

こうして、適切な場所にchild specを定義すれば、指定の間違いが避けられます。有効に活用していきましょう。