[erlang-questions] How to code against an interface rather than an implementation?
Steve Davis
steven.charles.davis@REDACTED
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 <bsva...@REDACTED <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 <h...@REDACTED <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
> >> erlang-q...@REDACTED <javascript:>
> >> http://erlang.org/mailman/listinfo/erlang-questions
> > _______________________________________________
> > erlang-questions mailing list
> > erlang-q...@REDACTED <javascript:>
> > http://erlang.org/mailman/listinfo/erlang-questions
> _______________________________________________
> erlang-questions mailing list
> erlang-q...@REDACTED <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.htm>
More information about the erlang-questions
mailing list