Dependency injection in Erlang (Disgression from: Longstanding issues: structs & standalone Erlang)

Romain Lenglet rlenglet@REDACTED
Mon Feb 27 05:03:34 CET 2006


Hi,

> I agree that one should keep it as simple as possible for
> developers, and the above concept, which resembles Fowlers
> Spring approach? allow you, as you state, to do many things
> already.

This is not "Fowler's approach". His (significant) contribution 
is to have pinpointed the name "Dependency Injection", and to 
have separated it from other concerns.

But what is described in his paper is very classical. For 
instance, you can check ObjectWeb's Kilim, a DI framework that 
existed long before Spring and other nowadays' trendy DI 
frameworks:
http://kilim.objectweb.org/

> Binding and
> attribute control could also use library functions to make
> life easier for a developers.

OK, why not.

> Maybe, for Erlang, you could be a bit more "Fractalish" and
> expose the Fractal interface names as well, meaning that you
> could expose the binding controller in a section like:
...
> This would make it easy (but not a must) to use erlang modules
> for your own interfaces
...

I think that you and I went too fast.
Let's not try not to transpose as-is conceptual models and APIs 
such as Fractal's, that were designed for object-oriented 
programming languages. And let's use precise and standard terms.


What is an interface?
Quoting RM-ODP part 2, clause 8.4: an interface is "An 
abstraction of the behaviour of an object that consists of a 
subset of the interactions of that object together with a set of 
constraints on when they may occur. Each interaction of an 
object belongs to a unique interface. Thus the interfaces of an 
object form a partitioni of the interactions of that object."
In the computational viewpoint, there is a distinction between 
server interfaces (respond to / consumes interactions) and 
client interfaces (initiate / produce interactions). (cf. part 
3)

Then, what can be described as an interface in an object-oriented 
language such as Java?
Interactions are interrogations, i.e. method calls. Operation 
signatures are the method signatures defined in Java interfaces, 
so Java interfaces can be described as interface signatures. So, 
a server interface is the set of all the method calls on a given 
Java object, whose method signatures are defined in a given Java 
interface. An interface identifier is therefore a tuple:
{object identifier, Java interface name}.
Fractal additionally introduced local identifiers for interfaces, 
so that an interface reference is instead a tuple:
{object identifier, local identifier}
This has been introduced for various reasons:
- an object can have several client interfaces with the same 
signature, but used for different purposes (and hence that may 
be bound to different server objects), so we need to distinguish 
them with local identifiers (i.e. names that are unambiguous in 
the context of that object),
- in order to unify interface and binding management (and also to 
simplify the ADL), server interfaces also have local 
identifiers, and all interfaces (both client and server) have an 
identifier in a single naming domain specific to the object they 
belong to.

Now, let's look at Erlang.
Interactions are announcements (Erlang message transmissions). 
All messages are received by a process in a single message 
queue, and all messages are accepted. Therefore, I interpret 
this as: an Erlang process has a single server interface, 
materialized by its input message queue.
That interface has a very simple signature: it contains one 
single annoucement signature, that could be specified as:
acceptMessage(Term).
All Erlang processes have a single server interface with that 
same interface signature.
Since there is a single server interface, there is no need to 
give it an identifier, and it is unambiguously identified by the 
process identifier. And since there is only one announcement 
signature defined in every interface signature, there is no need 
to pass its identifier (be it "acceptMessage" or anything 
else...).
However, since an object / process may have several client 
interfaces, it is necessary to distinguish them with identifiers 
in naming domains specific to every process. Cf. the calls I 
defined for Dependency Injection in my mail.

> In this way you could implement interface specification and
> contract checking in erlang modules, which could be more
> dynamic compared to a static interface. The nice thing is that
> you will get a specification for your client (and for
> implementing your server).

If for you "contract checking" equals Meyer's "Design by 
Contract(TM)", then I think that is wrong in the context of 
Erlang.
What is a contract?
Cf. RM-ODP part 2, clause 11.2.1: "An agreement governing part of 
the collective behaviour of a set of objects. A contract 
specifies obligations, permissions and prohibitions for the 
objects involved."
Notably, a binding is one kind of contractual context (i.e. "the 
knowledge that a particular contract is in place, and thus that 
a particular behaviour of a set of objects is required", cf. 
clauses 13.2.3, 13.4.2) that constrains the behaviour of the 
bound objects.
What is a behaviour? Cf. clause 8.6: "A collection of actions 
with a set of constraints on when they may occur."
In Erlang, a behaviour is then a collection of message 
transmissions.

Therefore, all what you want is simply to restrict the possible 
message transmissions between Erlang processes, and to check at 
runtime that such a restriction is respected.
You don't need to introduce interface types (i.e. signatures) to 
do that, unlike in Fractal or ErlCOM, and you don't need to 
introduce server interface names, etc. Let's keep it simple, 
stupid.
In the engineering viewpoint, this kind of checking is the role 
of interceptors, in bindings (cf. RM-ODP part 3, clause 
8.2.1.4). You can do that already with the simple API I 
proposed: an interceptor is simply a filter.

-module(myinterceptor).
init(Args) ->
    {ok, []}.
handle_call({getdep, Key}, From, State) ->
    Deps = State#state.deps,
    {reply, dict:find(Key, Deps), State};    
handle_call({setdep, Key, Pid}, From, State) ->
    Deps = State#state.deps,
    NewDeps = dict:store(Key, Pid, Deps),
    NewState = State#state{deps = NewDeps},
    {reply, ok, NewState}.
handle_cast(RequestToCheck, State) ->
    case check(RequestToCheck, State) of
        {ok, NewState} ->
            Deps = State#state.deps,
            {ok, InterceptedName} =
                dict:find('intercepted', Deps),
            gen_server:cast(InterceptedName, RequestToCheck),
            {noreply, NewState};
        {nok, NewState} ->
            %% log that
            {noreply, NewState};
        Else ->
            Else
    end.
check(RequestToCheck, State) ->
    %% anything you want here...

Instead of directly binding a client process to a server process, 
just bind the interceptor in between:
gen_server:call(Client, {setdep, service, Interceptor}),
gen_server:call(Interceptor, {setdep, intercepted, Server}),

Need anything else? :-)


> I think Fractal has yet another level, differing interface
> names and interface types, meaning that you should maybe bind
> your interface name to another module name.

I explained above the reasons why and when we need interface 
names. In addition, I think that it makes ADL specifications 
easier to read and write...

> This could be 
> quite interesting, the Erlang Corba idl compiler for instance
> generates interface modules with methods for idl specified
> metods like:
>
> ping(OE_THIS, NeededArguments) ->
>     corba:call(OE_THIS, ping, [NeededArguments], ?MODULE).

And then, what would be the interface and interrogation 
signatures corresponding to:
- client side: Pid ! "hello".
- server side: receive Msg -> io:format("~s~n", [Msg]).
Introducing interface signatures and interaction signatures does 
not fit Erlang, and introduce unnecessary arbitrary restrictions 
that would make reuse of existing Erlang programs very hard.

> > For instance, I don't like ErlCOM, which imposes a lot of
> > non-functional code in modules (altough the concepts are the
> > same as in Fractal, both being rooted in RM-ODP):
> > http://www.ist-runes.org/docs/publications/EUC05.pdf
>
> I think a lot of the non-functional code could be hidden if it
> was implemented as its own behaviour.

Not sure. Main part of the code are callbacks that return the 
definitions of the functional interfaces specific to the 
component. That cannot be put in a generic behaviour.

> > It tries to translate implementation solutions that make
> > sense in object-oriented, statically typed languages, into
> > Erlang/OTP.
>
> One problem with ErlCOM, if I understood it right, was that
> you could not have a many clients - one server relationship?

No, I doesn't have such a limitation.

> Interfaces are good from a specification point of perspective,
> implementing them as Erlang modules would give a better
> flexibility than static interfaces and add dynamics (like
> contract checking possibilites).

It is not interfaces that are important, but interactions (i.e. 
in Erlang, sent messages). Contracts are about interactions. 
Interface signatures such as Java interfaces (and optionally 
Design by Contract(TM)'s pre- and post-conditions) are only one 
form of specification one can agree upon.

> But I agree that this should 
> be a possibility, and not imposed. The example that you give
> below could be handled in an Erlang interface module if the
> State (or at least the attribute #state.acceptssayhello) is
> exposed in the interface.

That is one big problem with Design by Contract(TM): if you want 
interesting behaviour specifications, you end up disclosing all 
the internal state of objects, hence you break encapsulation. 
See papers on JML (Java Modelling Language) for a discussion 
about that problem, and possible solutions.
But again, DbC is not the only way to specify and check things 
that can be agreed upon about behaviours. It is only one of the 
most well-known.

Sometimes, writing control code is better. That is one of 
Fractal's mottos: "programmable control". Why invent a 
specification language, when you can already specify control 
things in your favorite programming language (cf. above the 
myinterceptor implementation).
It is just as concise and powerful as formal specification 
languages. And Erlang has pattern matching, etc. that make it 
very usable to implement behaviour checking.

> One problem with a gen_server is that it will kill your client
> if you do not get a response within the Timeout time, 5
> seconds default I think. This is not acceptable if you bind
> many components together expecting them to last for a long
> time. I think it could be a good idea to consider a fractal
> component behaviour, gen_fc ?, overriding the gen_server
> behaviour, making life, or at least the development, easier
> for developers.

I'm not sure if this is necessary, but why not?

I also agree that we don't necessarily need gen_server to 
implement Dependency Injection. My proposal was one example of 
implementation.

-- 
Romain LENGLET



More information about the erlang-questions mailing list