다음을 통해 공유


The Revenge of the Typewriter

I haven’t written anything for my blog for sometime now, other matters have been taking my full attention, but I decided it was about time I restarted it so here goes…

 

I recently had a conversation with a colleague that started an interesting thought process.  He asked me to look at some code he had written to see why it wouldn’t compile.  It was a fairly simple ASMX Web Service Provider/Consumer example.  He had started by defining a single object that was to be the message (let’s name the type “Message”) that would be passed back to the consumer from the call and he had implemented a single “Get” method on the provider.  When he receives the response in the consumer code, the compiler complains that he can’t cast the type “Message” to the type “Message”.  He was somewhat confused.

 

The explanation is simple if you understand two things, Namespaces and type equality.  To clarify the namespace issue first, let’s assume that the original Message object was defined in a separate DLL (so that it could easily be shared by the consumer and producer without sharing the “code”).  The namespace of this DLL we shall call “Common”.  The namespace of the producer code we shall call “Producer” and the namespace of the consumer code we shall call “Consumer”.  Given the following code in the producer:

 

Using Common;

[WebService]

public class MessageSvc : WebService {

                [WebMethod]

                public Message getMessage() {

                return this._message;

                }

}

 

It should be clear that the type being “sent” is Common.Message, but given the following consumer code, what is the type of the object being “received”:

 

Using Common;

MessageSvc messageSvc = new MessageSvcHost.MessageSvc();

Message message = messageSvc.getMessage();

 

Clearly the intent here is that we should receive the message into an object of type Common.Message, which seems quite logical as that is what was “sent”.  In fact it will not work, you will get a compiler error complaining that you can not cast MessageSvcHost.Message to Common.Message – why?

 

The answer is in understanding the code that has been generated for you by Visual Studio (remember the super duper advance word processor that made all the typewriters irrelevant?).  VS works its magic to make all this “complicated” Web Services stuff appear to be just a remote method call on an object.  VS generates a proxy class (MessageSvc in this example) that wraps all the code needed to translate from object calls into SOAP messages across the wire and back again.  Now an interesting thing is going on here – the major advances in 3GL languages that have made them so much better for writing good code in has been the move to ever stronger typing.  In C#, even an enum which is based on an int can’t even be cast directly to a int without a specific action – it is clearly not intended to be an int after all that is why we declared it as an enum.  Strong typing stops us from “blurring” the differences in the types.  In XML WebServices on the other hand a type “isa” some other type if it has the same shape (or in fact even enough of the same shape to meaningfully map it).  It is in fact this “loose” type definition that makes the whole portability thing possible.

 

What happens for you it that VS generates code to define an class with the same name as the XML message root that has a shape compatible with the shape of the XML message.  The fact that it has the same name is irrelevant from a type safety point of view.  Further more it will also be in a new namespace (that of the proxy code which will be MessageSvcHost in our example), further removing any possibility of confusion.  It is into an object of this type that the proxy code will pour the received XML data and thus the received type will be MessageSvcHost.Message, even though the type sent was Common.Message!

 

Is this a major flaw with the whole system?  No, not when you understand what happened and why.  Why doesn’t VS map the type correctly, after all the definition of Common.Message is available to the consumer code in our example?  To understand that we must understand that whereas we do have namespaces in XML (although this is an add-on) and the real message being sent on the wire will be marked as being of a known “type” (ie. name + namespace) it is an XML type name (ie. name + schema reference).  There is no hard mapping between XML Schemas and .NET runtime types – and neither should there be – if there was, what would happen to portability?  Visual Studio does in fact have access to a type that would be of the correct shape but that is only so in this example, “normally” we would not expect that to be the case so it generates one automatically for us.

 

In the Whidbey release of Visual Studio, there will be a feature that will allow you to make this example work as was originally intended by my colleague (by suppressing the automatic definition of a suitable type by Visual Studio and allowing you to supply your own), but before we get too excited about that, let’s see if that is the great idea that it appears to be.

 

Firstly let’s “correct” the consumer code so that it will compile and work:

 

 Using MessageSvcHost;

MessageSvc messageSvc = new MessageSvcHost.MessageSvc();

Message message = messageSvc.getMessage();

 

The actual class names already match, so we need only add a “Using” statement for the MessageSvcHost namespace and delete the “Using” statement for the Common namespace and all will work as the Visual Studio designers’ intended.  So what is the downside?  Well if Common.Message has any real code in it (such as convenient non-default constructors or property accessors etc.) we will not be able to use them.  MessageSvcHost.Message as defined for us by Visual Studio will not include this code (indeed it has no way to know such code exists, because it is not included in the WSDL from which this class definition was derived).

 

Remember Tenet 3 from the four tenets of SO as suggested by Don Box, “Services Share Schema and Contract, not Class”?  To do as my colleague actually intended would be to violate this principle.  Service Orientation can dramatically increase the reach and reuse of your applications, but it does come at a cost.  The cost is understanding that code is presumed never to be portable across boundaries and that means sometimes we have to live without the “niceties” that come with sharing code.  That sounds like a very “purist” attitude I know, after all what is the issue with sharing code on the same platform when we have gone to the trouble to separate out “data code” from “action code”?  I mean providing nice constructors is not a bad thing is it?  No, done carefully and with restraint it isn’t.  You can easily duplicate this “Common” message library to cover another platform because after all as long as the type shares the same “shape” with the XML document it will work across the wire just fine.  The temptation is to add more and more “convenience” code to the type library however and soon you are right back where you were with a portability nightmare and you can’t understand how you got there when Service Orientation was supposed to eliminate this condition.

 

Using fancy word processors to remove the boring repetitive effort that is the world of typewriters is no bad thing – it is the path to much greater productivity.  Unfortunately, when you do not understand what the word processor is doing for you, you can end up in a world of pain very quickly.

 

Beware of the “Revenge of the Typewriter”!

Comments

  • Anonymous
    March 22, 2005
    Thanks for the reminder to remember the tenets of SOA.