OTPデザイン原則集は、プロセス、モジュール、ディレクトリといった用語セットを使って、実用的なErlangコードを組み立てるための原則集です。
Erlang/OTPの基本的なコンセプトが、 監視ツリー (supervision tree)です。これは、 ワーカー 、 スーパバイザ といったアイディアを元にした、プロセスの構造化モデルです。
監視ツリー
上記の図は、四角い箱がスーパーバイザーを、円がワーカーを示しています。
監視ツリーでは、多くのプロセスが同じようなパターンに従った、似た構造をしています。例えば、スーパバイザははどれも似たような構造をしています。それらの違いはスーパバイザが監視する子供のプロセスだけです。そのため、多くのワーカーがサーバ=クライアントの関係におけるサーバであって、有限状態機械(FSM)を構成していて、エラーロガーのイベントハンドラをしています。
ビヘイビア というのは、これらの共通のパターンを形式化したものです。ビヘイビアの考え方は、プロセスに関する汎用的なコード(ビヘイビアモジュール)と、特定の部分(コールバックモジュール)に分割するというものです。
ビヘイビアモジュールはErlang/OTPの一部です。スーパバイザのようなプロセスを実装するためには、ユーザは事前定義の関数と コールバック関数 をエクスポートしている、コールバックのモジュールだけを定義すればよくなります。
どのようにして、汎用的なコードと特化したコードに分割されるのか、というサンプルを紹介します。プレーンなErlangで記述された、シンプルなサーバを実装した以下のコードについて見てみます。このサーバーは”channels”という数値を監視し続けます。他のプロセスはalloc/0, free/1をそれぞれ呼ぶことにより、チャンネルを割り当てたり、開放したりすることができます。
-module(ch1).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0]).
start() ->
spawn(ch1, init, []).
alloc() ->
ch1 ! {self(), alloc},
receive
{ch1, Res} ->
Res
end.
free(Ch) ->
ch1 ! {free, Ch},
ok.
init() ->
register(ch1, self()),
Chs = channels(),
loop(Chs).
loop(Chs) ->
receive
{From, alloc} ->
{Ch, Chs2} = alloc(Chs),
From ! {ch1, Ch},
loop(Chs2);
{free, Ch} ->
Chs2 = free(Ch, Chs),
loop(Chs2)
end.
以下のサーバのコードは、汎用部分だけに書き直した server.erl になります:
-module(server).
-export([start/1]).
-export([call/2, cast/2]).
-export([init/1]).
start(Mod) ->
spawn(server, init, [Mod]).
call(Name, Req) ->
Name ! {call, self(), Req},
receive
{Name, Res} ->
Res
end.
cast(Name, Req) ->
Name ! {cast, Req},
ok.
init(Mod) ->
register(Mod, self()),
State = Mod:init(),
loop(Mod, State).
loop(Mod, State) ->
receive
{call, From, Req} ->
{Res, State2} = Mod:handle_call(Req, State),
From ! {Mod, Res},
loop(Mod, State2);
{cast, Req} ->
State2 = Mod:handle_cast(Req, State),
loop(Mod, State2)
end.
以下のコードはコールバックモジュールの ch2.erl になります:
-module(ch2).
-export([start/0]).
-export([alloc/0, free/1]).
-export([init/0, handle_call/2, handle_cast/2]).
start() ->
server:start(ch2).
alloc() ->
server:call(ch2, alloc).
free(Ch) ->
server:cast(ch2, {free, Ch}).
init() ->
channels().
handle_call(alloc, Chs) ->
alloc(Chs). % => {Ch,Chs2}
handle_cast({free, Ch}, Chs) ->
free(Ch, Chs). % => Chs2
以上から、以下のような気づきが得られます:
上記の ch1.erl と ch2.erl では、 channels/0, alloc/1, free/2 の実装はサンプルに関連していないため、意図的に省略しています。完全を期すために必要な、これらの関数の実装方法の一つは下記のようになります。これはサンプル専用の実装で、現実のアプリケーションを実装するためには、チャンネルの割り当てを使い切ってしまうという状況など、さまざまな例外状況に対処する必要があります。
channels() ->
{_Allocated = [], _Free = lists:seq(1,100)}.
alloc({Allocated, [H|T] = _Free}) ->
{H, {[H|Allocated], T}}.
free(Ch, {Alloc, Free} = Channels) ->
case lists:member(Ch, Alloc) of
true ->
{lists:delete(Ch, Alloc), [Ch|Free]};
false ->
Channels
end.
ビヘイビアを利用せずに書かれたコードは、処理速度の面では効率的かもしれませんが、効率を改善するために一般性を犠牲にすることになります。一貫した方法で、システム内のすべてのアプリケーションを管理できるようにすることは非常に大切です。
ビヘイビアを利用すると、他のプログラマが書いたコードを読んで理解するのが容易になります。その場限りのアドホックなプログラム構造では、効率が良い場合もあるかもしれませんが、たいていの場合は理解が難しくなります。
サーバのモジュールは、Erlang/OTPのビヘイビアのgen_serverに対応します。gen_serverを使うことで大幅に簡略化されます。
標準的なErlang/OTPのビヘイビアには以下のものがあります:
クライアント=サーバの関係における、サーバを実装しています
有限状態機械を実装しています
イベントハンドリングの機能を実装しています
監視ツリーのスーパバイザを実装しています
コンパイラはモジュール属性の -behaviour(Behaviour) という行を理解します。もしもコールバック関数が足りない場合には、以下のように警告を出します:
-module(chs3).
-behaviour(gen_server).
...
3> c(chs3).
./chs3.erl:10: Warning: undefined call-back function handle_call/3
{ok,chs3}
Erlang/OTPはいくつものコンポーネントを伴います。それぞれのコンポーネントは、特定の機能を実装しています。Erlang/OTPの用語では、コンポーネントを アプリケーション と呼びます。Erlang/OTPアプリケーションのサンプルはMnesiaです。これはデータベースサービスをプログラムするのに必要なすべての機能を持っています。また、Debuggerもアプリケーションです。これはErlangのプログラムのデバッグに使用されます。最小のErlang/OTPベースのシステムは、KernelアプリケーションとSTDLIBアプリケーションを含みます。
アプリケーションの考え方は、プログラムの構造(プロセス)と、ディレクトリ構造(モジュール)の両方に適用されます。
もっともシンプルな種類のアプリケーションはプロセスを一つも含みませんが、いくつかの機能を実装したモジュールで構成されます。このようなアプリケーションは、 ライブラリアプリケーション と呼ばれます。ライブラリアプリケーションのサンプルとしてはSTDLIBがあります。
プロセスを含むアプリケーションは、標準的なビヘイビアを使用して、監視ツリーとして実装するのが最も簡単です。
どのようにアプリケーションをプログラムしていくのか、ということについては、 アプリケーション の章で説明していきます。
リリース はErlang/OTPアプリケーションのサブセットと、ユーザ定義のアプリケーションから作成されます。
どのようにリリースをプログラムするのか、ということについては リリース の章で説明していきます。
対象となる環境にどのようにリリースをインストールするのか、ということについては、システム原則の中のターゲットシステムの章で説明していきます。
リリースのハンドリング というのは、実行中のシステムにおいて、異なるバージョンのリリースの間で、アップグレードしたり、ダウングレードしたりすることです。どのようにこれを行うのか、ということに関しては、 release handling の章で説明していきます。
Copyright (c) 1991-2009 Ericsson AB