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

Romain Lenglet rlenglet@REDACTED
Fri Feb 24 07:57:22 CET 2006


Mikael Karlsson wrote:
[...]
> I am still
> missing a component binding contoller. Coming from objectweb,
> have you thought anything about a ( a'la Objectweb) Fractal
> binding (and attribute) controller for Erlang gen_servers?

I was one of the initial designers of Fractal, so I am quite 
fluent in Fractal concepts ;-), and like you I am missing some 
of Fractal features in Erlang. I even started implementing a 
generic Fractal membrane in Erlang. 

A problem with OTP is that it has mostly a static viewpoint: it 
is mostly all about code (modules), and the runtime architecture 
is static (the hierarchy of supervisor is generally static, 
processes have global names set statically, client processes 
depend statically on such global names, etc.).
On the other hand, Fractal has a purely dynamic viewpoint, in the 
spirit of the OSI RM-ODP standard (Reference Model of Open 
Distributed Processing). On some points Fractal is more advanced 
than RM-ODP (separation between membrane and content in every 
object / component), and on some points it is more restricted 
(the only allowed interactions are interrogations, not signals or 
announcements or flows), but it maps to RM-ODP quite well.

The strong point about Fractal/RM-ODP, is that the architecture 
is a dynamic concern: components (objects, in RM-ODP) are bound 
together through (possibly distributed) bindings, that are sorts 
of channels allowing interactions. Think of it as the channels 
and routes in SDL for instance, but dynamically reconfigurable. 
Fractal/RM-ODP allows also composite objects, i.e. objects that 
contain objects. In Fractal, these form hierarchies of control 
domains (composite components do control their content).
Those concepts can be found almost as-is in Erlang/OTP: Erlang 
objects are like RM-ODP objects, hierarchies of supervisors and 
processes are similar to hierarchies of composite components in 
Fractal (hierarchy of control domains)...
Erlang/OTP even offers runtime reflective access to the hierarchy 
of supervisors.

But one feature is missing: Dependency Injection.
In Erlang, bindings are implicit. I.e., there are bindings (in 
the engineering viewpoint, when first sending a message to a 
remote node, a TCP connection is first implicitly open, etc.), 
but creating and destroying bindings explicitly before and after 
sending a message is not necessary for a process.
The problem, since bindings are not explicit, is that it becomes 
impossible to manage the architecture at runtime: it is 
impossible to know which process would send messages to which 
process. Therefore, if I want to replace a process with another, 
there is no general way to replace its name (pid) in the state 
of the processes that depended on it. Hence, I can't replace the 
process.

In Erlang/OTP, a limited solution is the use of naming domains 
(node-local and global). But this is very limited. It makes it 
possible to replace a named process by another process with the 
same name: if client processes access that process through the 
naming domain, and not directly using its pid, then the 
replacement is possible and transparent. However, if for 
instance two processes 'client1' and 'client2' send messages to 
a same process 'server' through its name (e.g. 'server' is a 
name registered in the global naming domain), it is not possible 
at runtime to reconfigure 'client1' and 'client2' to make them 
access two different server processes. The problem, is that 
usually in Erlang/OTP (in all the Erlang programs I have seen), 
the names of server process are statically specified in the code 
of client modules, and there is no way to modify them at 
runtime.
And it is even difficult to modify them statically, because it 
generally requires to modify the code. As a consequence, this 
makes it even difficult to reuse code (modules): a module that 
sends messages to a process named 'myniceserver' cannot easily 
be reused without the module that starts and implements the 
process named 'myniceserver'.

That's where Dependency Injection is necessary.
Cf. Fowler's excellent article: 
http://www.martinfowler.com/articles/injection.html
The purpose of Fractal's binding controllers is essentially to 
offer dependency injection, but they can do more (they can 
create distributed bindings, etc.).
In Erlang, we already have bindings, although implicit, so I 
believe that we need only to add a dependency injection 
mechanism, as an OTP design principle.

Dependency injection would consist in having a naming domain 
local to every process, and letting the mapping between 
process-local names and real process names or pids be done from 
outside of the process implementation (hence, the architecture 
becomes a separate concern, implemented separately from 
functional code).

There are two ways to do that in Erlang. Let's take gen_server as 
an example.

1- constructor injection: simply pass the pids or names of all 
the processes messages will be sent to, in the Args parameter, 
e.g.:
gen_server:start_link(client1, [server1], []),
gen_server:start_link(client1, [server2], []),
gen_server:start_link(client2, [server1, server10, AnyOtherArg], 
[]),
...
In the modules, the domain name could be implemented generically 
as a dictionary in the process state:

-module(client2).

init([FooServerName, BarServerName, Anything]) ->
    Deps = dict:new(),
    Deps2 = dict:store('foo', FooServerName, Deps),
    Deps3 = dict:store('bar', BarServerName, Deps2),
    {ok, #state{deps = Deps3, anything = Anything}}.

handle_cast(Request, State) ->
    Deps = State#state.deps,
    {ok, FooServerName} = dict:find('foo', Deps),
    %% e.g., forward the request:
    gen_server:cast(FooServerName, {hey, Request}),
    {noreply, State}.

Drawback: there is no way to modify dependencies after the 
process is started. This is solved with method 2-.

Alternatively, the process names could be stored directly in the 
State record in that case (one record field for every 
dependency), but it makes reflective access more difficult, cf. 
method 2- below...


2- getter/setter/interface injection: implement gen_server calls 
to get / set dependencies, i.e. to modify the process' local 
naming domain. E.g.:

-module(client2).
...
handle_call({getdep, Key}, From, State) ->
    Deps = State#state.deps,
    {reply, dict:find(Key, Deps), State};    
handle_call({setdep, Key, Pid}, From, State) ->
    %% should check that Key is valid...
    Deps = State#state.deps,
    NewDeps = dict:store(Key, Pid, Deps),
    NewState = State#state{deps = NewDeps},
    {reply, ok, NewState}.


Of course, it is preferable to implement both approaches 
simultaneously. In addition, we could also add as in Fractal the 
distinction between optional and mandatory client interfaces, and 
the distinction between singleton and collection interfaces.
And maybe it would be more efficient to use the process' 
dictionary directly (using get/1 and put/2)...??

Attribute control should be done the same way: through init/1 
parameters, and through gen_server calls ({getattr, Attr} and 
{setattr, Attr, Val}). Although both concerns seem very similar 
that way, they must be separate (i.e. we must not to mix binding 
and attribute control) because the callbacks have a different 
semantics. For instance, when setting a dependency (setdep 
call), one would like to automatically link/1 the client and the 
server process.



The dependency injection implementation above is the very 
minimum, but it allows many things already: transparent 
interposition, application-specific distributed bindings 
implemented in Erlang (e.g. one could implement a transparent  
proxy process between communicating processes, to do load 
balancing between several server processes, or to do group 
communication transparently...), etc.
Of course, if we want to implement generic membranes as in 
Fractal, we would have to add a lot of things around, but 
functional modules would not have to implement more than the DI 
callbacks shown above, just as in Fractal/Java.
My opinion is: KISS for developers, and be as Erlang- and 
OTP-compliant as possible.


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
It tries to translate implementation solutions that make sense in 
object-oriented, statically typed languages, into Erlang/OTP. 
But I think that it does not fit Erlang/OTP well.
For instance, the idea to formally define interface signatures 
statically is a good idea in static typing languages such as 
Java (and I am a strong advocate of that), but does make little 
sense in a dynamic language such as Erlang, in which case it 
restricts flexibility for little gain.
For instance, the set of messages that can be received or sent by 
an Erlang process at a given time, may change during the 
process' lifetime. In RM-ODP terms, its set of server and client 
interfaces mayh change over time. For instance, consider guards 
in  receive statements, or in handle_cast callbacks:

test(State) ->
    receive
        {sayhello, Arg}
        when State#state.acceptsayhello == true ->
            ...
    end.

handle_cast({sayhello, Arg}, State)
    when State#state.acceptsayhello == true ->
    ...

This cannot be captured in ErlCOM or Fractal, which consider that 
the set of client and server interfaces, and their signatures, 
(i.e. the component's type) do not change after component 
creation.

One should not impose such limitations in Erlang. So le'ts keep 
it simple, stupid...
And again, I think that the only thing that should be imposed to 
developers is the implementation of DI callbacks as described 
above.
Any other control should be implemented outside of the functional 
modules' implementations, and even should be made optional.

-- 
Romain LENGLET



More information about the erlang-questions mailing list