> ## 8 Writing an Application

> This chapter describes and examplifies the following tasks, which all make up the larger task of writing an Erlang application:

> - how to structure an application
> - how to create a supervision tree
> - how to use the common behaviours
> - how to install event handlers
> - how to configure an application
> - how to write an application specification
> - how to test an application
> - how to write a distributed application.

> ### 8.1 Structuring the Application

> Every application has an application master process that monitors the behaviour of the entire application. It starts the application by calling the start function specified in the application specification. This start function is assumed to start one process that is the main process of the application. Normally, this process is a supervisor, but it could also be a supervisor bridge.

> Most applications are structured as a supervision tree, where the main process is the root supervisor of the application, and all other processes in the application are located somewhere under this supervisor.

> To illustrate this point, suppose that we want to build an application named `hlr`. We want this application to contain the `vshlr` server introduced in Client-Server Principles, and a special alarm event handler. This simple application will then have one supervisor and two worker as shown in Illustration of HLR Application.

> Illustration of HLR Application

> ### 8.2 Designing the Processes

> Each application has an application callback module, with behaviour `application`. This module is called when the application is started, and when it has stopped. The `start` function in this module starts the topmost supervisor for the application.

> All worker processes in the application should be written using the standard behaviours, such as `gen_server`, `gen_fsm` or `gen_event`. Alternatively, they should be special processes written with `sys` and `proc_lib`. There are two main reasons for this:

> - we want to be able to change code on all processes
> - we want a fault tolerant system.

> Simple, temporary processes can be written as normal Erlang processes without using a standard behaviour. However, these processes must be started with `proc_lib:spawn_link` instead of the BIFs `spawn` or `spawn_link`. However, it will not be possible to change code in these processes.

> | Always use `spawn_link`, and never `spawn`. You might otherwise loose track of the process and produce a zombie process.

> The next example illustrates the following scenario:

> - we want a special alarm event handler installed during the lifetime of the `hlr` application
> - this event handler is called `hlr_alarm_h` and writes each alarm to a special file
> - when starting `hlr`, we want to install this event handler in the already existing alarm handler.

> To implement this, we place the call `gen_event:add_handler(alarm_handler, hlr_alarm_h, FileName)` in the initialisation function of the application.

> The application callback module for `hlr` will then look as follows:

>     -module(hlr).
>     -vsn(1).
>     -behaviour(application).

>     %% External exports
>     -export([start/2, stop/1]).

>     %%%-----------------------------------------------------------------
>     %%% This module implements the application HLR.
>     %%%-----------------------------------------------------------------
>     start(_, _) ->
>         case hlr_sup:start() of
>             {ok, Pid} ->
>                 gen_event:add_handler(alarm_handler, hlr_alarm_h, []),
>                 {ok, Pid, []};
>             Error -> Error
>         end.

>     stop(_State) ->
>         gen_event:delete_handler(alarm_handler, hlr_alarm_h).

> The supervisor will look as follows:

>     -module(hlr_sup).
>     -vsn(1).
>     -behaviour(supervisor).

>     %% External exports
>     -export([start/0]).

>     %% Internal exports
>     -export([init/1]).

>     %%%-----------------------------------------------------------------
>     %%% This module implements a supervisor for the HLR application.
>     %%%-----------------------------------------------------------------
>     start() ->
>         supervisor:start_link({local, hlr_sup}, hlr_sup, []).

>     init([]) ->
>         SupFlags = {one_for_one, 4, 3600},
>         Vshlr = {xx2, {vshlr_2, start_link, []},
>                  permanent, 2000, worker, [vshlr_2]},
>         {ok, {SupFlags, [Vshlr]}}.

> #### 8.2.1 Configuring the Application

> In this section, the `hlr` alarm event handler is used as an example of how to configure an application. This handler is an instance of the `gen_event` behaviour. Its purpose is to write some alarms to a specified file. The event handler should be configured to write to the filename specified in the alarm output.

> The file `hlr_alarms.cnf` contains a list of all alarms which should be logged. This file looks as follows:

>     hlr_almost_full.
>     hlr_inconsistent.

> This file is stored in the private directory `priv` of the application. It is found by calling `code:priv_dir(hlr)`.

> Each application has an associated environment where configuration parameters are defined. This environment is specified in the application specification, and is overridden by the system configuration file. The value of the parameter `hlr_alarm_file` is a string which specifies the file which logs all alarms. The value of the parameter is found with the call `application:get_env(hlr, hlr_alarm_file)`. The `hlr_alarm_h` looks as follows:

>     -module(hlr_alarm_h).
>     -vsn(1).
>     -behaviour(gen_event).

>     -export([init/1, handle_event/2, handle_info/2, terminate/2]).

>     -record(state, {fd, alarms}).
>     %%-----------------------------------------------------------------
>     %% Callback functions from gen_event
>     %%-----------------------------------------------------------------
>     init(_) ->
>         CnfFile = filename:join(code:priv_dir(hlr), "hlr_alarm.cnf"),
>         Alarms = case file:consult(CnfFile) of
>                      {ok, List} -> List;
>                      _ -> []
>                  end,
>         case application:get_env(hlr, hlr_alarm_file) of
>             {ok, File} ->
>                 {ok, Fd} = file:open(File, write),
>                 {ok, #state{fd = Fd, alarms = Alarms}};
>             undefined ->
>                 {error, {no_config, hlr_alarm_file}}
>         end.

>     handle_event({set_alarm, Alarm}, State)->
>         case is_hlr_alarm(Alarm, State) of
>             true -> io:format(State#state.fd, "set alarm: ~p~n", [Alarm]);
>             false -> ok
>         end,
>         {ok, State};
>     handle_event({clear_alarm, AlarmId}, State)->
>         case is_hlr_alarm(Alarm, State) of
>             true -> io:format(State#state.fd, "clear alarm: ~p~n", [AlarmId]);
>             false -> ok
>         end,
>         {ok, State}.

>     handle_info(_, State) -> {ok, State}.

>     terminate(_, State) ->
>         file:close(State#state.fd).

>     is_hlr_alarm({AlarmId, _}, #state{alarms = Alarms}) ->
>         lists:member(AlarmId, Alarms).

> #### 8.2.2 Application Specification

> An application specification is required in order to test the `hlr` application. This specification is placed in the file `hlr.app`, which looks as follows:

>     {application, hlr,
>        [{description, "VSHLR"},
>         {vsn, "1.0"},
>         {modules, [{vshlr_2, 1}, {hlr_alarm_h, 1}, {hlr_sup, 1}, {hlr, 1}]},
>         {registered, [hlr_sup, xx2]},
>         {applications, [kernel, stdlib, sasl]},
>         {env, [{hlr_alarm_file, "hlr.alarms"}]},
>         {mod, {hlr, []}}]}.

> This specification says that if no `hlr_alarm_file` is specified in the system configuration file, we use the file `hlr.alarms` in the current directory as a default.

> #### 8.2.3 Testing the Application

> The next task is to test the application. In doing so, we also want to specify another `hlr` alarm file. We do this by writing a configuration file called `sys.config`:

>     [{hlr, [{hlr_alarm_file, "alarms.log"}]}].

> The following interaction shows how to test the application. The command to start the system is followed by a command to start the application itself.

>     erl -pa . -config ./sys

>     5> application:start(hlr, temporary).

>     =PROGRESS REPORT==== 29-May-1996::14:04:05 ===
>          Supervisor: {local,hlr_sup}
>          Started:  [{pid,<0.54.0>},
>                       {name,xx2},
>                       {mfa,{vshlr_2,start_link,[]}},
>                       {restart_type,permanent},
>                       {shutdown,2000},
>                       {child_type,worker}]

>     ok
>     6> gen_event:which_handlers(alarm_handler).
>     [hlr_alarm_h,alarm_handler]
>     7> vshlr_2:i_am_at(martin, home).
>     ok
>     8> vshlr_2:find(martin).
>     {at,home}
>     9> application:stop(hlr).
>     ok
>     10> gen_event:which_handlers(alarm_handler).
>     [alarm_handler]

> ### 8.3 Distributed Applications

> This section describes and illustrates how distributed applications can be used.

> The example illustrated in this section is shown in .

> Example of Distributed Application

> This system has the following components and characteristics:

> - there are two administrative CPUs, `adm1` and `adm2`
> - there are six functional CPUs, `fp1 - fp6` which are organized as shown in the illustration below
> - the two administrative CPUs are used for redundancy and we want to use both of them for performance reasons
> - there are five applications of different type:
>   1. `snmp`. This is a management application, which interfaces an operator. There must be only one instance of this application in the system.
>   2. `cmip`. This is a management application, which interfaces an operator. There must be only one instance of this application in the system.
>   3. `ch`. This is a call handling application. It needs the applications `ss7` and `x25`. We want as many instances of this application as possible, but only one per node.
>   4. `ss7`. This application interfaces `ss7`. We want as many instances of this application as possible, but only one per node.
>   5. `x25`. This application interfaces `x25`. There must only be one instance of this application in the system.

> The administrative CPUs take care of the management applications `snmp` and `cmip`, and the functional CPUs the call handling application `ch`. This application uses the interfaces `ss7` and `x25`, which are represented by corresponding applications. As shown in the figure, only `fp1` and `fp2` have an ss7 interface, and only `fp3` and `fp4` have an x25 interface.

> This is summarized in the table below:

> Application | Instances | Nodes
> `snmp` | 1 | `adm1, adm2`
> `cmip` | 1 | `adm1, adm2`
> `ch` | N | `fp1 - fp6`
> `ss7` | N | `fp1, fp2`
> `x25` | 1 | `fp3, fp4`
> Node Distribution for Example Application

> The following sections describe how to specify this parameter for the different applications in the example.

> #### 8.3.1 The SNMP and CMIP Applications

> These applications can run on either `adm1` or `adm2`. In normal operating mode, we want one application to run at each of these processors, `snmp` on `adm1`, and `cmip` on `adm2`. If one of the nodes goes down, the other node starts the application and both applications will run on one node. When the faulty node restarts, it takes over its application from the other node. This arrangement is specified as follows:

>     {snmp, [adm1, adm2]},
>     {cmip, [adm2, adm1]}

> In the boot script, `snmp` and `cmip` is started on both nodes.

> #### 8.3.2 The CH Application

> The `ch` application is a local application and is started in the boot script on each `fp` node. This call handling application is run on each `fp` node and the application controller can therefore not view this application as distributed.

> #### 8.3.3 The SS7 Application

> This application also has several instances. For this reason, it cannot be distributed, but is local on nodes `fp1` and `fp2`.

> #### 8.3.4 The X25 Application

> This is an application with one instance only and it can run on either `fp3` or `fp4`. Accordingly, it is a distributed application. In normal operating mode, we do not care on which one of these two processors the application runs, but if this node goes down, the other node must start the application. There is no need to move the application back to the original node if it restarts. This requirement is expressed as follows:

>     {x25, [{fp3, fp4}]}

Copyright © 1991-2003 Ericsson Utvecklings AB