e2 Services

A service is an Erlang process that provides some functionality without an application.

There are two general types of services:

  • Registered services, which can be accessed by name

  • Unregistered services, which must be accessed by their process ID

Example

Here’s a simple registered service that provides system wide access to a database:

-module(db).

-behavior(e2_service).

-export([start_link/0, get/0]).

-export([init/1, handle_msg/3, terminate/2]).

start_link() ->
    e2_service:start_link(?MODULE, [], [registered]).

init([]) ->
    {ok, some_db:connect("some_user", "some_pwd")}.

get() ->
    e2_service:call(?MODULE, get_db).

handle_msg(get_db, _From, Db) ->
    {reply, Db, Db}.

terminate(_Reason, Db) ->
    some_db:close(Db).

Let’s break this module down step by step. The code for this module can be divied into these categories:

  • Module information

  • Service initialization

  • Service message handling

  • Service termination

Module Information

All Erlang modules have this declaration – it’s the module name:

-module(db).

The behavior attribute tells us what type of module it is, and helps the Erlang compiler check for any missing callback functions:

-behavior(e2_service).

The exports attribute indicates which functions are callable from outside the module:

-export([start_link/0, get/0]).

-export([init/1, handle_msg/3, terminate/2]).

Service Initialization

Processes in Erlang are typically started with a start_link function. In this case, we’re starting our module using e2_service:start_link/3:

start_link() ->
    e2_service:start_link(?MODULE, [], [registered]).

The first argument is ?MODULE – a macro reference to this module name, which is db. This is used as the e2_service callback module.

The second argument is one of two things, depending on whether the callback module exports init/1:

  • If the module exports init/1 (as in this example), the second argument in start_link/3 is the value used in the call to init/1

  • If the module does not export init/1, the second argument is the initial process state

This module exports init/1, which is called by e2 after the new process has been created. This is where the service creates its initial state:

init([]) ->
    {ok, some_db:connect("some_user", "some_pwd")}.

In this case, the service is connecting to a database, and returning the database connection. This will be used in the first call to handle_msg/3.

Process state is used by the service throughout its life type. Each time the service responds to a message, it has an opportunity to perform some work and modify its state.

Service Message Handling

e2 services interact with clients by replying to messages sent by clients. In this case, to get the system wide database, a client will send a message to the server. This message sending is provided in an easy to use function, callable by the client:

get() ->
    e2_service:call(?MODULE, get_db).

The function calls e2_service:call/2, which sends the registered db process the get_db message. The call will wait until a result is sent back by the service.

The server in turn handles the message:

handle_msg(get_db, _From, Db) ->
    {reply, Db, Db}.

This function is very simple – it returns the system wide database Db that was initialized in init/1. The third element in the return value is the next state of the server.

Service Termination

When the service is stopped, it has a chance to cleanly close the database:

terminate(_Reason, Db) ->
    some_db:close(Db).

Using the Service

If started is started, a client would use it this way:

Db = db:get(),
some_db:set_value(Db, "some_key", "some_value").

This is a very simple service: it handles the initialization of a database connection, makes it available to other Erlang processes, and handles the shut down of the database.

It could be enhanced, if needed, with more features:

  • Manage a pool of database connections

  • Serialize access to a database connection

Example 2

The following service is from examples/utils and illustrates how state is managed and changes by a service:

-module(sequence).

-behavior(e2_service).

-export([start_link/0, next/0, reset/0, reset/1]).

-export([handle_msg/3]).

start_link() ->
    e2_service:start_link(?MODULE, 1, [registered]).

next() ->
    e2_service:call(?MODULE, next).

reset() ->
    reset(1).

reset(Start) ->
    e2_service:cast(?MODULE, {reset, Start}).

handle_msg(next, _From, Next) ->
    {reply, Next, Next + 1};
handle_msg({reset, Next}, noreply, _OldNext) ->
    {noreply, Next}.