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

H.C. v. Stockhausen hc@REDACTED
Sat Aug 10 18:53:10 CEST 2013


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 <bsvancara@REDACTED> 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 <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
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list