はじめに
この記事では、Elixirを用いた各種設定ファイルのロード方法やTipsについて説明します。
なお、以降の説明は、以下のようなディレクトリ構成で各ファイルが配置されているという前提のもとで進めます。
config
|-setting.json
|-setting.toml
|-setting.yml
lib
|-loader.ex
JSON
まずはJSON形式のファイルロードについて考えてみます。
JSONの設定ファイルには、次のような値が格納されているとしましょう。
{
"job": {
"name": "new_job",
"step": [
{"level": 1, "desc": "Start"},
{"level": 2, "desc": "Stop"}
],
"repeat": false
}
}
このようなJSON形式の文字列をパースす場合は Poison を用いることができます。
次のように deps を追加し、Poisonによって読み込んだ文字列をオブジェクトへとパースします。
defp deps do
[{:poison, "~> 3.1"}]
end
def load_json() do
"./config/setting.json"
|> File.read!()
|> Poison.decode!()
end
上記のコードを実行すると、次の結果が得られます。
iex> Loader.load_json()
%{
"job" => %{
"name" => "new_job",
"repeat" => false,
"step" => [
%{"desc" => "Start", "level" => 1},
%{"desc" => "Stop", "level" => 2}
]
}
}
YAML
次にYAMLを使うケースについて考えてみましょう。
次のようなYAMLの設定ファイルがあるとします。
job:
name: new_job
step:
- level: 1
desc: Start
- level: 2
desc: Stop
repeat: false
このような YAML で定義されたファイルの内容を取得する場合、 YamlElixir を使うことができます。
以下のように、deps の追加と関数の定義を行いましょう。
defp deps do
[{:yaml_elixir, "~> 2.1"}]
end
def load_yaml() do
"./config/setting.yml"
|> File.read!()
|> YamlElixir.read_from_string()
end
このコードを実行すると、次のように YAML がパースされたオブジェクトが得られます。
iex> Loader.load_yaml()
%{
"job" => %{
"name" => "new_job",
"repeat" => false,
"step" => [
%{"desc" => "Start", "level" => 1},
%{"desc" => "Stop", "level" => 2}
]
}
}
また、YamlElixirは read_from_file/1
や read_from_file!/1
といった関数も提供しているので、次のようにファイルの読み込みとオブジェクトへのパースをまとめて行うこともできます。
def load_yaml_from_file() do
"./config/setting.yml"
|> YamlElixir.read_from_file!()
end
TOML
TOML は設定を定義するためのファイルフォーマットであり、ハッシュテーブルへと明確にマップすることが可能であるという特徴を持ちます。これまで登場したような設定は、以下のように定義されます。
[job]
name = "new_job"
repeat = false
[[job.step]]
level = 1
desc = "Start"
[[job.step]]
level = 2
desc = "Stop"
(ここでは TOML 自体の説明は割愛しますが、 設定ファイル記述言語 TOML のQiita記事が詳しく説明していますので参考にしてみてください)
上記のようなTOMLの設定をロードするには、 Toml というElixirライブラリを利用することができます。
次のように、 deps の追加、関数の定義をしましょう。
defp deps do
[{:toml, "~> 0.3"}]
end
def load_toml() do
"./config/setting.toml"
|> File.read!()
|> Toml.decode!()
end
このコードにより、次のようにTOMLの設定値を取り出すことができます。
iex(10)> Loader.load_toml()
%{
"job" => %{
"name" => "new_job",
"repeat" => false,
"step" => [
%{"desc" => "Start", "level" => 1},
%{"desc" => "Stop", "level" => 2}
]
}
}
また、 Toml はファイルから直接オブジェクトを取り出すための関数 decode_file!
も提供しているので、次のように書くこともできます。
def load_toml_from_file() do
"./config/setting.toml"
|> Toml.decode_file!()
end
ビルド時の設定ロード
これまで紹介した方法は、コードが実行される際にファイルの内容を読み出してオブジェクトへとパースするという方法を取ってきました。しかし、この方法は実行毎にファイルIOが発生するため、頻繁に実行される処理などでは好ましいものではありません。また、設定ファイルの内容を反映させた上でDistilleryなどで配布したいうというケースもあるでしょう。
このようなケースでは、ビルドの時点でファイルから設定を読み込んでおき、モジュールの属性(attribute)として定義しておくという方法を取ることができます。次のコードを見てみましょう。
defmodule Loader do
@json_config "./config/setting.json" |> File.read!() |> Poison.decode!()
def json_config(), do: @json_config
end
このコードをビルドすることで、 setting.json
から読み出した内容を @json_config
へと反映させます。そのため、 Loader.json_config
を実行すると、ファイルIOを発生させることなく特定の設定ファイルの内容を取得することができるようになります。
dev環境のみ動的読み込み
前述した方法では、ビルド時に設定内容をロードしておくことでファイルアクセス無しに設定内容を取得することができました。しかし、これは逆に言うと、ビルドをし直さない限り設定ファイルの内容が反映されないということを意味します。これは、開発時のように設定を頻繁に変更してトライアンドエラーを繰り返すようなケースでは都合が悪く、ビルド無しで毎回設定ファイルを読み込んでくれた方が効率が良いでしょう。
そこで、以下のようにすると、dev環境でのみ設定ファイルを毎回ロードさせることができます。
defmodule Loader do
if Mix.env == :dev do
def json_config() do
"./config/setting.json" |> File.read!() |> Poison.decode!()
end
else
@json_config "./config/setting.json" |> File.read!() |> Poison.decode!()
def json_config(), do: @json_config
end
end
見るとわかるように、 Mix.env
によって得られる値が :dev
であるかどうかによってビルドされるコードが決定されます。そのためこのケースでは、dev でのみ設定ファイルを都度読み込む処理となります。
(他にも Dependency Injection を利用するという方法を取ることもできます)