Versioning Web Services

Originally [here] but the site is unreliable.

Background

When deploying large networks for web services, particularly when many of the installations are out of our control, the services must be able to interact with different versions of other services. They must cope with older clients using newer services, and older services interacting with newer ones.

Existing work

This is not a new problem.

Possible Solution: 'Loosen up'

The general solution seems to be to 'loosely couple' the service interfaces and carry out the versioning transformations behind. At the most extreme, this consists of a method doIt(XmlDocument) .

This rather defeats the point of having a defined, contractual web interface, and we cannot be sure that the service is going to cope with our input correctly. Indeed we don't even know what input it requires until trying it out or a human reads some documents.

It also only moves the problem from the interface to the implementation; the interfaces of newer and older services may be be compatible, but the way they behave are unlikely to. An older service receiving a new document with different content from a new service might work at best, but is likely to fail, and at worst may behave unpredictably.

Possible Solution: Resiliant Classes

A MSN-TV video by a chap called Doug Purdy was cited as being a good view on versioning:

Basically a set of WSDL-generated classes for a particular object (eg Personv1, Personv2) requires an associated 'resiliant' superclass (Person) that holds version and unknown extra information for classes that it knows nothing about. Decisions about what to do with a WSDL-generated class when it arrives can be based on the version information retrieved from the superclass.

However this makes the resiliant class (Person) quite heavyweight as it needs to be able to interpret any and all WSDL-generated classes. It has to make guesses about WSDL-generated classes that it knows nothing about. This might get particularly difficult in the case of changes rather than additions; in the example given, what happens if Name is split to Forname and Surname? Properties are untyped and undefined, and we're back to the problems we get with the generic doit() method above.

(An interesting philosophical point was that he considered namespaces inappropriate; that a new namespace defined a new type. Which seems correct - but then I do consider different versions as indeed being different types, and trying to use one class to represent a variety of versions can make it very cumbersome)

Some notes on Good Practice.

Java beans should be generated from the WSDL rather than the other way around. This means we can create WSDL that is suitable for the outside world to use in general, rather than Java-oriented WSDL. It also means that our code breaks if the interface changes, rather than the other way around - and broken code is obvious at generate/compile time rather than much later at runtime when someone actually uses that interface.

WSDL -> Java is also the only direction possible when implementing 3rd party interfaces that are defined elsewhere using other toolsets.

However there is the build/coding overhead of producing the beans, the runtime overhead of marshalling/unmarshalling the beans, and we have to handle conversions between several versions of beans. Some XML document schemas can be very large, with matching large and unwieldy beansets generated. Instead, we can in principle just load the message as a DOM and examine it using getElementByTagName() . However we lose compile time checking, and if a tag moves or disappears from an interface definition, we will not realise the mismatch until particular runtime circumstances occur.

Not all WSDL-defined message components have to be blindly represented as beans; for example some parameters might be fed to other services or to transformers, and marshalling and unmarshalling them may be inappropriate. .

SOAPy Beans and Business Objects

SOAPy Beans are those created to rempresent a WSDL model. Business Objects are those that represent objects within the business logic.

Using SOAPy Beans as business objects is often not practical. Real world classes have enumerations, calculated fields, and methods that carry out tasks. They may have properties that should not be publically settable; indeed the whole object may be immutable. Making them SOAPable beans means exposing properties (and a constructor) that may be inappropriate.

Any code that uses a SOAPy bean becomes entirely dependent on that particular web interface - the mechanism we happen to be using to publish the application at that particular time. Of course, this is not important for short-term web services, but will be for large systems with other interfaces (such as grid, or other web services) that have their own messages and therefore their own generated beans.

We can wrap our SOAPy Beans with Business Objects, and/or we can build constructors for our Business Objects that accept a SOAPy bean to construct from. However both of these still leave us with code that is dependent on our publishing mechanism, and we have to create a constructor to take each SOAPy Bean as the interface changes.

[TD: examples]

Suggested Solution

Don't Change

Instead of assuming that web service interfaces change, I suggest you preserve and deprecate the existing interface and add a new one. This puts the work onto the service components to manage versioning, not the client. An old client can continue to use new services through the service's preserved old interface. New clients can include the old client code to use old services.

One layer of multiple interfaces

Axis generates an interface for you to implement around the binding. If you're doing web services more directly, then you'll presumably have a class to handle the SOAP messaging, seperate from the business logic. This is the place to convert between your SOAPy Beans and Business Objects.

For example the datacenter might have an AxisDataServer-v4 which converts between real-world objects and the generated beans for that particular web interface. Later, we might have a new interface which we can call AxisDataServer-v5, for which we will add conversions from all the new v5 generated beans. We may need to adapt the v4 layer if the business logic has changed (indeed it may be driving the change).

If you are providing client libraries, the same applies; as you add new interfaces, leave in the code for the old one and add a converter from a consistent API to the binding layers.

The generated beans are now contained along with the binding and the code that interprets that particular web interface.

This means:

-- MartinHill - Oct2004