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