Erlang Central

Difference between revisions of "Building An OTP Application"

From ErlangCentral Wiki

(Part Three: Testing What We Have Built)
(Part Three: Testing What We Have Built)
Line 1,037: Line 1,037:
 
Msg: {location_changed,{martin,"at home"}}     
 
Msg: {location_changed,{martin,"at home"}}     
 
ok
 
ok
 +
 +
(a@core.martinjlogan.com)3> location_server:fetch_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin).         
 +
{ok,"at home"}
 
</code>
 
</code>

Revision as of 14:18, 17 October 2006

Contents

Author

Martin Logan

Building an OTP Application Overview

Erlang is a clean and simple language to learn. The learning curve goes up significantly when we start to talk about OTP. How to start an application, what the function of a supervisor is, where to put the gen_server. That is just the beginning it gets far more confusing when questions are asked like, "what is a .app file, what is a release, and can someone please tell me what .script and .boot files do?"

This tutorial will walk you through creating a new OTP application called location_server. This application will store the location of clients, allow others to query for that location from any node in our network, and allow clients to subscribe to notifications in the event of a change of location. Over the course of creating the location_server I will answer all the above questions.

Part One: Building the Server

First thing to do is download the reference build system for this tutorial, this can be found at www.erlware.org under downloads otp_base-<vsn> Once you have the code on your local Unix/Linux machine extract the build system.

> tar -xzfv otp_base-R1.tgz
> cd otp
> ls
build  lib  licence.txt  Makefile  README  release tools

The first thing we are going to do is make an initial build of our build system support files, this is done by typing "make" in the otp directory.

The second thing we are going to do is create a skeleton for our application. Once we do I will disect that skeleton and explain the meaining of all its parts. To create our location_server application we use the "appgen" utility.

> cd tools/utilities
> ./appgen location_server ls
> cd -

We have just created an application under lib/location_server and a release under release/location_server_rel. We will now cd into lib/location server and see what we have made.

> cd lib/location_server
> ls
include  Makefile  src  vsn.mk

The include directory contains a file called location_service.hrl. This is the place we will put all our shared macro and record definitions. The makefile does what you would expect it to. The src directory contains all of our erlang (.erl) files. The vsn.mk file contains the make variable LOCATION_SERVER_VSN=1.0 which is the version of our application. This version plays a role in how we handle releases.

> cd src
> ls
>
location_server.app.src    location_server.erl  ls_sup.erl
location_server.appup.src  ls_server.erl        Makefile

Our src directory contains a file called location_server.erl. Notice that this Erlang source file has the same name as our application. By convention this file contains the API for our application. This file also contains the -behaviour(application) directive. This means that this file is the entry point for the OTP application structure.

%%%-------------------------------------------------------------------
%%% Author  : martinjlogan <martinjlogan@martinjlogan.com>
%%% @doc The entry point into our application.
%%% @end
%%%-------------------------------------------------------------------
-module(location_server).

-behaviour(application).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("location_server.hrl").

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

%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc The starting point for an erlang application.
%% @spec start(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start(Type, StartArgs) ->
    case ls_sup:start_link(StartArgs) of
    {ok, Pid} ->
        {ok, Pid};
    Error ->
        Error
    end.

%%--------------------------------------------------------------------
%% @doc Called to shudown the auction_server application.
%% @spec shutdown() -> ok
%% @end
%%--------------------------------------------------------------------
shutdown() ->
    application:stop(auction_server).

%%====================================================================
%% Internal functions
%%====================================================================

%%--------------------------------------------------------------------
%% Called upon the termintion of an application.
%%--------------------------------------------------------------------
stop(State) ->
    ok.

The start/2 function is a callback function used by the OTP application system to start applications specified by the OTP release system. This function must take two arguments, I will get to what those are later on, and it must return {ok, Pid} for a succesful application startup. Notice that the start/2 function calls ls_sup:start_link/1. ls_sup.erl contains our top level supervisor. Notice the prefix ls it is the first two letters of the two words that make up our application name; location_server. This prefix is a convention and it is used in liu of a package structure in Erlang to avoid name conflicts within a release.

The top level supervisor starts all the workers and lower level supervisors that our application will require. It specifies a strategy detailing how processes will be restarted when they crash. This a key concept in Erlang and I suggest reading the design principles on supervision located at www.erlang.org. Ok, lets take a look at our supervisor.

%%%-------------------------------------------------------------------
%%% Author  : martinjlogan <martinjlogan@martinjlogan.com>
%%% @doc The top level supervisor for our application.
%%% @end
%%%-------------------------------------------------------------------
-module(ls_sup).

-behaviour(supervisor).

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([start_link/1]).

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

%%--------------------------------------------------------------------
%% Macros
%%--------------------------------------------------------------------
-define(SERVER, ?MODULE).

%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the supervisor.
%% @spec start_link(StartArgs) -> {ok, pid()} | Error
%% @end
%%--------------------------------------------------------------------
start_link(StartArgs) ->
    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

%%====================================================================
%% Server functions
%%====================================================================
%%--------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok,  {SupFlags,  [ChildSpec]}} |
%%          ignore                          |
%%          {error, Reason}
%%--------------------------------------------------------------------
init([]) ->
    RestartStrategy    = one_for_one,
    MaxRestarts        = 1000,
    MaxTimeBetRestarts = 3600,

    SupFlags = {RestartStrategy, MaxRestarts, MaxTimeBetRestarts},

    ChildSpecs =
    [
     {ls_server,
      {ls_server, start_link, []},
      transient,
      1000,
      worker,
      [ls_server]}
     ],
    {ok,{SupFlags, ChildSpecs}}.

This file exports two functions start_link/1 and init/1. These are both callbacks that conform to the supervisor behaviour, note the -behaviour(supervisor) directive which is present at the top of the file. Naming a function start_link is a convention in OTP that means the function will spawn a process and that the caller of the function, the parent process, will be linked (see link/1) to the child process. In this case the OTP application structure will be linked to the supervisor process.

The init/1 function is where the true guts of the supervisor are. Remember that you should never put any program logic in your supervisor, it should be as simple as possible and never fail, the fate of your application depends on this. Lets break this down:

 
RestartStrategy    = one_for_one,
MaxRestarts        = 1000,
MaxTimeBetRestarts = 3600,

The RestartStrategy one_for_one means that for each process that dies simply restart that process without affecting any of the others. There are two other restart strategies, one_for_all and rest_for_one, you can read about these in the supervisor documentation. MaxRestarts is the maxumum number of restarts allowed within MaxTimeBetweenRestarts seconds.

{ls_server,
  {ls_server, start_link, []},
   permanent,
   1000,
   worker,
   [ls_server]}

This supervisor has one ChildSpec which means it will start exactly one process. Going in order down the data structure ls_server is the name of the process to be started, {ls_server, start_link, []} is the module function and args that is called to start the child, permanent refers to the fact that the process should never be alowed to die without being restarted, 1000 is the time the process should be given to shutdown before being brutally killed, worker means the child is not a supervisor, [ls_server] is a list of module dependencies of the child.

The supervisor callback module function init/1 returns all of this information and the supervisor behaviour takes care of starting all the children in the order that they were specified in the child specs.

Now we move on to the ls_server.erl file. This file exhibits the behaviour gen_server. This means that it follows the callback prescription for that behaviour and exports the following callback functions:

%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

Additionally we are also going to create the following external functions:

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
     start_link/0,
     stop/0,
     store_location/3,
     fetch_location/2,
     alert_to_change/2
     ]).

Lets get started. It is important to document all of your external functions. The build system supports "edoc" with the "make docs" target which will automatically go through and compile all of your documentation and place it in your application directory under the directory doc. And now on to our functions:

%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

This function starts the server, registers it locally as ?SERVER (see source for -define(SERVER, ?MODULE).) and indicates to the gen_server behaviour that the current module exports the needed callback functions.

%%--------------------------------------------------------------------
%% @doc Stops the server.
%% @spec stop() -> ok
%% @end
%%--------------------------------------------------------------------
stop() ->
    gen_server:cast(?SERVER, stop).

Stop is simple, but notice the use of gen_server:cast/2. This function is about the same as using the "!" operator. It is used to send an asynchronous message to a gen server. In this case we send the atom 'stop' to a locally registered process identified by the macro ?SERVER.

%%--------------------------------------------------------------------
%% @doc Stores the location of a client.
%% <pre> 
%% Types:
%%  Node = node()
%%  Id = atom()
%%  Location = string()
%% </pre>
%% @spec store_location(Node, Id, Location) -> ok
%% @end
%%--------------------------------------------------------------------
store_location(Node, Id, Location) when is_atom(Id), is_list(Location) ->
    gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}).

store_location/3 is a very simple function but there is a lot for us to pay attention to here. Notice that we use the guard expressions, is_atom/1, and is_list/1. We are making sure that the types of the variables passed in are correct. This is an example of not trusting our borders. We check the types here so that we will never again have to check them. Notice that the name of the function is the same as the "tag" on the message we are sending:

gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}).

store_location in that line is our tag and will differentiate this message from others that this server understands. {Id, Location} is the payload that we are sending with the tag store_location. Messages sent in the gen_server should follow this convention of {Tag, Value}. There is an even more important thing to notice about the line above. Notice that the first argument to this function is {?SERVER, Node} instead of just ?SERVER as we saw in cast. This is the case because we want to be able to contact a process on any node in the network not just our own. To message a remotely registered process we can use the syntax {RegisteredName, Node} where node can be any node in the network including the local node.

%%--------------------------------------------------------------------
%% @doc Fetches the location of a client by its Id.
%% <pre> 
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec fetch_location(Node, Id) -> {ok, Location} | {error, Reason} | EXIT
%% @end
%%--------------------------------------------------------------------
fetch_location(Node, Id) ->
    gen_server:call({?SERVER, Node}, {fetch_location, Id}).

The things to notice here is that we no longer use guards, we don't care, this function will not change the state of our server and if it fails it does not matter much to us. Most importantly notice the use of gen_server:call/2. This function makes a blocking synchronous call to a gen_server.

%%--------------------------------------------------------------------
%% @doc Subscribes the caller to notifications of location change for 
%% the given Id. When a location changes the caller of this function
%% will be sent a message of the form {location_changed, {Id, NewLocation}}.
%% <pre> 
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec alert_to_change(Node, Id) -> bool() | EXIT
%% @end
%%--------------------------------------------------------------------
alert_to_change(Node, Id) ->
    gen_server:call({?SERVER, Node}, {alert_to_change, {Id, self()}}).

Now that we have all of our external functions finished lets flesh out our server. One thing that those new to Erlang often get confused by is the idea that these external functions are called in the process space of the calling process but that the messages they generate are received by clauses in the same module but that exist in an entirely different process. It is important to use modules to encapsulate server protocols when possible.

%%====================================================================
%% Server functions
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%%--------------------------------------------------------------------
init([]) ->
    error_logger:info_msg("ls_server:init/1 starting~n", []),
    {ok, dict:new()}.

The init function is used to setup process state before the process can be accessed from the outside. Here our state is embodied by a dict. This dict will be used to store key value pairs of the form {Id, {Location, ListOfSubscribers}}. The init function is also logging the fact that the ls_server process is starting up. The location and management of log files will be covered in the second part of this tutorial covering how to create a release for your application.

%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call({fetch_location, Id}, From, State) ->
    case dict:find(Id, State) of
        {ok, {Location, ListOfSubscribers}} -> {reply, {ok, Location}, State};
        error                               -> {reply, {error, no_such_id}, State}
    end;
handle_call({alert_to_change, {Id, Subscriber}}, From, State) ->
    case dict:find(Id, State) of
        {ok, {Location, ListOfSubscribers}} ->
            NewListOfSubscribers = [Subscriber|lists:delete(Subscriber, ListOfSubscribers)],
            NewState             = dict:store(Id, {Location, NewListOfSubscribers}, State),
            {reply, true, NewState};
        error -> 
            {reply, false, State}
    end.
%%--------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast({store_location, Id, Location}, State) ->
    case dict:find(Id, State) of
        {ok, {Location, ListOfSubscribers}} ->
            lists:foreach(fun(Subscriber) -> Subscriber ! {location_changed, {Id, Location}} end, ListOfSubscribers),
            {reply, true, State};
        error -> 
            NewState = dict:store(Id, {Location, []}, State),
            {reply, false, NewState}
    end,
    {noreply, State}.

Ok, that is it, we are done with the server all we have to do is change directories into our application directory, type "make" and "make docs", and wait for a succesful source and documentation compile.

> cd ..
> make
> make docs
> ls
doc  ebin  include  Makefile  src  vsn.mk

Notice that there are two new directories in our application root directory "doc" and "ebin". location_server/doc contains html documentation of everything we have done. All of our external functions are present in these docs. Lets explore the ebin directly a little more deeply.

> cd ebin
> ls
location_server.app    location_server.beam  ls_sup.beam
location_server.appup  ls_server.beam

The ebin contains a single .beam bytecode compiled file for each of our .erl source files. The ebin directory also contains a .app and a .appup file. The .appup file is used for release upgrade handling and is beyond the scope of this tutorial. The .app file is essential for creating and installing OTP releases so we will look at it in depth.

{application, location_server,
 [
  % A quick description of the application.
  {description, "An Erlang Application."},

  % The version of the applicaton
  {vsn, "1.0"},

  % All modules used by the application.
  {modules,
   [
    location_server, ls_server, ls_sup
   ]},

  % All of the registered names the application uses. This can be ignored.
  {registered, []},

  % Applications that are to be started prior to this one. This can be ignored
  % leave it alone unless you understand it well and let the .rel files in
  % your release handle this.
  {applications,
   [
    kernel,
    stdlib
   ]},

  % OTP application loader will load, but not start, included apps. Again
  % this can be ignored as well.  To load but not start an application it
  % is easier to include it in the .rel file followed by the atom 'none'
  {included_applications, []},

  % configuration parameters similar to those in the config file specified
  % on the command line. can be fetched with gas:get_env
  {env, []},

  % The Module and Args used to start this application.
  {mod, {location_server, []}}== Part One: Building the Server ==
                                                                                
First thing to do is download the reference build system for this tutorial, this can be found at <b>[http://www.erlang.org www.erlware.org]</b> under <b>downloads otp_base-<vsn></b> Once you have the code on your local Unix/Linux machine extract the build system.
                                                                                
<code caption="Code listing 1.0">
> tar -xzfv otp_base-R1.tgz
> cd otp
> ls
build  lib  licence.txt  Makefile  README  release tools

The first thing we are going to do is make an initial build of our build system support files, this is done by typing "make" in the otp directory.

The second thing we are going to do is create a skeleton for our application. Once we do I will dissect that skeleton and explain the meaning of all its parts. To create our location_server application we use the "appgen" utility.

> cd tools/utilities
> ./appgen location_server ls
> cd -

We have just created an application under lib/location_server and a release under release/location_server_rel. We will now cd into lib/location server and see what we have made.

> cd lib/location_server
> ls
include  Makefile  src  vsn.mk

The include directory contains a file called location_service.hrl. This is the place we will put all our shared macro and record definitions. The makefile does what you would expect it to. The src directory contains all of our erlang (.erl) files. The vsn.mk file contains the make variable LOCATION_SERVER_VSN=1.0 which is the version of our application. This version plays a role in how we handle releases.

> cd src
> ls
>
location_server.app.src    location_server.erl  ls_sup.erl
location_server.appup.src  ls_server.erl        Makefile

Our src directory contains a file called location_server.erl. Notice that this Erlang source file has the same name as our application. By convention this file contains the API for our application. This file also contains the -behaviour(application) directive. This means that this file is the entry point for the OTP application structure.

%%%-------------------------------------------------------------------
%%% Author  : martinjlogan <martinjlogan@martinjlogan.com>
%%% @doc The entry point into our application.
%%% @end
%%%-------------------------------------------------------------------
-module(location_server).
                                                                                
-behaviour(application).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("location_server.hrl").
 
%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
     start/2,
     shutdown/0,
     stop/1
     ]).
 
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc The starting point for an erlang application.
%% @spec start(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start(Type, StartArgs) ->
    case ls_sup:start_link(StartArgs) of
    {ok, Pid} ->
        {ok, Pid};
    Error ->
        Error
    end.
 
%%--------------------------------------------------------------------
%% @doc Called to shutdown the auction_server application.
%% @spec shutdown() -> ok
%% @end
%%--------------------------------------------------------------------
shutdown() ->
    application:stop(auction_server).
 
%%====================================================================
%% Internal functions
%%====================================================================
 
%%--------------------------------------------------------------------
%% Called upon the termination of an application.
%%--------------------------------------------------------------------
stop(State) ->
    ok.

The start/2 function is a callback function used by the OTP application system to start applications specified by the OTP release system. This function must take two arguments, I will get to what those are later on, and it must return {ok, Pid} for a successful application start-up. Notice that the start/2 function calls ls_sup:start_link/1. ls_sup.erl contains our top level supervisor. Notice the prefix ls it is the first two letters of the two words that make up our application name; location_server. This prefix is a convention and it is used in lieu of a package structure in Erlang to avoid name conflicts within a release.

The top level supervisor starts all the workers and lower level supervisors that our application will require. It specifies a strategy detailing how processes will be restarted when they crash. This a key concept in Erlang and I suggest reading the design principles on supervision located at www.erlang.org. Ok, lets take a look at our supervisor.

%%%-------------------------------------------------------------------
%%% Author  : martinjlogan <martinjlogan@martinjlogan.com>
%%% @doc The top level supervisor for our application.
%%% @end
%%%-------------------------------------------------------------------
-module(ls_sup).
 
-behaviour(supervisor).
 
%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([start_link/1]).
 
%%--------------------------------------------------------------------
%% Internal exports
%%--------------------------------------------------------------------
-export([init/1]).
 
%%--------------------------------------------------------------------
%% Macros
%%--------------------------------------------------------------------
-define(SERVER, ?MODULE).
 
%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% @doc Starts the supervisor.
%% @spec start_link(StartArgs) -> {ok, pid()} | Error
%% @end
%%--------------------------------------------------------------------
start_link(StartArgs) ->
    supervisor:start_link({local, ?SERVER}, ?MODULE, []).
 
%%====================================================================
%% Server functions
%%====================================================================
%%--------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok,  {SupFlags,  [ChildSpec]}} |
%%          ignore                          |
%%          {error, Reason}
%%--------------------------------------------------------------------
init([]) ->
    RestartStrategy    = one_for_one,
    MaxRestarts        = 1000,
    MaxTimeBetRestarts = 3600,
 
    SupFlags = {RestartStrategy, MaxRestarts, MaxTimeBetRestarts},
 
    ChildSpecs =
    [
     {ls_server,
      {ls_server, start_link, []},
      transient,
      1000,
      worker,
      [ls_server]}
     ],
    {ok,{SupFlags, ChildSpecs}}.
 

This file exports two functions start_link/1 and init/1. These are both callbacks that conform to the supervisor behaviour, note the -behaviour(supervisor) directive which is present at the top of the file. Naming a function start_link is a convention in OTP that means the function will spawn a process and that the caller of the function, the parent process, will be linked (see link/1) to the child process. In this case the OTP application structure will be linked to the supervisor process.

The init/1 function is where the true guts of the supervisor are. Remember that you should never put any program logic in your supervisor, it should be as simple as possible and never fail, the fate of your application depends on this. Lets break this down:

RestartStrategy    = one_for_one,
MaxRestarts        = 1000,
MaxTimeBetRestarts = 3600,

The RestartStrategy one_for_one means that for each process that dies simply restart that process without affecting any of the others. There are two other restart strategies, one_for_all and rest_for_one, you can read about these in the supervisor documentation. MaxRestarts is the maximum number of restarts allowed within MaxTimeBetweenRestarts seconds.

{ls_server,
  {ls_server, start_link, []},
   permanent,
   1000,
   worker,
   [ls_server]}

This supervisor has one ChildSpec which means it will start exactly one process. Going in order down the data structure ls_server is the name of the process to be started, {ls_server, start_link, []} is the module function and args that is called to start the child, permanent refers to the fact that the process should never be allowed to die without being restarted, 1000 is the time the process should be given to shutdown before being brutally killed, worker means the child is not a supervisor, [ls_server] is a list of module dependencies of the child.

The supervisor callback module function init/1 returns all of this information and the supervisor behaviour takes care of starting all the children in the order that they were specified in the child specs.

Now we move on to the ls_server.erl file. This file exhibits the behaviour gen_server. This means that it follows the callback prescription for that behaviour and exports the following callback functions:

%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

Additionally we are also going to create the following external functions:

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
     start_link/0,
     stop/0,
     store_location/3,
     fetch_location/2,
     alert_to_change/2
     ]).

Lets get started. It is important to document all of your external functions. The build system supports "edoc" with the "make docs" target which will automatically go through and compile all of your documentation and place it in your application directory under the directory doc. And now on to our functions:

%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

This function starts the server, registers it locally as ?SERVER (see source for -define(SERVER, ?MODULE).) and indicates to the gen_server behaviour that the current module exports the needed callback functions.

%%--------------------------------------------------------------------
%% @doc Stops the server.
%% @spec stop() -> ok
%% @end
%%--------------------------------------------------------------------
stop() ->
    gen_server:cast(?SERVER, stop).

Stop is simple, but notice the use of gen_server:cast/2. This function is about the same as using the "!" operator. It is used to send an asynchronous message to a gen server. In this case we send the atom 'stop' to a locally registered process identified by the macro ?SERVER.

%%--------------------------------------------------------------------
%% @doc Stores the location of a client.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%%  Location = string()
%% </pre>
%% @spec store_location(Node, Id, Location) -> ok
%% @end
%%--------------------------------------------------------------------
store_location(Node, Id, Location) when is_atom(Id), is_list(Location) ->
    gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}).

store_location/3 is a very simple function but there is a lot for us to pay attention to here. Notice that we use the guard expressions, is_atom/1, and is_list/1. We are making sure that the types of the variables passed in are correct. This is an example of not trusting our borders. We check the types here so that we will never again have to check them. Notice that the name of the function is the same as the "tag" on the message we are sending:

gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}).

store_location in that line is our tag and will differentiate this message from others that this server understands. {Id, Location} is the payload that we are sending with the tag store_location. Messages sent in the gen_server should follow this convention of {Tag, Value}. There is an even more important thing to notice about the line above. Notice that the first argument to this function is {?SERVER, Node} instead of just ?SERVER as we saw in cast. This is the case because we want to be able to contact a process on any node in the network not just our own. To message a remotely registered process we can use the syntax {RegisteredName, Node} where node can be any node in the network including the local node.

%%--------------------------------------------------------------------
%% @doc Fetches the location of a client by its Id.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec fetch_location(Node, Id) -> {ok, Location} | {error, Reason} | EXIT
%% @end
%%--------------------------------------------------------------------
fetch_location(Node, Id) ->
    gen_server:call({?SERVER, Node}, {fetch_location, Id}).

The things to notice here is that we no longer use guards, we don't care, this function will not change the state of our server and if it fails it does not matter much to us. Most importantly notice the use of gen_server:call/2. This function makes a blocking synchronous call to a gen_server.

%%--------------------------------------------------------------------
%% @doc Subscribes the caller to notifications of location change for
%% the given Id. When a location changes the caller of this function
%% will be sent a message of the form {location_changed, {Id, NewLocation}}.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec alert_to_change(Node, Id) -> bool() | EXIT
%% @end
%%--------------------------------------------------------------------
alert_to_change(Node, Id) ->
    gen_server:call({?SERVER, Node}, {alert_to_change, {Id, self()}}).

Now that we have all of our external functions finished lets flesh out our server. One thing that those new to Erlang often get confused by is the idea that these external functions are called in the process space of the calling process but that the messages they generate are received by clauses in the same module but that exist in an entirely different process. It is important to use modules to encapsulate server protocols when possible.

%%====================================================================
%% Server functions
%%====================================================================
 
%%--------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%%--------------------------------------------------------------------
init([]) ->
    error_logger:info_msg("ls_server:init/1 starting~n", []),
    {ok, dict:new()}.

The init function is used to setup process state before the process can be accessed from the outside. Here our state is embodied by a dict. This dict will be used to store key value pairs of the form {Id, {Location, ListOfSubscribers}}. The init function is also logging the fact that the ls_server process is starting up. The location and management of log files will be covered in the second part of this tutorial covering how to create a release for your application.

The call back function that handles gen_server:call invocations is appropriately named handle_call. Our handle_call function clauses are shown below in code listing 1.16.

%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call({fetch_location, Id}, From, State) ->
    case dict:find(Id, State) of
        {ok, {Location, ListOfSubscribers}} -> {reply, {ok, Location}, State};
        error                               -> {reply, {error, no_such_id}, State}
    end;
handle_call({alert_to_change, {Id, Subscriber}}, From, State) ->
    case dict:find(Id, State) of
        {ok, {Location, ListOfSubscribers}} ->
            NewListOfSubscribers = [Subscriber|lists:delete(Subscriber, ListOfSubscribers)],
            NewState             = dict:store(Id, {Location, NewListOfSubscribers}, State),
            {reply, true, NewState};
        error ->
            {reply, false, State}
    end.

And our handle_cast function - I am guessing you can figure out what it is for by now.

%%--------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast({store_location, {Id, Location}}, State) ->
    NewState =
        case dict:find(Id, State) of
            {ok, {Location, ListOfSubscribers}} ->
                lists:foreach(fun(Subscriber) -> Subscriber ! {location_changed, {Id, Location}} end, ListOfSubscribers),
                dict:store(Id, {Location, ListOfSubscribers}, State);
            error -> 
                dict:store(Id, {Location, []}, State)
        end,
    {noreply, NewState}
 
The next step we need to take is to define our application level API.  We do this by placing and exporting the functions we want the world to see into a facade; the application module, location_server.erl.  In this case the functions come from a single file, ls_server.erl. 

<code caption="Code listing 1.18">
%%--------------------------------------------------------------------
%% @doc Stores the location of a client.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%%  Location = string()
%% </pre>
%% @spec store_location(Node, Id, Location) -> ok
%% @end
%%--------------------------------------------------------------------
store_location(Node, Id, Location) ->
    ls_server:store_location(Node, Id, Location).

%%--------------------------------------------------------------------
%% @doc Fetches the location of a client by its Id.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec fetch_location(Node, Id) -> {ok, Location} | {error, Reason} | EXIT
%% @end
%%--------------------------------------------------------------------
fetch_location(Node, Id) ->
    ls_server:fetch_location(Node, Id).

%%--------------------------------------------------------------------
%% @doc Subscribes the caller to notifications of location change for
%% the given Id. When a location changes the caller of this function
%% will be sent a message of the form {location_changed, {Id, NewLocation}}.
%% <pre>
%% Types:
%%  Node = node()
%%  Id = atom()
%% </pre>
%% @spec alert_to_change(Node, Id) -> bool() | EXIT
%% @end
%%--------------------------------------------------------------------
alert_to_change(Node, Id) ->
    ls_server:alert_to_change(Node, Id).

Ok, that is it, we are done coding all we have to do is change directories into our application directory, type "make" and "make docs", and wait for a successful source and documentation compile.

> cd ..
> make
> make docs
> ls
doc  ebin  include  Makefile  src  vsn.mk

Notice that there are two new directories in our application root directory "doc" and "ebin". location_server/doc contains html documentation of everything we have done. All of our external functions are present in these docs. Lets explore the ebin directly a little more deeply.

> cd ebin
> ls
location_server.app    location_server.beam  ls_sup.beam
location_server.appup  ls_server.beam

The ebin contains a single .beam byte-code compiled file for each of our .erl source files. The ebin directory also contains a .app and a .appup file. The .appup file is used for release upgrade handling and is beyond the scope of this tutorial. The .app file is essential for creating and installing OTP releases so we will look at it in depth.

{application, location_server,
 [
  % A quick description of the application.
  {description, "An Erlang Application."},
 
  % The version of the application
  {vsn, "1.0"},
 
  % All modules used by the application.
  {modules,
   [
    location_server, ls_server, ls_sup
   ]},
 
  % All of the registered names the application uses. This can be ignored.
  {registered, []},
 
  % Applications that are to be started prior to this one. This can be ignored
  % leave it alone unless you understand it well and let the .rel files in
  % your release handle this.
  {applications,
   [
    kernel,
    stdlib
   ]},
 
  % OTP application loader will load, but not start, included apps. Again
  % this can be ignored as well.  To load but not start an application it
  % is easier to include it in the .rel file followed by the atom 'none'
  {included_applications, []},
 
  % configuration parameters similar to those in the config file specified
  % on the command line. can be fetched with gas:get_env
  {env, []},
 
  % The Module and Args used to start this application.
  {mod, {location_server, []}}
 ]
}.

The comments in the above code pretty much explain it all. This .app file is created dynamically by the make process from the location_server/src/location_server.app.src file. This means that you should pretty much never have to manually edit this file.

Congratulations you have just created your first beautiful OTP application. Now lets move on to the second part of our tutorial and create a release in order to run and deploy our OTP application.

]

}. </code>

The comments in the above code pretty much explain it all. This .app file is created dynamically by the make process from the location_server/src/location_server.app.src file. This means that you should pretty much never have to manually edit this file.

Congratulations you have just created your first beautiful OTP application. Now lets move on to the second part of our tutorial and create a release in order to run and deploy our OTP application.

Part Two: Crafting a Release

The first thing we need to do is get into the release skeleton that the tools/utilities/appgen script has created for us. From the otp directory:


> cd release/location_server_rel
> ls
location_server_rel.config.src  Makefile  yaws.conf.src
location_server_rel.rel.src     vsn.mk

This directory contains a vsn.mk file which serves to store the version string for this release. It contains a yaws.conf.src file which will be used to generate a yaws.conf file should you choose to include the yaws webserver in your release. The two files we are going to concentrate on right now are the location_server_rel.config.src file and the location_server.rel.src file. The .config.src file is what make will translate into a .config file. This file includes all of the configuration the different applications in our release require to do things such as truncate and rotate our log files. The .rel.src file is what the make process will turn into a .rel file which will inturn be used to create our .script and .boot files, the files that tell the Erlang system what code to load when starting up. Here is our .rel.src file.

{release,
 {"location_server_rel", "%REL_VSN%"},
 erts,
 [
  kernel,
  stdlib,
  sasl,
  fslib,
  gas,
  location_server
 ]
}.

This file specifies the name of our release followed by a version string that will be filled in when we do a make with the version string present in our vsn.mk file. Following that we specify that we want erts to be present in our release and the kernel, stdlib, sasl, fslib, gas, and finally our own custom application the location_server. For more details on fslib and gas, the two applications that together provide configuration and log rotation and truncation for our release you can go to erlware.org and see full documentation about them. Alternatively you can cd to either otp/lib/fslib or otp/lib/gas and run "make docs" and just generate all of the docs locally. When we run make this rel.src file will be turned into an actual .rel file which contains all the version numbers associated with the applications in the .rel.src file. For more info on the .rel.src structure take a look at the docs for the fslib file fs_boot_smithe.

Time to build and run our release.

> make
> ls
local                           location_server_rel.rel          Makefile
location_server_rel             location_server_rel.rel.src      vsn.mk
location_server_rel.boot        location_server_rel.rel.src.tmp  yaws.conf.src
location_server_rel.config.src  location_server_rel.script

Quite a number of files and two directories have been generated. We can see a .rel file, a .script file, and a .boot file. Take a look at the contents of each, well, the .boot file might not prove to be too interesting because it is just a binary version of the .script file. Pay attention to the two directories that have been created; local and location_server_rel. location_server_rel is a staging area for installation and production releases of our application, for more info reference OTP Base documentation at erlware.org. We are going to focus on the local directory here. This directory allows us to run our release interactively. If we wanted to run it as a daemon it would be easy enough to invoke local/location_server_rel.sh with the -detached flag to accomplish that. For now we are going to cd into local and just run our application as is.

> cd local
> ./location_server_rel.sh
Erlang (BEAM) emulator version 5.4.12 [source] [hipe]

INFO REPORT 14-Oct-2006::00:18:33
gas_sup:init

INFO REPORT 14-Oct-2006::00:18:33
ls_server:init/1 starting

Eshell V5.4.12  (abort with ^G)
(martinjlogan_location_server_rel@core.martinjlogan.com)1>

We have successfully started the location_server release. Can you see where the log message we placed in ls_server to indicate its having reached the init/1 function has shown up on the console? That is not the only place our message shows up:


> ls log/1.0/
err_log  sasl_log

The err_log file contains all the logs that get printed via the sasl error_logger with info_msg or error_msg. The other log, sasl_log, is where you will find all the sasl error reports. If your release crashes the first place to look is the sasl_log. By default the build system has wired in the use of fs_elwrap_h via the G.A.S application that is part of your release in order to truncate and rotate logfiles. This prevents the log files from growing indefinitely. To configure or remove this functionality you can use the location_server.config file located in the "local" directory.


{gas,
  [
   % Tell GAS to start elwrap.
   {mod_specs, [{elwrap, {fs_elwrap_h, start_link}}]},

   % elwrap config.
   {err_log, "/home/martinjlogan/work/otp/release/location_server_rel/local/log/1.0/err_log"},
   {err_log_wrap_info, {{err,5000000,10},{sasl,5000000,10}}},
   {err_log_tty, true} % Log to the screen
   ]},

The mod_specs inform the GAS application as to how and what services to start up for a release. Following that are the actual config tuples elwrap uses to do its job. Starting with the location of the err_log and followed by the wrap specs which indicate for both the sasl and error logs how large a file can get before it is rotated out and just how many files there may be for a given logger before old files start getting deleted. The last bit of config toggles whether or not the error logger writes to the screen.

Part Three: Testing What We Have Built

To test our project we are going to start 3 separate Erlang named nodes. Open three terminal windows and start three nodes, one is the location server as started by local/location_server.sh, two is started with erl -name a -pz <path to location_server ebin&gt, three is started with erl -name b -pz <path to location_server ebin&gt. The -pz option on both tells the interpreter to add the specified path to the code loader search path. This means that we will have the location_server interfaces available for our use in node a and b . (This loading of code without starting an application can be accomplished by adding the tuple 'none' after an included application in the .rel.src file.) So now we have three nodes a@machinename.com b@machinename.com and <username>_location_server@machinename.com. To connect our nodes execute nat_adm:ping('a@machinename.com') from the other two nodes. After this is complete running nodes() on any of the nodes should yeild a complete list of the other nodes in our cloud. Run the following commands on your nodes. Make sure you pay attention to the node names in the following example because the commands are not all run on the same node.

(a@core.martinjlogan.com)1> location_server:store_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin, "at work").
ok

(b@core.martinjlogan.com)1> location_server:alert_to_change('martinjlogan_location_server_rel@core.martinjlogan.com', martin).
true
(b@core.martinjlogan.com)2> receive Msg -> io:format("Msg: ~p~n", [Msg]) end.

(a@core.martinjlogan.com)2> location_server:store_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin, "at home").
ok

(b@core.martinjlogan.com)2>    
Msg: {location_changed,{martin,"at home"}}    
ok

(a@core.martinjlogan.com)3> location_server:fetch_location('martinjlogan_location_server_rel@core.martinjlogan.com', martin).           
{ok,"at home"}