OTPアプリケーション
この記事は英語の原文を日本語に翻訳したものです。原文: https://adoptingerlang.org/docs/development/otp_applications/
翻訳元: adoptingerlang/adoptingerlang 2025-12-18(コミット 899008f)
Erlang/OTPのリリースに含めるコンポーネントはすべてOTPアプリケーションでなければならないため、それが何であり、どう動くのかを理解しておくことは大いに役立ちます。 本章では、OTPアプリケーションの基本的な構造と、それが自分のプロジェクトにとって何を意味するのかを見ていきます。
プロジェクト構造
まずはRebar3のテンプレートを使うところから始めましょう。Erlang/OTPが期待するディレクトリ構造にきちんと従った新規プロジェクトを作成できるからです。 どのようなテンプレートが使えるか見てみます。
$ rebar3 new
app (built-in): Complete OTP Application structure.
cmake (built-in): Standalone Makefile for building C/C++ in c_src
escript (built-in): Complete escriptized application structure
lib (built-in): Complete OTP Library application (no processes) structure
plugin (built-in): Rebar3 plugin project structure
release (built-in): OTP Release structure for executable programs
umbrella (built-in): OTP structure for executable programs
(alias of 'release' template)
用途ごとにどれを使うべきかをまとめると、次のようになります。
| プロジェクトの種類 | 使うテンプレート | 備考 |
|---|---|---|
| スクリプトやコマンドラインツール | escript | 利用者側にErlangのインストールが必要 |
| ライブラリ(モジュールの集まり) | lib | 依存関係として使える |
| ライブラリ(ステートフルなプロセスを持つ) | app | 依存関係として使える |
| 完全な実行可能プログラム | umbrella または app | 完全なリリースにでき、推奨されるデプロイ手段 |
| 複数のライブラリの集まり | umbrella | gitの依存関係としては使えないが、個々のアプリはhexで公開できる |
| Rebar3の拡張 | plugin | |
| Cコードのコンパイル | cmake | CやC++を移植性高くコンパイルする方法として"pc"プラグインも参照 |
特定のテンプレートの詳細は rebar3 new help <template> を呼び出せば確認できます。
例を見てみましょう。
$ rebar3 new help lib
lib:
built-in template
Description: Complete OTP Library application (no processes) structure
Variables:
name="mylib" (Name of the OTP library application)
desc="An OTP library" (Short description of the app)
date="2019-03-15"
datetime="2019-03-15T19:52:31+00:00"
author_name="Fred Hebert"
author_email="mononcqc@ferd.ca"
copyright_year="2019"
apps_dir="apps" (Directory where applications will be created if needed)
これらの値はコマンドラインで自由に変更できますが、ここに挙げたものがデフォルトの変数です。 実際に自分で値を指定して何が生成されるか見てみましょう。
$ rebar3 new lib mylib desc="Checking out OTP libs"
===> Writing mylib/src/mylib.erl
===> Writing mylib/src/mylib.app.src
===> Writing mylib/rebar.config
===> Writing mylib/.gitignore
===> Writing mylib/LICENSE
===> Writing mylib/README.md
mylib ディレクトリに移動し、さっそく rebar3 compile を呼び出します。
$ rebar3 compile
===> Verifying dependencies...
===> Compiling mylib
ディレクトリ構造を見てみると、プロジェクトの中身は次のようになっているはずです。
mylib/
├─ _build/
│ └─ default/
│ └─ lib/
│ └─ mylib/
│ ├─ ebin/
│ │ ├─ mylib.app
│ │ └─ mylib.beam
│ ├─ include/
│ ├─ priv/
│ └─ src/
│ └─ ...
├─ .gitignore
├─ LICENSE
├─ README.md
├─ rebar.config
├─ rebar.lock
└─ src/
├─ mylib.app.src
└─ mylib.erl
_build/ ディレクトリはビルドツールの作業場であり、必要な成果物を好きなだけしまっておける場所です。
中身を手で触る必要は決してなく、消したくなったらいつでも消してかまいません。
とはいえ、このディレクトリを見るとRebar3がどう物事を構造化しているかがわかるので、興味深い場所でもあります。
_build/ の中身はすべてプロファイルごとに分かれています。
プロファイルを使うと、default、test、prod のどれでビルドするかによって、異なる依存関係やコンパイラオプションでビルドを切り替えられます。
実際には好きなだけプロファイルを定義し、それらを組み合わせることもできます。
仕組みの詳細はRebar3のドキュメントで説明されています。
各プロファイルの中では、lib/ ディレクトリに、標準ディストリビューションのライブラリ以外でプロジェクトが使うすべてのOTPアプリケーションが入っています。
そこには先ほどの mylib ライブラリも複製されていますが、プロジェクトルート直下の構造とは少し異なります。
- コンパイル済みの
.erlファイルはebin/ディレクトリに移され、拡張子は.beamになる mylib.appファイルが生成される(元のソースはmylib.app.srcだった)include/とpriv/への2つのシンボリックリンクが追加される。これらはプロジェクトルートに同名のディレクトリがあれば、それを指す。include/はヘッダファイル(.hrl)用、priv/は本番環境でコピーして使える必要のあるファイル用のディレクトリである- プロジェクトルートにあったそれ以外のファイルはすべて破棄される
依存関係があれば(Dependencies章を参照)、それも同じく _build/<profile>/lib/ ディレクトリに置かれます。
基本的には _build/ ディレクトリはまるごと無視し、バージョン管理には含めないようにします。
.gitignore ファイルを見れば、_build/ が自動的に無視される設定になっていることがわかります。
Rebar3はデフォルトでライセンスを選んでくれます(オープンソースで公開するつもりなら、ライセンスは必ず選ぶべきだからです)。選ばれるのはErlang自体が採用しているApache 2.0ライセンスで、必要に応じて自由に差し替えてかまいません。
また、Rebar3はREADMEファイルも用意してくれるので、これも関連する内容で書き直し、更新するとよいでしょう。サボらずドキュメントを書きましょう。
続いて興味深い2つのファイル、rebar.config と rebar.lock があります。
ロックファイルは、プロジェクト内の各依存関係がどのバージョンを使っていたかをRebar3が記録するためのものであり、そのためバージョン管理に含めておくべきです。
詳しくはDependencies章を参照してください。
rebar.config ファイルは、Rebar3が連携するあらゆるErlangツールのオプションを公開する、完全に宣言的な設定ファイルです。
指定できる値はすべて公式ドキュメントで説明されていますが、デフォルトの状態ではほとんど空です。
実際、デフォルト値のままで依存関係もないなら、このファイル自体を削除してしまってもかまいません。
プロジェクトがOTPアプリケーションとしての構造さえ持っていれば、あとはRebar3がやるべきことを判断してくれます。
では、そのために求められる標準的な構造を見ていきましょう。
ライブラリをアプリにする条件
他のフレームワークと同じく、OTPの流儀に従うために満たすべき条件がいくつかあります。
察しがついているかもしれませんが、OTPのようなフレームワークにおける基本的な要件の一つがディレクトリ構造です。
コンパイル後のライブラリに <appname>.app ファイルを含む ebin/ ディレクトリさえあれば、Erlangランタイムシステムはモジュールを読み込んでコードを実行できます。
この基本要件が、Erlangエコシステム全体のプロジェクト構造を方向づけています。
ビルドされた .app ファイルがどのようなものか見てみましょう。
$ cat _build/default/lib/mylib/ebin/mylib.app
{application, mylib, [
{description, "Checking out OTP libs"},
{vsn, "0.1.0"}, % バージョン番号(文字列)
{registered, []}, % 登録するプロセス名があれば
{applications, [ % 実行時に依存するOTPアプリケーション名の一覧。
kernel, stdlib % kernelとstdlibは必ず必要
]},
{env, []}, % デフォルトの設定値({Key, Val}のペア)
{modules, [mylib]}, % アプリケーションに含まれるモジュールの一覧
%% 以下の内容は任意で、パッケージ公開時のみ使う
{licenses, ["Apache 2.0"]},
{links, []} % 関連するURL
]}.
これは要するに、アプリケーションに関するすべてを記述したメタデータファイルです。
中身がわかるように注釈を付けておいたので、ぜひ確認してください。
この中の多くの項目は手で書くのが面倒なので、ソースファイル(src/mylib.app.src)を見てみると、Rebar3のテンプレートを適用した時点でほとんどのフィールドがあらかじめ埋められていることがわかります。
modules が空になっていることにも気づくかもしれません。これは意図的なもので、コードをコンパイルするときにRebar3がこの一覧を埋めてくれます。
その中でも群を抜いて重要で、常に最新の状態に保つべきフィールドが applications タプルです。
これによってErlangのライブラリは、OTPアプリケーションを動かすために起動すべき順序を把握でき、ビルドツールも利用可能なすべてのOTPアプリケーション間の依存関係グラフを構築して、リリースをビルドする際にどれを残しどれを取り除くべきかを判断できます。
もう一つ、見落としがちな点があります。今ここにあるのはライブラリであり、実行するプロセスを持たないにもかかわらず、設定値を定義する仕組みは用意されており(詳しくはまだ執筆されていないConfiguration章で扱います)、依存関係も尊重されなければならないという点です。 たとえば、このライブラリ自体はステートレスでも、ステートフルなHTTPクライアントを使っている場合がありえます。 その場合、Erlang VMはいつ自分のコードを呼び出しても安全かを把握しておく必要があります。
ここでは、ステートレスなアプリケーションとステートフルなアプリケーションの違いがどこにあるのかに絞って見ていきましょう。
実行可能アプリケーションの条件
実行可能アプリケーションを作るために、Rebar3のappテンプレートを使い、ステートレスなアプリケーションとの違いを見ていきます。
さっそくコマンドラインツールを使って、次のコマンドを実行してみましょう。
$ rebar3 new app myapp
===> Writing myapp/src/myapp_app.erl
===> Writing myapp/src/myapp_sup.erl
===> Writing myapp/src/myapp.app.src
===> Writing myapp/rebar.config
===> Writing myapp/.gitignore
===> Writing myapp/LICENSE
===> Writing myapp/README.md
$ cd myapp
よく見ると、<appname>.erlの代わりに2つのモジュール、<appname>_app.erlと<appname>_sup.erlができていることがわかります。
これらはすぐ後で詳しく見ていきますが、まずはアプリケーションのトップレベルのメタデータファイルであるmyapp.app.srcファイルに注目しましょう。
$ cat src/myapp.app.src
{application, myapp,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {myapp_app, []}}, % ここが新しい点!
{applications, [kernel, stdlib]},
{env,[]},
{modules, []},
{licenses, ["Apache 2.0"]},
{links, []}
]}.
ここで新しく加わっているのは {mod, {<appname>_app, []}} タプルだけです。
このタプルは、特定の引数([])とともに呼び出せる特別なモジュール(<appname>_app)を指定します。
呼び出されると、このモジュールはsupervisorツリーのプロセス識別子(pid)を返すことが期待されます。
myapp_app モジュールの中身を見てみると、これらのコールバックがどうなっているかがわかります。
%%%-------------------------------------------------------------------
%% @doc myappの公開API
%% @end
%%%-------------------------------------------------------------------
-module(myapp_app).
-behaviour(application).
%% アプリケーションのコールバック
-export([start/2, stop/1]).
%%====================================================================
%% API
%%====================================================================
start(_StartType, _StartArgs) ->
myapp_sup:start_link().
stop(_State) ->
ok.
start/2 コールバックは、Erlangランタイムシステムによってアプリケーションが起動されるときに呼び出されます。
このとき、.appファイルのapplicationsタプルで定義された依存関係はすべてすでに起動済みです。
ここで一度きりの初期化処理を行えます。
テンプレートのアプリケーションでは、アプリケーションのルートのsupervisorを起動する処理だけが行われています。
stop/1 コールバックは、誰かがどこかでOTPアプリケーションのシャットダウンを決めた後、supervisorツリー全体が停止し終わった後に呼び出されます。
つまるところ、appファイルにこのmodの行がわずかに加わっていることと、supervisor構造が存在することこそが、実行可能アプリケーションとライブラリアプリケーションを分ける違いなのです。
supervisorツリーを気にせず、他の多くの言語のようにmain()関数さえあれば動き出せればよいという場合は、escriptがよい選択肢になるかもしれません。
escriptはErlang仮想マシンをラップする特殊なCプログラムです。ラップする際に、仮想マシンのルートErlangプロセスに自分のコードを呼び出させることで、main()という発想をリリース構造の上に後付けする小さな仕組みも組み込まれています。
結果として、リリースやOTPアプリケーション、supervisorツリーを気にすることなく、インタプリタのようにコードを実行できます。escriptについて詳しくはErlangの公式ドキュメントを参照してください。Rebar3にも複雑なescriptバンドルを作るコマンドが用意されています。
ここまでで、Erlang/OTPのプロジェクト構造にまつわる大半の奇妙な点と、この謎めいた「OTPアプリケーション」にまつわることはひととおり理解できたはずです。 次の章からはsupervisorツリーを少し掘り下げ、ステートフルな実行可能アプリケーションをどう組み立てるかを見ていきます。