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

Steve Davis <>
Sat Aug 10 21:07:01 CEST 2013


Hi,

Are you trying to do something like this (just an example)? 

HTH,
/s

=====
% -*- mode:erlang -*-
{application, my_db, [
  {description, "my_db"},
  {vsn, "0.1"},
  {mod, {my_db_app, []}},
  {env, [
    {my_db, my_simple_db}
  ]},
  {modules, [
    my_db, my_db_app, my_db_sup
  ]},
  {applications, [kernel, stdlib]}
]}.
=====
-module(my_db).

-export([start/0, stop/0]). 
-export([create/1, read/1, update/1, delete/1]).

start() ->
application:start(my_db).
stop() ->
application:stop(my_db).

create({K, V}) ->
gen_server:call(?MODULE, {create, K, V}).
read(K) ->
gen_server:call(?MODULE, {read, K}).
update({K, V}) ->
gen_server:call(?MODULE, {update, K, V}).
delete(K) ->
gen_server:call(?MODULE, {delete, K}).
====
-module(my_db_app).

-define(TTY(X), io:format("~p~n", [X])).

-behaviour(application).
-export([start/2, stop/1]).

start(_, _) ->
{ok, DbModule} = application:get_env(my_db),
?TTY(DbModule),
my_db_sup:start_link([DbModule]).

stop(State) ->
State.
====
-module(my_db_sup).

-export([start_link/1]).

-behaviour(supervisor).
-export([init/1]).

%%
start_link(Opts) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, Opts).

%%
init([DbModule]) ->
Server = {my_db, {DbModule, start_link, [[]]}, permanent, 2000, worker, 
[]}, 
{ok, {{one_for_all, 0, 1}, [Server]}}.
====
-module(my_simple_db).

-export([start_link/1]).

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

start_link(Opts) ->
gen_server:start_link({local, my_db}, ?MODULE, Opts, []).

init(_) ->
{ok, dict:new()}.

handle_call({create, K, V}, _, Db) ->
case dict:is_key(K, Db) of
false ->
{reply, {ok, K}, dict:store(K, V, Db)};
true ->
{reply, {error, {exists, K}}, Db}
end;
handle_call({read, K}, _, Db) ->
case dict:find(K, Db) of
{ok, V} ->
{reply, {ok, {K, V}}, Db};
error ->
{reply, {error, not_found}, Db}
end;
handle_call({update, K, V}, _, Db) ->
case dict:is_key(K, Db) of
true ->
{reply, {ok, K}, dict:store(K, V, Db)};
false ->
{reply, {error, not_found}, Db}
end;
handle_call({delete, K}, _, Db) ->
case dict:is_key(K, Db) of
true ->
{reply, {ok, K}, dict:erase(K, Db)};
false ->
{reply, {error, not_found, K}, Db}
end;
handle_call(_, _, Db) ->
{reply, error, Db}.

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

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

terminate(_, _State) ->
ok.

code_change(_, State, _) ->
{ok, State}.
====
On Saturday, August 10, 2013 11:53:10 AM UTC-5, H.C. v. Stockhausen wrote:
>
> Hi, 
>
> thank you for all your replies. I appreciate your help. 
>
> The DB example, even if applicable to my use case, was perhaps a 
> little too specific. I don't necessarily need to abstract at a CRUD 
> level - a higher business level would work too. What I wanted to learn 
> is how to swap out implementations (at whatever level really), so 
> thank you for your samples, explanations and advise. 
>
> I am happy though to have also learned that my initial ideaI wasn't 
> too bad. If I wanted to allow third parties to substitute their 
> implementations for mine (possibly in a closed source product even?) 
> it could be an option. 
>
> Best regards, 
> Hans 
>
> On 9 August 2013 19:23, Bohuslav Svancara < <javascript:>> 
> wrote: 
> > 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 < <javascript:>> 
> >> 
> >> 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 
> >>  <javascript:> 
> >> http://erlang.org/mailman/listinfo/erlang-questions 
> > _______________________________________________ 
> > erlang-questions mailing list 
> >  <javascript:> 
> > http://erlang.org/mailman/listinfo/erlang-questions 
> _______________________________________________ 
> erlang-questions mailing list 
>  <javascript:> 
> http://erlang.org/mailman/listinfo/erlang-questions 
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20130810/d2165ac6/attachment.html>


More information about the erlang-questions mailing list