Building An OTP Application
From Erlang Community
(Difference between revisions)
| Revision as of 03:50, 20 October 2006 (edit) Martinjlogan (Talk | contribs) (Explains how to build a properly formed OTP application) ← Previous diff |
Revision as of 03:53, 20 October 2006 (edit) (undo) Martinjlogan (Talk | contribs) Next diff → |
||
| Line 1: | Line 1: | ||
| - | == Author == | ||
| - | Martin Logan | ||
| - | == Building an OTP Application Overview == | ||
| - | |||
| - | Erlang is a clean, simple language, and it is easy to learn. That's erlang, now lets talk OTP. The learning curve goes up si | ||
| - | gnificantly when getting into OTP. Questions arise, such as; how do I start an application, what is the function of a supervi | ||
| - | sor, and how do I make use of gen_server? That is just the beginning it gets far more confusing... "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 loc | ||
| - | ation of clients, allow others to query for that location from any node in our network, and allow clients to subscribe to noti | ||
| - | fications that are sent in the event of a change of location. Over the course of creating the location_server I will answer al | ||
| - | l of the above questions on OTP applications. | ||
| - | |||
| - | == Part One: Building the Server == | ||
| - | |||
| - | You will need to download the reference build system for this tutorial, this can be found at <b>[http://www.erlang.org www.erl | ||
| - | ware.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 | ||
| - | </code> | ||
| - | |||
| - | The first thing we are going to do is make an initial build of our build system, this is done by typing "make" in the otp dire | ||
| - | ctory. | ||
| - | |||
| - | The second thing to do is create a skeleton for the application. Once we do I will disect that skeleton and explain the meain | ||
| - | ing of all its parts. To create the location_server application skeleton we use the "appgen" utility. | ||
| - | |||
| - | <code caption="Code listing 1.1"> | ||
| - | > cd tools/utilities | ||
| - | > ./appgen location_server ls | ||
| - | > cd - | ||
| - | </code> | ||
| - | 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. | ||
| - | |||
| - | <code caption="Code listing 1.2"> | ||
| - | > cd lib/location_server | ||
| - | > ls | ||
| - | include Makefile src vsn.mk | ||
| - | </code> | ||
| - | |||
| - | The include directory contains a file called location_service.hrl. This is the place we will put all our shared macro and rec | ||
| - | ord definitions. The src directory contains all of our erlang (.erl) files. The vsn.mk file contains the make variable LOCATI | ||
| - | ON_SERVER_VSN=1.0 which is the initial version of our application (feel free to change this). This version plays a role in ho | ||
| - | w releases are handled, more on this later. | ||
| - | |||
| - | <code caption="Code listing 1.3"> | ||
| - | > cd src | ||
| - | > ls | ||
| - | > | ||
| - | location_server.app.src location_server.erl ls_sup.erl | ||
| - | location_server.appup.src ls_server.erl Makefile | ||
| - | </code> | ||
| - | |||
| - | The src directory contains a file called location_server.erl. Notice that this Erlang source file has the same name as our ap | ||
| - | plication. By convention this file contains the API for our application. This file also contains the -behaviour(application) | ||
| - | directive. This means that this file can be used as the entry point for the OTP application structure. | ||
| - | |||
| - | <code caption="Code listing 1.4"> | ||
| - | %%%------------------------------------------------------------------- | ||
| - | %%% 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. | ||
| - | </code> | ||
| - | |||
| - | The start/2 function is a callback function used by the OTP application system to start applications specified by the OTP rele | ||
| - | ase system. This function must take two arguments (you can read the docs on supervision for more info on the args) and it mus | ||
| - | t return {ok, Pid} or {ok, Pid, State}, the former being more typical, for a succesful application startup. Notice that the st | ||
| - | art/2 function calls ls_sup:start_link/1. ls_sup.erl contains our top level supervisor. Notice the prefix "ls" in the name ls_ | ||
| - | sup it is the first two letters of the two words that make up our application name; location_server. This prefix is a convent | ||
| - | ion and it is used in liu of a package structure in Erlang to avoid name conflicts within a release. You can use anything for | ||
| - | your prefix, I only offer the first letter of each word in your application name as a suggestion. | ||
| - | |||
| - | The top level supervisor starts all the workers and lower level supervisors that our application will require. It specifies a | ||
| - | strategy detailing how its processes will be restarted when they crash. This a key concept in Erlang and I suggest reading t | ||
| - | he design principles on supervision located at www.erlang.org. | ||
| - | |||
| - | <code caption="Code listing 1.5"> | ||
| - | %%%------------------------------------------------------------------- | ||
| - | %%% 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, []}, | ||
| - | permanent, | ||
| - | 1000, | ||
| - | worker, | ||
| - | [ls_server]} | ||
| - | ], | ||
| - | {ok,{SupFlags, ChildSpecs}}. | ||
| - | |||
| - | </code> | ||
| - | |||
| - | This file exports two functions start_link/1 and init/1. These are both functions used to 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 conventi | ||
| - | on 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 our | ||
| - | super down: | ||
| - | |||
| - | <code caption="Code listing 1.6"> | ||
| - | RestartStrategy = one_for_one, | ||
| - | MaxRestarts = 1000, | ||
| - | MaxTimeBetRestarts = 3600, | ||
| - | </code> | ||
| - | |||
| - | 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 doc | ||
| - | umentation. MaxRestarts is the maxumum number of restarts allowed within MaxTimeBetweenRestarts seconds. | ||
| - | |||
| - | <code caption="Code listing 1.7"> | ||
| - | {ls_server, | ||
| - | {ls_server, start_link, []}, | ||
| - | permanent, | ||
| - | 1000, | ||
| - | worker, | ||
| - | [ls_server]} | ||
| - | </code> | ||
| - | |||
| - | This supervisor has one ChildSpec which means it will start exactly one process. Going in order down the data structure ls_ser | ||
| - | ver is the name of the process to be started, {ls_server, start_link, []} is the module function and args that is called to st | ||
| - | art the child, permanent refers to the fact that the process should never be alowed to die without being restarted (there are | ||
| - | other options, see the docs on supervision at erlang.org), 1000 is the time the process should be given to shutdown before bei | ||
| - | ng 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 star | ||
| - | ting 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 callbac | ||
| - | k prescription for that behaviour and exports the following callback functions: | ||
| - | |||
| - | <code caption="Code listing 1.8"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% gen_server callbacks | ||
| - | %%-------------------------------------------------------------------- | ||
| - | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). | ||
| - | </code> | ||
| - | |||
| - | Additionally we are also going to create the following external functions: | ||
| - | |||
| - | <code caption="Code listing 1.9"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% External exports | ||
| - | %%-------------------------------------------------------------------- | ||
| - | -export([ | ||
| - | start_link/0, | ||
| - | stop/0, | ||
| - | store_location/3, | ||
| - | fetch_location/2, | ||
| - | alert_to_change/2 | ||
| - | ]). | ||
| - | </code> | ||
| - | |||
| - | Lets get started. It is important to document all of your external functions. The OTP Base 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 applicat | ||
| - | ion directory under the directory doc (location_server/doc). On to our functions: | ||
| - | |||
| - | <code caption="Code listing 1.10"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% @doc Starts the server. | ||
| - | %% @spec start_link() -> {ok, pid()} | {error, Reason} | ||
| - | %% @end | ||
| - | %%-------------------------------------------------------------------- | ||
| - | start_link() -> | ||
| - | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). | ||
| - | </code> | ||
| - | |||
| - | This function starts the server, registers it locally as ?SERVER (see source for -define(SERVER, ?MODULE).) and indicates to t | ||
| - | he gen_server behaviour that the current module exports the needed callback functions. | ||
| - | |||
| - | <code caption="Code listing 1.11"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% @doc Stops the server. | ||
| - | %% @spec stop() -> ok | ||
| - | %% @end | ||
| - | %%-------------------------------------------------------------------- | ||
| - | stop() -> | ||
| - | gen_server:cast(?SERVER, stop). | ||
| - | </code> | ||
| - | |||
| - | Stop is simple, but notice the use of gen_server:cast/2. This function is about the same as using the "!" operator. It is us | ||
| - | ed to send an asynchronous message to a gen server. In this case we send the atom 'stop' to a locally registered process iden | ||
| - | tified by the macro ?SERVER. | ||
| - | |||
| - | <code caption="Code listing 1.12"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% @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}}). | ||
| - | </code> | ||
| - | |||
| - | 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 e | ||
| - | xpressions, is_atom/1, and is_list/1. We are making sure that the types of the variables passed in are correct. This is an e | ||
| - | xample of not trusting our borders. We check the types here so that we will never again have to check them. Did you notice t | ||
| - | hat the name of the function is the same as the "tag" on the message we are sending? No? I will explain; | ||
| - | |||
| - | <code caption="Code listing 1.13"> | ||
| - | gen_server:cast({?SERVER, Node}, {store_location, {Id, Location}}). | ||
| - | </code> | ||
| - | |||
| - | store_location in the line above is our "tag" and will differentiate this message from others that this server understands (it | ||
| - | s alphabet). {Id, Location} is the payload that we are sending with the tag store_location. Messages sent in the gen_server sh | ||
| - | ould follow this convention of {Tag, Value}. There is an even more important thing to notice about the line above. Notice tha | ||
| - | t 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. | ||
| - | |||
| - | <code caption="Code listing 1.14"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% @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}). | ||
| - | </code> | ||
| - | |||
| - | 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 serve | ||
| - | r 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 | ||
| - | synchronous (blocking) call to a gen_server. | ||
| - | |||
| - | <code caption="Code listing 1.15"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% @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()}}). | ||
| - | </code> | ||
| - | |||
| - | Now that we have all of our external functions finished lets flesh out our server. One thing that those new to Erlang often g | ||
| - | et confused by is the idea that these external functions are called in the process space of the calling process but that the m | ||
| - | essages they generate are received by clauses in the same module but that exist in an entirely different process. It is import | ||
| - | ant to use modules to encapsulate server protocols when possible. | ||
| - | |||
| - | <code caption="Code listing 1.16"> | ||
| - | %%==================================================================== | ||
| - | %% 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()}. | ||
| - | </code> | ||
| - | |||
| - | The init function is used to setup process state before the process can be accessed from the outside. Here our state is embod | ||
| - | ied by a [http://www.erlang.org/doc/doc-5.5.1/lib/stdlib-1.14.1/doc/html/index.html 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 pr | ||
| - | ocess is starting up. The location and management of log files will be covered in the second part of this tutorial covering ho | ||
| - | w to create a release for your application. | ||
| - | |||
| - | <code caption="Code listing 1.17"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% 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. | ||
| - | </code> | ||
| - | |||
| - | <code caption="Code listing 1.17"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% 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) -> % Remember that State is a dict() | ||
| - | NewState = | ||
| - | case dict:find(Id, State) of | ||
| - | {ok, {OldLocation, 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}. | ||
| - | </code> | ||
| - | |||
| - | We are done with the server and all the functionality of our location_server application, but we have one last step to take. | ||
| - | It is clearly awkward and ugly to have clients of our application calling ls_server:store_location etc... What we need is a we | ||
| - | ll defined external API for clients. The place to put that API is in the module that has the same name as our application, ou | ||
| - | r application behaviour module, location_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). | ||
| - | </code> | ||
| - | |||
| - | Of course the functions must be exported. | ||
| - | |||
| - | <code caption="Code listing 1.19"> | ||
| - | %%-------------------------------------------------------------------- | ||
| - | %% External exports | ||
| - | %%-------------------------------------------------------------------- | ||
| - | -export([ | ||
| - | start/2, | ||
| - | shutdown/0, | ||
| - | store_location/3, | ||
| - | fetch_location/2, | ||
| - | alert_to_change/2, | ||
| - | stop/1 | ||
| - | ]). | ||
| - | </code> | ||
| - | |||
| - | Now we are done with the coding of location_server, all we have to do is change directories into our application directory, ty | ||
| - | pe "make" and "make docs", and wait for a succesful source and documentation compile. | ||
| - | |||
| - | <code caption="Code listing 1.20"> | ||
| - | > cd .. | ||
| - | > make | ||
| - | > make docs | ||
| - | > ls | ||
| - | doc ebin include Makefile src vsn.mk | ||
| - | </code> | ||
| - | |||
| - | Notice that there are two new directories in our application root directory "doc" and "ebin". location_server/doc contains htm | ||
| - | l documentation of everything we have done. All of our external functions are present in these docs. Lets explore the ebin di | ||
| - | rectly a little more deeply. | ||
| - | |||
| - | <code caption="Code listing 1.21"> | ||
| - | > cd ebin | ||
| - | > ls | ||
| - | location_server.app location_server.beam ls_sup.beam | ||
| - | location_server.appup ls_server.beam | ||
| - | </code> | ||
| - | |||
| - | 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. | ||
| - | |||
| - | <code caption="Code listing 1.22"> | ||
| - | {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 == | ||
| - | |||
| - | 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 fi | ||
| - | le. | ||
| - | |||
| - | Congratulations you have just created your first beautiful OTP application. Now lets move on to the second part of our tutoria | ||
| - | l and create a release in order to run and deploy our OTP application. | ||
| - | |||
| - | == Part Two: Crafting a Release == | ||
| - | |||
| - | From the otp directory: | ||
| - | |||
| - | <code caption="Code listing 2.0"> | ||
| - | > cd release/location_server_rel | ||
| - | > ls | ||
| - | location_server_rel.config.src Makefile yaws.conf.src | ||
| - | location_server_rel.rel.src vsn.mk | ||
| - | </code> | ||
| - | |||
| - | 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 fil | ||
| - | e. The .config.src file is what make will translate into a .config file. This file includes all of the configuration the diff | ||
| - | erent applications in our release require to do things such as truncate and rotate our log files. The .rel.src file is what th | ||
| - | e 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. | ||
| - | |||
| - | <code caption="Code listing 2.1"> | ||
| - | {release, | ||
| - | {"location_server_rel", "%REL_VSN%"}, | ||
| - | erts, | ||
| - | [ | ||
| - | kernel, | ||
| - | stdlib, | ||
| - | sasl, | ||
| - | fslib, | ||
| - | gas, | ||
| - | location_server | ||
| - | ] | ||
| - | }. | ||
| - | </code> | ||
| - | |||
| - | 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 ver | ||
| - | sion string present in our vsn.mk file. Following that we specify that we want erts to be present in our release and the kerne | ||
| - | l, stdlib, sasl, fslib, gas, and finally our own custom application the location_server. For more details on fslib and gas, th | ||
| - | e two applications that together provide configuration and log rotation and truncation for our release you can go to [http://www.erlware.org erlware.org] and see full documentation about them. Alternatively you can cd to either otp/lib/fslib or otp/li | ||
| - | b/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 a | ||
| - | n actual .rel file which contains all the version numbers associated with the applications in the .rel.src file. For more inf | ||
| - | o on the .rel.src structure take a look at the docs for the fslib file [http://www.erlware.org/fos_erlware/markup/fslib_doc/in | ||
| - | dex.html fs_boot_smithe]. | ||
| - | |||
| - | Time to build and run our release. | ||
| - | |||
| - | <code caption="Code listing 2.2"> | ||
| - | > 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 | ||
| - | </code> | ||
| - | |||
| - | Quite a number of files and two directories have been generated. We can see a .rel file, a .script file, and a .boot file. Ta | ||
| - | ke a look at the contents of each, well, the .boot file might not prove to be too interesting because it is just a binary vers | ||
| - | ion 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 [http://www | ||
| - | .erlware.org/fos_erlware/project/otp_base OTP Base] documentation at erlware.org. We are going to focus on the local director | ||
| - | y here. This directory allows us to run our release interactively. If we wanted to run it as a daemon it would be easy enoug | ||
| - | h to invoke local/location_server_rel.sh with the -detached flag to accomplish that. For now we are going to cd into local an | ||
| - | d just run our application as is. | ||
| - | |||
| - | <code caption="Code listing 2.3"> | ||
| - | > 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> | ||
| - | </code> | ||
| - | |||
| - | 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: | ||
| - | |||
| - | <code caption="Code listing 2.4"> | ||
| - | > ls log/1.0/ | ||
| - | err_log sasl_log | ||
| - | </code> | ||
| - | |||
| - | 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 or | ||
| - | der to truncate and rotate logfiles. This prevents the log files from growing indefinitely. To configure or remove this func | ||
| - | tionality you can use the location_server.config file located in the "local" directory. | ||
| - | |||
| - | |||
| - | <code caption="Code listing 2.4"> | ||
| - | {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 | ||
| - | ]}, | ||
| - | </code> | ||
| - | |||
| - | 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 indica | ||
| - | te 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, o | ||
| - | ne is the location server as started by local/location_server.sh, two is started with erl -name a -pz <path to location_ser | ||
| - | ver ebin>, three is started with erl -name b -pz <path to location_server ebin>. The -pz option on both tells the inte | ||
| - | rpreter 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@machinen | ||
| - | ame.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 yield a complete list of the othe | ||
| - | r 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. | ||
| - | |||
| - | <code caption="Code listing 3.0"> | ||
| - | (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"} | ||
| - | </code> | ||
| - | |||
| - | Here is what happened above, we started a location server as the node 'martinjlogan_location_server_rel@core.martinjlogan.com' | ||
| - | via the location_server_rel.sh start-up script. Then we started two nodes a@core.martinjlogan.com and b@core.martinjlogan.co | ||
| - | m. We then followed these steps: | ||
| - | <ol> | ||
| - | <li>At prompt count 1 on "a" we did a store location with the key 'martin' and the value "at work"</li> | ||
| - | <li>At prompt count 1 on "b" we did an alert to change for the key 'martin'</li> | ||
| - | <li>At prompt count 2 on "b" we drop into a receive for a Msg which we will print on receipt</li> | ||
| - | <li>At prompt count 2 on "a" we do a store location, which changes the location for 'martin' to "at home" and causes "b" to ex | ||
| - | it | ||
| - | the receive clause printing the message: Msg: {location_changed,{martin,"at home"}}. Subscription works!</li> | ||
| - | <li>Finally at prompt count 3 on "a" we prove that fetch_location works.</li> | ||
| - | </ol> | ||
| - | |||
| - | |||
| - | You can download the code for this tutorial [http://www.erlware.org/fos_erlware/priv/downloads/location_server.tgz here] | ||
| - | That's it, happy Erlanging. | ||
| - | |||
| - | [[Category:HowTo]] | ||

Digg It
Del.icio.us
Reddit
Facebook
Stumble Upon
Technorati

