Patterns of Change : a perfect world spoiled by reality



Patterns of Change

Recently, Sam Ruby and I had a discussion about how to cope with change in Web services.  His approach is basically to follow the same model that COM's IDispatch interface used (for the unintiated, IDispatch is the basis for dynamic scripting in the Microsoft COM architecture).  This article offers another point of view (basically, another way of dealing with change that is quite a bit more flexible).

How IDispatch works

Now, I may be a bit rusty on it since it has been quite a while since I've dug into the details of IDispatch, but basically, when a COM object implemented the IDispatch interface, that told the COM runtime that it allowed clients to query metadata about its methods dynamically at runtime.  This mechanism could be used to figure out which methods were exported and what parameters those methods required and which were optional.  This is what allowed COM objects to be used in scripting environments like IIS. You could get away with adding a new method to an objects interface and have scripting clients dynamically start using it without having to create a brand new object interface and without having to recompile your clients.  It is very dynamic, very easy, very flexible, and very slow.

Sam's proposal simply reinvents IDispatch for Web Services by relying on clients using metadata (WSDL descriptions) to detect changes in the interface (there are some subtle differences in what Sam is proposing and what IDispatch did but the basic process is essentially the same).  It's an ok approach, but there are better ones. (I'm sorry, having to parse through metadata every time I invoke a Web service just to see if an operation or parameter has changed just flat sucks.  Late bound interfaces are good in some contexts, but not in all).

Design Patterns: Command vs. Proxy

The vast majority of Web services available today would fall into the Proxy and Adapter design patterns (actually, they would represent a combination of the Proxy and Adapter patterns).  The WSDL interfaces provide an approximation of the underlying object interface with a one-to-one relationship between the methods exposed by the object and the operations exposed in the WSDL.  In fact, most WSDL documents are generated automatically directly from the underlying object interface.  This is a simple recipe for brittle code.  Whenever you add a new piece of functionality to your code, you have to change the interface, which forces your clients to change.

The solution is to decouple your WSDL interface from your underlying code implementation.  This is done by viewing SOAP messages as command value objects and using document style WSDL interfaces rather than as direct RPC method invocations on objects.

Example: Common Data Services

An example of this approach is illustrated in the Common Data Services implementation provided in the Web Services Toolkit.  The WSDL defines just a handful of basic verbs ("Insert", "Query", "Update", "Delete", etc) but leaves it wide open as to the type of data that is being written, queried, updated or deleted.

To illustrate the following SOAP message contains an insert command.  The WSDL only defines the <cds:Insert /> part and says absolutely nothing about the <abc:MyOwnTypeOfData /> element

<Envelope>
  <Body>
    <cds:Insert>
      <abc:MyOwnTypeOfData>Hello Sam!</abc:<MyOwnTypeOfData>
    </cds:Insert>
  </Body>
</Envelope>

On the server side, the CDS implementation in Axis receives this envelope, translates the <cds:Insert /> element into a CDS Insert Command Object and passes it off to the CDS Service Implementation (which is pluggable,  meaning that anybody can implement their own CDS implementation and plug it into the toolkit).  It is the responsibility of the specific CDS service implementation to figure out how to interpret and act upon the CDS Command object.  It is completely up to the implementation to determine whether or not inserting a <abc:MyOwnTypeOfData/> element makes sense.  The web service interface doesn't care one way or the other.

The coolest part about this is that I can create new implementations of CDS that use the basic command objects but that work with any combination of input parameters, and I never have to change my WSDL interface to do so.

Another example of how to design for flexibility is evident in the CDS Batch operation.  The Batch operation is a CDS command value object that represents a collection of CDS command value objects that must all be invoked as a single batch.  There is a base abstract CDS request type.  Anybody can create new CDS command value objects by extending this base type.  These new operation types can then be used in the batch request without ever having to modify the WSDL interface definition  This allows new operations to be added without changing the WSDL interface. 

I didn't do this, but I could also have created a generic operation on my portType that would accept any CDS command value object.  (Now that I think about it, I will add this capability to the next version), allowing me to plug in new operations dynamically.

The point here is simple: WSDL can be treated as a strict IDL contract if your WSDL interfaces are designed with flexibility and evolution in mind. By applying some well known design patterns, you can really do some pretty cool stuff.

 


Copyright © 2002 James Snell.
Last update: 6/25/2002; 9:22:02 PM.