Interface design in distributed solutions
Navigation:
Page 1 -
Page 2 -
Page 3
Feedback:
Tell us how you liked our thinktecture conversations!
Ralf Westphal (RW)
Hi, Ingo! Right now I´m pondering some publications
on Component Based Development (CBD). They are supposed to bring together my own
general software architecture framework - the Software Universe
[1] - as well as the more concrete Software Cells
[2]
and Contract First Design
[3,4].
So what I´m concerned with is software at all scales from small desktop systems
to large distributed ERP systems. Of course those systems differ in many regards,
but I think, they also share quite some fundamental aspects and principles.
On one of those aspects I´d like to hear your opinion: the definition of
interfaces or contracts on two different levels: contracts between components
and contracts between services. Within the framework of the
Software Universe, i.e. the holarchy of the "tangible" software artifacts
like Methods, Types, Solutions etc., this concerns the Contracts between
Components and Applications.
The interfaces between such two artifacts are worth a closer look, because
the mark the transition from stack-based to stream-based communication. On all
levels from Component downwards to single Statement communication between a
client and a service uses the stack to pass parameters. Methods call other
Methods and pass parameters on the stack and get results back on the stack.
Likewise you can say Types and Components communicate via the stack. And this is
true because below the Application level within the holarchy all holons live
within the same address space. So communication means calling local procedures.
It´s all very simple and easy.
Now, when you look from the Application upward the holarchy, communication
switches from stack to streams. Different Applications don´t run within the same
address space. So in order to call a method in a another Application and
transfer data back and forth, you need to employ some other means than a stack.
Especially you cannot pass addresses, e.g. object references. That means, you
need to marshal all data as messages to and from the other Application. This
happens usually over streams hiding a TCP/IP communication.
Communication within an Application, i.e. between Components, thus is very,
very, very different than cross-process communication, i.e. between
Applications. Passing data on a stack is so much easier than doing the same for
example over a network. RPC might look like local calls, but it is fundamentally
different.
And I think, this fundamental difference between stack and stream based
communication, should show up in some way in how contracts are defined for
Components or Applications. Don´t you think so, too?
Now, on the side of Components I'm quite sure, contracts should be made up of
interfaces as much as possible. Here´s an example: A server Component offers to
send (SendMessage()) and receive (GetMessage()) emails. Message
are passed in and returned "in one piece". A contract for this service could
then look like this:
namespace EmailManager.Contract
{
public interface IEmailServer
{
IEmailReceived GetMessage(int index);
void SendMessage(IEmailToSend msg);
}
...
The contract defines the messages to go in and out as interfaces instead of
classes or structs.
But before I get to my main question, let me stop here and get some feedback
from you, if we at least agree this far. Ok?
Cheerio!
Ralf
[1] Ralf Westphal, "A journey through the software universe from single
statement to Software Societies" in: Software Cells,
http://weblogs.asp.net/ralfw/archive/2005/05/05/405728.aspx
[2] Ralf Westphal, Software Cells,
http://weblogs.asp.net/ralfw/category/9899.aspx
[3] Ralf Westphal, Contract First Design und Microkernel-Frameworks - Teil 1,
dotnetpro 6/2005,
http://www.dotnetpro.de/articles/onlinearticle1703.aspx
[4] Ralf Westphal, Spiken nicht erlaubt: Contract First Design und
Microkernel-Frameworks - Teil 2, dotnetpro 9/2005,
http://www.dotnetpro.de/articles/onlinearticle1752.aspx
Ingo Rammer (IR)
Hi Ralf! Even though I generally agree regarding the usage of interfaces for
inter-component communication, I have to admit that I would create your
component interfaces in a slightly different way. I have noticed that you
separate the interfaces to the structural data types (the messages) in a send and a receive
interface (IEmailToSend, IEmailReceived), but keep the IEmailServer interface as
a single interface for sending and receiving operations.
This is something I would quite likely do differently based on the
observation of the real world infrastructure surrounding your application. There
are protocols which only allow sending (SMTP), protocols which only allow
receiving (POP3, IMAP) and infrastructure elements which allow both (Exchange
Server's programmability model for example). I would therefore as a first step
split this logic:
namespace EmailManager.Contract
{
public interface IEmailSender
{
void SendMessage(IEmailToSend msg);
...
}
(Just as a side note: passing an int for the message ID might not be
sufficient as this ID would not be scoped. It is therefore not deterministic if
a new message is received on the server side while your application is
enumerating messages. Here, I would quite likely introduce a BeginSession() and
EndSession() operation which can later be used to somehow qualify the meaning or
scope of the message index. A similar thing would also be true for example for
DELE operations in POP3, when the actual deletion of the message only occurs if
you successfully disconnect from the server by sending a QUIT command.)
But let's get to the question of interfaces: When defining interfaces, I
usually follow an approach where I identify implementations which might be
variable at runtime (i.e. cases where there might be more than one
implementation for a given interface). I know that there are different approaches
for creating interface and class hierarchies, like the one you seem to be
following which suggests
that everything which is shared beyond the boundary of a component should be
based on an interface. I would argue that this would in most cases substantially
increase the effort without providing a tangible benefit. As long as you know
for sure that there will always only be a single implementation, I think it is
perfectly ok to bind directly to the implementation.
I believe that a strict definition ("always use interfaces over component
boundaries") was necessary in the past, in COM for example [1], mainly because
of the binary binding mechanisms used. In environments like Java and .NET (which
bind by method name, not by VTable-position) this might be different. In a
number of cases, I think that it might be reasonable to bind directly to an
implementation or to an abstract base class instead of an interface.
In the .NET Framework for example, a lot of the calls into the base class
library itself (which is certainly a component different from the main
application) uses base classes (like System.IO.Stream) or directly use the
exposed implementation classes (like System.Windows.Forms.Form,
System.Windows.Forms.MessageBox, but even System.Web.Mail which would reflect a
similar use case like the one you are suggesting.)
To summarize my approach: I usually tend to follow OO-design principles even
(or especially) on the component boundary.
But even in this case, I think that one needs to evaluate whether or not
using interfaces in this way makes sense. If IEmailSender is implemented by a
class for which there is no specific factory available, it might not make
tremendous sense. After all, if the client would have to use code like the
following, the complete idea of run-time decoupling might be wasted:
public class Client
{
public static void Test()
{
IMessageSender snd = new SmtpMessageSender();
snd.SendMessage(...)
}
}I would therefore quite likely extend the destination component to also include a matching factory:
public class EmailConnectionFactory
{
public IEmailSender GetSender(string protocol)
{
// figure out which instance to return
}
public IEmailReceiver GetReceiver(string protocol)
{
// figure out which instance to return
}
}I would then maybe even combine this with a configuration file like the following to provide for an even larger degree of runtime decoupling:
<configuration>
<emailProtocolHandlers>
<protocol name="SMTP">
<sender typeName="My.Component.SmtpSender" />
</protocol>
<protocol name="POP3">
<sender typeName="My.Component.Pop3Receiver" />
</protocol>
<protocol name="EXCHANGE">
<sender typeName="My.Component.ExchangeSender" />
<sender typeName="My.Component.ExchangeReceiver" />
</protocol>
</emailProtocolHandlers>
</configuration>
But as I said before, I simply think that there can not be a generic answer like "always use interfaces on your component boundaries" or even like "always decouple using a provider pattern" or similar.
A developer might for example just want to create a reusable SMTP sender. If he would just go ahead and create a class like the following, I think it might be reasonable for his team and employer if he'd go ahead and encapsulate it in a reusable component. Even without any factories, interfaces or other means for runtime decoupling:
public class SmtpSender
{
public static void SendMessage(string destinationAddress,
string senderAddress, string subject, string body)
{
// implementation removed
}
}What do you think about this approach? To use interfaces only when you know that there can or will be more than one implementation?
[1] Even as a combination of IDL interfaces and C++ pure virtual classes.
Navigation:
Page 1 -
Page 2 -
Page 3
Feedback:
Tell us how you liked our thinktecture conversations!