Interface design in distributed solutions
Navigation:
Page 1 -
Page 2 -
Page 3
Feedback:
Tell us how you liked our thinktecture conversations!
RW
Hi, Ingo!
I didnīt guess, weīd differ on my first easy question that much :-) But thatīs ok for me. So letīs clarify the local contract question first:
Splitting the operation interface into one for sending email and one for receiving email is just fine with me. Whether Iīd do it, probably would depend on the more concrete scenario. To generally say, because sending and receiving emails is defined by two protocols (SMTP, POP3) a component concerned with both operations should reflect this distinction too, I donīt find all too convincing. Why not view it from the component clientīs perspective: The client does not want to know about such details. It just wants to deal with email messages, sending them or receiving them. Why bother with two interfaces? For the client sending and receiving are two sides of the same medal like reading from and writing to a stream.
Using a less brittle identifier for an email message to retrieve than an int sure is a good idea. I did not want to suggest, my int was an optimal choice. I just tried to keep the example simple. And since POP3 uses an index to retrieve messages during a session, I used it too.
Also I agree with you on a pair of methods like BeginSession()/EndSession() (or the usual Open()/Close()). By no means I thought my email interface was complete. Those session-related methods, though, did not add anything to the problem I was concerned with. Thatīs why I left them out.
Ok, now with this out of the way, letīs come back to my original first question I was so sure weīd easily be of the same opinion: Is it ok to use just interfaces in a component contract?
Your answer essentially is "It depends!" :-) Well, what can I say... Mostly in life this is an answer to be on the safe side. But it bounces back the decision to the one asking. Although I acknowledge, a programmerīs life is not easy, I think it does not need as difficult as usually perceived. So as a technology evangelist I want to give developers more guidance with easy to remember "rules". No, I donīt want to limit their choices, I donīt want to constrain them - but I want to make their life easier for 90% of all cases.
A while back you pointed me to the Japanese concept of Shu Ha Ri [1] so let me use it to put my position in context: My personal opinion is, many, many developers are in the Shu and maybe Ha stages. Only few have already reached the Ri level. Very few are masters, many, many are apprentices. This is not to belittle the hard work developers invest in their work. Instead for me it is the result of my perception of our industry: Our industry is still immature compared to the automobile or computer hardware manufacturing industries. And one of the singns of immaturity is a lack of rules. The patterns movement was an overdue reaction to this state of the industry. But we still need more rules, heuristics, and agreement on what one should know ("canon of knowledge"). However, what follows from a lack of rules? Itīs not everyone being a master - but rather the contrary: most everyone still being an apprentice (despite all personal experience). But I digress... ;-)
So, yes, I can agree with your "It depends!" answer - but thatīs an answer on the Ri level or software development. Of course you can use other types than an interface to define a component contract. For example, use an enum to pass a priority level to the sending method. Or due to a lack of widespread use or O/R Mapping define a Typed DataSet as part of a contract. But doing so for me is not where you should start your thinking. Instead Iīd like to put forward the Shu-level rule of: Compose your component contracts from interfaces.
And hereīs why: Because there always will be at least two implementations. One implementation is the "real one", the production component, the one the client uses in the final release of the software.
The other implementation is - a mock-up implementation the client uses during development. And if you are serious about testing, you will use mock-ups during development.
To make more clear, what I mean, hereīs a layout scetch of a small software system, which uses the email component, as Iīd set it up:
client.sln
client.csproj
references contracts.dll
client.cs
mockup.emailmanager.sln
references contracts.dll
sender.cs
received.cs
contracts.sln
contracts.csproj
emailmanager.cs
emailmanager.sln
emailmanager.csproj
references contracts.dll
factory.cs
smptsender.cs
pop3received.cs
exchangereceiver.cs
You see, there are two components (client and email manager service) and a contract. All are set up in different Visual Studio solutions. Doing this physically and visually separates the componentsī code. This allows for easier simultaneous work on the implementations and serves decoupling, because you cannot simply peek into another project in your solution to gain some knowledge about implementational details of a components which should not concern you.
Now, while developer Charles is working on his assignment to implement the client, he is in no way dependent on what developer Eve is doing. Whether Eve has already started developing her email manager component or is busy debugging it is of no real interest to Charles. He is not hampered in any way, because he does not rely on Eveīs work. Of course, in the end Charles wants to see his client to run faultless using the real email manager component from Eve. But for the most time Charles can do without it.
Instead Charles develops his own little email manager (see the mock-up project in client.sln) and tests his client againt it. Once heīs satified he simply replaces the instanciation of mock-up types with the instanciation of types from the real email manager component. (How to make those instanciations easy to change is a question of its own. Maybe we need to talk about it later.)
Do you agree this is how one should go aboud developing a software system? If yes, you need to agree, there always are more than one implementations of any component. (The above example equals a component with an assembly just for simplicityīs sake. A Component of course can consist of more than a single assembly. But that has no impact on my argument.)
Also, I disagree the .NET Framework "bind[s] by method name". It binds by method name plus fully qualified type name plus (!) assembly name. Thatīs what the reference section in an assemblyīs manifest is for. It provides a dictionary of assemblies an assembly is dependent on. And all type usages within an assembly refer to that dictionary. So when running a program and calling a method the CLR first needs to look up the type and then the assembly. If itīs not in memory then it searches it using an algorithm (so as to not rely on a central registry). That means: When I call a method on a class my code expects it to reside in a certain assembly.
As much as I like the binding by name compared to the earlier indexes into VTables it does not solve all problems. To be more precise: It does not allow me to swap between implementations, if they are located in different classes, in different namespaces, in different assemblies. Assembly references in the manifest are static. You can replace an assembly by one with the same name, thatīs fine. But I find that very limiting.
Thatīs why I think, a client should not bind to classes, but to interfaces. A class always implies a certain implementation in a certain assembly. But not an interfaces. So with interfaces in contracts a client is independent where during runtime an implementation is loaded from.
Of course, this entails a factory of some sort. You correctly pointed that out. If this factory should be specific or generic, is not important for our discussion at the moment. Also, the question how the factory can be discovered or how a factory maps an interface to an implementation, is not important at the moment. Dependency Injection or Microkernel frameworks like Spring.NET [2] or Picocontainer [3] provide answers to this question.
Now for abstract base classes: They are similar to interfaces and Iīm not all against using them in contracts. But Iīd argue they tempt to fill them in with at least some implementational details (which would not be in line with what I layed out above) and they obviously confine implementations to a specific root class and class hierarchy. I find that limiting.
So, where are we? In general we agree on many things. And youīre not opposed to describing data entities using interfaces. However, we disagree on the importance of interfaces in component contracts. I think they are more important than you think they are.
What do you say? Shall we move on to my real questoin which is concerned with service contracts as opposed to component contracts? Or to be more precise: It has to do with the relation between service contracts and component contracts.
[1] Shu Ha Ri, http://www.wadokarate.co.uk/shuhari.htm
[2] Spring.NET, http://www.springframework.net/
[3] Picocontainer, http://www.picocontainer.org
Hi Ralf,
I think that we actually agree on most parts of the topic at hand. I think the main difference is that I don't fully believe (anymore) in the existence context-free "best practices". That's why I always try to create some kind of context around a suggestion.
If the context of the solution is a project in which the plan has been made to use TDD and mock-objects, then absolutely, yes! I would totally follow - and recommend - your approach. But I think that this is only one possible option out of lots of different ways to implement projects [1]. It is not necessarily (context free) good or bad to use mock ups. And the approach can be quite costly in larger enterprise systems -- let's say you have a planned set of 200 public classes. Instead of implementing just these 200 artefacts, you will have to create 200 additional interfaces and a total of 400 classes plus factories (or use one of the many available containers ... each of which has its own set of issues). Even though this could (and quite likely even would) improve the quality of the resulting code and the inter-team or inter-developer communication, I think that one has to balance the costs.
But if we agree that - if project decisions and constraints - allow the use of TDD and mock ups, then yes, I think we can shelve this part of the discussion and continue on to service interfaces vs. component interfaces.
Cheers,
-Ingo