ServicesResourcesConferences Our TeamWeblogsAboutContact
     

Developer Resources

  Architecture Briefings
  .NET Remoting FAQ
  Articles
  Conversations
  Tools and Samples
  Books










Interface design in distributed solutions

NavigationPage 1  -  Page 2  -  Page 3

Feedback: Tell us how you liked our thinktecture conversations!
 

RW

Yes, your example smells of Strong Tagging, i.e. instead of defining the interface of a remote service using lots of fine grained operations with fine grained signatures, define just a single entrypoint (or endpoint) with a set of message schemas. The code receiving the messages interprets them and relais them to the actual servicing operation or maybe even several operations. Well, when did I talk about Strong Tagging... That was way back in 1999 or 2000, Iīd say. I think, SOAP and Microsoftīs Web Services were barely on the radar of many developers.

Ok, back to todayīs options. Today is Indigo (WCF) and WSDL.

So, yes, I guess we agree with regard to service contracts. A service contract contains interfaces for the operations and data-only classes for non-scalar data to be passed back and forth between client and service. Or to be more precise: Data is not described using an interface but rather lies open for anyone to see.

Now, finally, hereīs the question that was on my mind all the time: If a service contract seems to always describe data using a data contract class (!) and a component contract is free to use an interface or a class - how do you map between the two?

The premise behind my thinking is, I define the contract for a component independently of how/where I will use the component. Whether my email component is run locally or remotely should not concern me (much). I just define the component contract for example as follows:

namespace EmailManager.Contract.Local
{
	public interface IEmailServer
	{
		IEmailReceived GetMessage(int index);
	}
	public interface IEmailReceived
	{
	        string fromAddr { get; }
        	string[] toAddr { get; }
	        string subject  { get; }
        	string plainTextBody { get; }
	}
}

The client of my component can get at all the necessary details of an email message, like subject line or the senderīs address, but the client does not and needs not to know, how an IEmailReceived object internally keeps the data. This kind of encapsulation is perfectly fine and even desirable, Iīd say.

Now, at some point the future someone decides to run the email manager component remotely. So the following service contract is defined:

namespace EmailManager.Contract.Remote
{
    [ServiceContract]
    public interface IEmailServer
    {
        [OperationContract]
        EmailReceivedDC GetMessage(int index);
    }

    [DataContract]
    public class EmailReceivedDC
    {
        [DataMember]
        public string fromAddr;
        [DataMember]
        public string[] toAddr;
        [DataMember]
        public string subject;
        [DataMember]
        public string plainTextBody;
    }
}

Thatīs also perfectly reasonable, right?

The service implementaton would then receive remote calls and delegate them to the component. The service would just be a thin layer around the original email manager component and a container or a means to host the component:

namespace EmailManager.Implementation.Service
{
    public class EmailService : EmailManager.Contract.Remote.IEmailServer
    {
        public EmailReceivedDC GetMessage(int index)
        {
            EmailManager.Contract.Local.IEmailServer c;
            c = new ...; // instanciate the interface implementation
           
            EmailManager.Contract.Local.IEmailReceived emailMsgLocal;
            emailMsgLocal = c.GetMessage(index);

            EmailManager.Contract.Remote.EmailReceivedDC emailMsgDataObject;
            emailMsgDataObject = new EmailManager.Contract.Remote.EmailReceivedDC();

            // copy local msg data to data object
            emailMsgDataObject.fromAddr = emailMsgLocal.fromAddr;
            emailMsgDataObject.toAddr = emailMsgLocal.toAddr;
            emailMsgDataObject.subject = emailMsgLocal.subject;
            emailMsgDataObject.plainTextBody = emailMsgLocal.plainTextBody;

            return emailMsgDataObject;
        }
    }
}

My question revolves around the highlighted part of the code: Copying data from objects returned from a component to service contract data objects seems so tedious or awkward. This needs to happen for each data object coming in to or going out from a service operation and which is passed to or comes from a component thatīs doing the real work - but knows nothing of the service contract.

However, I just showed the simpler outgoing message. There is a clear 1:1 mapping between the properties defined by the component contract IEmailReceived interface and the fields of EmailReceivedRC.

But what about the other way round? For a SendMessage(IEmailToSend) method there would be a service operation of SendMessage(EmailToSendDC). Who should define a class implementing IEmailToSend? The email component (and maybe provide a factory method for it)? Or the service implementation?

What Iīd like to see is some kind of pattern, some guidelines as to where to put responsibilities. Do you understand what I mean? And building on such a pattern Iīd like to see automation, i.e. code generation for those simple delegating service implementations.

IR

Hi Ralf,

That's a very good point and creating this kind of code is a boring and mundane task a lot of the developers I know would rather avoid. When consulting with development teams, I usually see three different approaches to solve this challenge of communicating with a service. The first one (which I arguably see most often) is to define the service so that the internally used class will be the same class which defines the data contract. In your case this could for example mean that the data contract itself implements the interface you have previously defined:

[DataContract]
public class EmailReceivedDC : IEmailReceived
{
  [DataMember(Name="fromAddr")]
  public string _fromAddr;
  [DataMember]
  public string[] _toAddr;
  [DataMember]
  public string _plainTextBody;
  [DataMember]
  public string _subject;


  public string fromAddr
  {
    get { return _fromAddr; }
  }

  public string[] toAddr
  {
    get { return _toAddr; }
  }

  public string subject
  {
    get { return _subject; }
  }

  public string plainTextBody
  {
    get { return _plainTextBody; }
  }
}

The second approach I see very often is indeed to simply copy the values over and maybe automate a part of this task using a generic helper method which uses Reflection to copy values field by field from one object to another one with the same structure.

The third option is slightly different. There are a number of developers and architects who would argue that in most cases, a remote interface should look different from a local interface. If your email handling component would for example also deal with attachments, a local implementation could always return these alongside the email (after all, the data is already in your process anyway) wheras the remote service will quite likely provide different methods to retrieve attachments. (To allow you to not retrieve them if you don't need them or are on a low bandwidth connections.) Proponents of this approach, seem to be the true believers in the four tenets of services and would therefore argue that it is actually a good thing to provide a different internal representation. The process of copying information from the in-memory structures to the service-based message structures provides a desirable level of separation for them.

Another interesting part in this discussion is to look at how Exceptions are handled. That's another reasons why remote services usually provide a different - implicit in this case - interface: they can not return Exceptions in the same way a local piece of code can. But starting this discussion would open a completely different can of worms ...

In the end, I think that you can separate the world into at least two major groups with two different usage patterns: if service orientation (and the benefits you achieve from separation alongside concepts like the "four tenets") is crucial, I would tend not to re-use the implementation but to copy the data from the in-memory representation to a (quite often differently structured) service message.

If service orientation on the other hand is just a means to achieve a specific end (for example to avoid having to deploy database clients in the correct version to thousands of PCs [1]), I would usually look into the first approach: to re-use existing implementation instead of copying it over from one object to another 100% identical structure.

Cheers,
-Ingo

[1] I think that a majority of our enterprise customers who use Oracle would like to avoid maintaining another TNSNAMES.ORA at all costs ;-)

RW

Hi, Ingo!

First let me summarize the solutions you mentioned:

  1. The data contract class (in the service contract definition, e.g. EmailReceivedDC) implements the component contractīs interface.
  2. The data contract class is made to look like its corresponding class on the component contract - if there is such a class and not just an interface. By looking the same (but not being the same) an object of one of the classes can be copied easily into an object of the other class using reflection.
  3. Service and component will (often) have quite different contracts and thus explicit copying data is inevitable.

Hm... Although solution 2 seems to save a lot of work, since the code for copying data between the data contract class and a component contract class can be generic, it doesnīt feel right. The coupling between the two data classes is very loose, because it just depends on the equality of field names. (Code generation for data contract classes from component contract types could help avoiding any errors due to spelling mistakes.) Also, solution 2 relies on classes as opposed to interfaces used in the component contract to describe data going in/out.

That leaves me with solutions 1 and 3. Solution 1 I like quite a bit for data thatīs passed to (!) the service and which is supposed to be relayed to the component. The service implements the componentīs data interface on the data contract class and allīs fine for the receiving component. Very elegant, Iīd say. Itīs the solution I alluded with "Who should define a class implementing IEmailToSend? [...] Or the service implementation?" (However, you showed this solution using the interface for data going in the opposite direction. Thatīs the wrong place to use it, since you never get an EmailReceivedDC object from a component, but Iīd say we can neglect that for the purpose of just showing how to implement it in general.)

Solution 3 has considerable theoretical appeal. I like it very much, too, because Iīm a great fan of decoupling services from components. In your example, though, the service doesnīt just delegate operations/data to the component, but rather functions as an adapter. It maps one kind of contract to another kind. "In-proc component thinking" is adapted to service oriented thinking. And thatīs the reason, why I would say, itīs a different type of problem.

On the other hand, both problems are probably related to each other and they hinge on the same question. How should I design a component contract? Should I need to take into consideration how a component might be used? Wheter it will always be used in-proc or whether it will be used "on the rim" of a service, so that it can be called from the outside?

Ideally I could neglect the environment of a component and just follow "in-proc thinking." But that might lead to overhead if later on the component if hosted in a service implementation. For example, if an email object returned from a component contains all data including large attachments, but a service wants to provide separate operations to get at the information pieces, then either the email object has to be retrieved several times by the services or needs to be cached.

So what seems to be more realistic is: Think about the context where a component will be used while you design its contract. Will it be a, letīs say, stateful in-proc scenarios? Or will it be a stateless service oriented scenarios? And to err on the save side would mean, to design the component contract for service orientation, Iīd say. If a component contract is design for in-proc scenarios and a service is wrapped around it, which can look quite differently like your example shows, than this probably will always lead to overhead.

Now, if you design a component contract for in-proc use, I think itīs prudent to minimize the number implementation details in it. Use more interfaces whereever possible - but of course classes are ok to; but keep in mind: a class ties the clients of a component contract to a certain implementation.

If, however, you design a component contract for service orientation, then be aware that data objects (like IEmailToSend) need to be copied to data contract classes when leaving the service or need to be copied from data contract classes, when entering the service. For data objects just being passed into a component this might not be a problem, since the service contract can implement their interface on a data contract class (solution 1 above).

But what about data coming from the component, e.g. IEmailReceived? Are the properties of the interface enough to extract the objects data and put it into a data contract class? Hm... Or is it wiser to use classes for such data objects on the component contract in order to allow for solution 2 above?

As you might guess, Iīm looking for some guidelines as to how to design component (!) contracts. Weīve seen, limiting the choise for operations as well as data to interfaces is not necessary - but less implementation details makes for easier testing. But in how far should service orientation influence component contracts? Should I decide differently on how to define data transport structures when a component is used as a service? Should my choice depend on whether data just leaves the component, just enters it or is in/out? (Of course, service orientation influences the granularity of my operations. In terms of your example: my email component might provide a simple IEmailReceived GetMessage(int) operation, whereas a service would want to differentiate between the main text of an email and its attachments with EmailReceivedTrunkDC GetMessageTrunk(int) and EmailReceivedAttachmentDC GetMessageAttachment(int, int).)

What do you think? Any rough guidelines on your mind?

-Ralf

IR

Hi Ralf,

I think I can totally agree with you here. As a guideline I would recommend the following decision tree:

A) Can you envision that your component is ever used in a remote scenario?

Yes: Go to question (B)

No: Follow regular CBD patterns. You should be fine.

B) Will the complete component interface (or a large part of it) be exposed over the service boundary or only a small subset?

Everything: Try to err on the side of caution and design it with SO patterns in mind. (i.e. in this case I would split the "get" operations into "email with attachment", "email without attachment", "the lits of attachment for an email" and "the content of one specific attachment"). Go to question (C).

Just a small subset: Follow regular CBD patterns and later provide a service based faįade. Go to question (C) for the next question on service interface design.

C) Do you believe in strict decoupling and service autonomy and is this desired in your solution or are you just using a service as a means to an end (for example to avoid deploying database clients to your users' PCs)?

I'm a believer: Manually creating the message's content will be a second nature to you anyway. I think you are already wondering why we might even suggest anything else ;-)

I'm just being pragmatic: You're welcome to use a simple solution (like items #1 or #2 in my previous reply) if you really only use a service as a means to a specific end.

What do you think about this decision tree?

Cheers,
-Ingo

RW

Ingo, this looks very good. This really is a start for component and contract design, Iīd say. Less art, more trade ;-) Hereīs a visualziation of your decision tree (which shows itīs not a tree ;-)

And with that, Iīd say, weīve reached a point, where we can stop for a while and ponder our results. Iīm quite content with what weīve come up with during the course of our conversation. Maybe the answer to my initial question is not as clear cut as I expected it do be... but then now weīve a base to start further thinking and refinement from. For example, what does it mean to "follow regular CBD patterns" regarding component contracts? Or how should solution #2 (automatic copying to/from data contract fields) look like?

It was nice talking to you :-)

Cheers,

Ralf

IR

I totally agree with you: I believe that even though there are only very few simple and final answers in our business, a decision tree like the one we have discerned is a very reasonable means to reach a pragmatic solution for a given problem.

Thanks again for the opportunity to refine my thinking about service contracts!

Cheers,
-Ingo

 

NavigationPage 1  -  Page 2  -  Page 3

Feedback: Tell us how you liked our thinktecture conversations!
 






 
© 2002 - 2006 by Thinktecture, Ingo Rammer and Christian Weyer. All rights reserved. | Contact | Impressum