[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