The OTP design principles define how to structure Erlang code in terms of processes, modules, and directories.
A basic concept in Erlang/OTP is the supervision tree. This is a process structuring model based on the idea of workers and supervisors:
In the following figure, square boxes represents supervisors and circles represent workers:
In a supervision tree, many of the processes have similar structures, they follow similar patterns. For example, the supervisors are similar in structure. The only difference between them is which child processes they supervise. Many of the workers are servers in a server-client relation, finite-state machines, or event handlers such as error loggers.
Behaviours are formalizations of these common patterns. The idea is to divide the code for a process in a generic part (a behaviour module) and a specific part (a callback module).
The behaviour module is part of Erlang/OTP. To implement a process such as a supervisor, the user only has to implement the callback module which is to export a pre-defined set of functions, the callback functions.
The following example illustrate how code can be divided into a
generic and a specific part. Consider the following code (written in
plain Erlang) for a simple server, which keeps track of a number
of "channels". Other processes can allocate and free the channels
by calling the functions
-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.
The code for the server can be rewritten into a generic part
-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.
And a callback module
-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
Notice the following:
In
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.
Code written without using behaviours can be more efficient, but the increased efficiency is at the expense of generality. The ability to manage all applications in the system in a consistent manner is important.
Using behaviours also makes it easier to read and understand code written by other programmers. Improvised programming structures, while possibly more efficient, are always more difficult to understand.
The
The standard Erlang/OTP behaviours are:
For implementing the server of a client-server relation
For implementing state machines
For implementing event handling functionality
For implementing a supervisor in a supervision tree
The compiler understands the module attribute
-module(chs3).
-behaviour(gen_server).
...
3> c(chs3).
./chs3.erl:10: Warning: undefined call-back function handle_call/3
{ok,chs3}
Erlang/OTP comes with a number of components, each implementing some specific functionality. Components are with Erlang/OTP terminology called applications. Examples of Erlang/OTP applications are Mnesia, which has everything needed for programming database services, and Debugger, which is used to debug Erlang programs. The minimal system based on Erlang/OTP consists of the following two applications:
The application concept applies both to program structure (processes) and directory structure (modules).
The simplest applications do not have any processes, but consist of a collection of functional modules. Such an application is called a library application. An example of a library application is STDLIB.
An application with processes is easiest implemented as a supervision tree using the standard behaviours.
How to program applications is described in
A release is a complete system made out from a subset of Erlang/OTP applications and a set of user-specific applications.
How to program releases is described in
How to install a release in a target environment is described in the section about target systems in Section 2 System Principles.
Release handling is upgrading and downgrading between
different versions of a release, in a (possibly) running system.
How to do this is described in