[erlang-questions] How to code against an interface rather than an implementation?

Bohuslav Svancara bsvancara@REDACTED
Fri Aug 9 19:23:22 CEST 2013


Hello!

I tried ContextErlang as an exercise:
http://www.guidosalvaneschi.com/wp/software/contexterlang/

The "context programming" concept can be applied very well here (I think).

Here is an example what I am doing.

(1)
The "base" module "mt4_db_interface" implements MySql operations using emysql.
It also includes a definition for "context switch". (See
mt4_db_interface_context/0 ):

-module(mt4_db_interface).

-behaviour(context_agent).

% ContextErlang
-context_call([connect/5,get_c2_id_for_mt4_id/2,update_c2_id_for_mt4_id/3,delete/2]).
-context_cast([]).

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

%% External exports
-export([connect/5,get_c2_id_for_mt4_id/2,delete/2,update_c2_id_for_mt4_id/3,start_link/0]).

% ContextErlang
-include("../../ContextErlang/include/context_agent_api.hrl").

-define(PoolId,mt4). % a definition for emysql:add_pool(...)

-record(mt4_db_interface_state, {dbHandle,
dummy_database_for_tests=dict:new()}). % in the records.hrl in the
real program

start_link() -> context_agent:start_link({local, ?MODULE}, ?MODULE, [], []).

get_c2_id_for_mt4_id(SystemId,MT4OrderId) ->
context_agent:call(?MODULE, {get_c2_id_for_mt4_id,
SystemId,MT4OrderId}, 60000).

update_c2_id_for_mt4_id(SystemId,MT4OrderId,C2SignalId) ->
context_agent:call(?MODULE, {update_c2_id_for_mt4_id,
SystemId,MT4OrderId,C2SignalId}, 60000).

delete(SystemId,MT4OrderId) -> context_agent:call(?MODULE, {delete,
SystemId,MT4OrderId}, 60000).

connect(Host, Port, User, Password, DatabaseName) ->
context_agent:call(?MODULE, {connect, Host, Port, User, Password,
DatabaseName}, 60000).

init([]) ->
   io:format("Starting ~p~n",[?MODULE]),
   {context,mt4_db_interface_context(),#mt4_db_interface_state{}}.

handle_call({connect, Host, Port, User, Password, DatabaseName},
_From, State) ->
    Reply = do_connect(Host, Port, User, Password, DatabaseName),
    {reply, Reply, State};

handle_call({get_c2_id_for_mt4_id, SystemId,MT4OrderId}, _From, State) ->
    Reply = do_get_c2_id_for_mt4_id(?PoolId,SystemId,MT4OrderId),
    {reply, Reply, State};

handle_call({update_c2_id_for_mt4_id, SystemId,MT4OrderId,C2SignalId},
_From, State) ->
    Reply = do_replace_c2_id_for_mt4_id(?PoolId,SystemId,MT4OrderId,C2SignalId),
    {reply, Reply, State};

handle_call({delete, SystemId,MT4OrderId}, _From, State) ->
    Reply = do_delete(?PoolId,SystemId,MT4OrderId),
    {reply, Reply, State};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% =============================================================================
%% MySql implementations:  emysql:execute(DbId,Select)
do_connect(Host, Port, User, Password, DatabaseName)->
    % Connect to MySql here
    emysql:add_pool(?PoolId,...)...
ok.

do_get_c2_id_for_mt4_id(DbId,SystemId,MT4OrderId) ->
emysql:execute(...), ....
do_replace_c2_id_for_mt4_id(DbId,SystemId,MT4OrderId,C2SignalId) ->
emysql:execute(...), ....
do_delete(DbId,SystemId,MT4OrderId) ->  emysql:execute(...), ....

%% ====================================================================
%% @doc ContextErlang.
%% Create a context structure for this module.
%% Defined variation 'use_dummy_db_for_mt4' is not active in the
production mode. Activated just for unit tests.
%% @end
%% ====================================================================
mt4_db_interface_context() ->
    Spec = [ {what_db_to_use_slot, use_dummy_db_for_mt4} ],
    context_ADT:create(Spec).


(2)
A variation "use_dummy_db_for_mt4" which implements a fake database
and is used for unit tests:

%% ===========================================================================
%% Author: Bohuslav Svancara
%% Created: 2013-07-20
%% Description: Experimental ContextErlang variation module for
mt4_db_interface.
%%
%% This variation is used for unit tests where MySql is very hard to use.
%% ===========================================================================

-module(use_dummy_db_for_mt4).
-export([handle_call/3,on_activation/1,on_deactivation/1,test/0]).

-context_call([connect/5,get_c2_id_for_mt4_id/2,update_c2_id_for_mt4_id/3,delete/2]).
-context_cast([]).

-record(mt4_db_interface_state, {dbHandle,
dummy_database_for_tests=dict:new()}). % in records.hrl in a real
program

handle_call({connect, Host, Port, User, Password, DatabaseName},
_From, State) ->
    {reply, ok, State#mt4_db_interface_state{ dummy_database_for_tests
= dict:new()}};

handle_call({get_c2_id_for_mt4_id, SystemId,MT4OrderId}, _From, State) ->
    Reply = case dict:find({SystemId,MT4OrderId},
State#mt4_db_interface_state.dummy_database_for_tests) of
                <snip>
            end,
    {reply, Reply, State};

handle_call({update_c2_id_for_mt4_id, SystemId,MT4OrderId,C2SignalId},
_From, State) ->
    NewDict = dict:update({SystemId,MT4OrderId}, fun(_) -> C2SignalId
end, C2SignalId,
State#mt4_db_interface_state.dummy_database_for_tests),
    {reply, ok, State#mt4_db_interface_state{dummy_database_for_tests
= NewDict}};

handle_call({delete, SystemId,MT4OrderId}, _From, State) ->
    NewDict = dict:erase({SystemId,MT4OrderId},
State#mt4_db_interface_state.dummy_database_for_tests),
    {reply, ok, State#mt4_db_interface_state{dummy_database_for_tests
= NewDict}}.

on_activation(State) -> {ok,State}.

on_deactivation(State) -> {ok,State}.


(3)
 I a production code the mt4_db_interface module is used:

 {ok, Host, Port, User, Password, DatabaseName} = get_db_config(),
 mt4_db_interface:start_link(),
 mt4_db_interface:connect(Host, Port, User, Password, DatabaseName),...

 mt4_db_interface:get_c2_id_for_mt4_id(...)
 mt4_db_interface:update_c2_id(...)
 mt4_db_interface:delete(...)

(4)
In unit tests mt4_db_interface is switched (dynamic switch - the most
interesting thing in context programming) to "use_dummy_db_for_mt4"
implementation:

 mt4_db_interface:start_link(),
 mt4_db_interface:in_cur_context_activate(mt4_db_interface,
use_dummy_db_for_mt4, what_db_to_use_slot),
 mt4_db_interface:connect("DummyHost", 0, "DummyUser",
"DummyPassword", "DummyDatabaseName"),

 mt4_db_interface:get_c2_id_for_mt4_id(...)
 mt4_db_interface:update_c2_id(...)
 mt4_db_interface:delete(...)


Is there any other person trying context programming in Erlang?

Sincerely,
Bohuslav Svancara


2013/8/9 H.C. v. Stockhausen <hc@REDACTED>
>
> Hello,
>
> I need a DB backend for my application but I'd like to be able to swap
> it out for different DBs if I choose so later on.
>
> I would like to code against an interface and tell the application
> what specific backend to use through config rather than code changes.
>
> Is there a pattern for doing that? Hot code upgrades and multi-node
> are not a real concern at this time - mostly, since I have no
> experience yet with either - however if doing it right means taking
> that into account too I'd also like to learn more about that.
>
> I am thinking of defining a custom behaviour ("my_crud" perhaps), then
> to implement it for various DBs and to also let a config driven
> adapter implement it that I then use to throughout the code to talk to
> the DB layer.
>
> For example, using Mnesia and AWS DynamoDB:
>
> - my_crud.erl (behaviour)
> - my_db_mnesia.erl (implements behaviour)
> - my_db_dynamo.erl (implements behaviour)
> - my_db.erl (configurable adapter that also implements behaviour)
> - my.config
>
> my_db:insert(Key, Value).
>
> Is that a reasonable approach that makes proper use of Erlang and
> behaviours or is this just not how one should do it?
>
> Thank you for any help & best regards,
> Hans
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list