 | Level: Intermediate Kyle Brown (brownkyl@us.ibm.com), Senior Technical Staff Member, IBM Michael Ellis (msellis@ca.ibm.com), Solution Architect, IBM
30 Jan 2004 API versioning is a common problem in the design of any distributed system, and Web services are unfortunately no exception. In this article, Kyle Brown and Michael Ellis will outline the scope of the versioning difficulties facing Web services developers, provide some template solutions, and discuss architectures and best practices for addressing the problem.
The correct handling of API versioning has been one of the most difficult issues faced by developers of distributed systems. Various schemes have been proposed, ranging from the laissez faire approach taken by CORBA to the stricter schemes used in DCOM. With the advent of Web services, there are some new features that you can take advantage of that can help alleviate the problem, but the brutal fact of the matter is that versioning has not been built into the Web services architecture. Current products from IBM and other vendors do not directly address the versioning issue, requiring developers to solve the problem through the application of patterns and best practices.
Understanding the Web services API versioning problem is easy. Imagine that we have a simple WSDL document that contains a WSDL operation defined using the fragment in Listing 1. (This operation is derived from an example in the WSDL specification -- see Resources.)
Listing 1. GetLastTradePrice WSDL
<types>
<schema targetNamespace="http://example.com/stockquote.xsd"
xmlns="http://www.w3.org/2000/10/XMLSchema">
<element name="TradePriceRequest">
<complexType>
<all>
<element name="tickerSymbol" type="string"/>
</all>
</complexType>
</element>
<element name="TradePriceResponse">
<complexType>
<all>
<element name="price" type="float"/>
</all>
</complexType>
</element>
</schema>
</types>
<message name="GetTradePriceInput">
<part name="tickerSymbol"
element="xsd1:TradePriceRequest"/>
</message>
<message name="GetTradePriceOutput">
<part name="result"
element="xsd1:TradePriceResponse "/>
</message>
<portType name="StockQuotePortType">
<operation name="GetLastTradePrice">
<input message="tns:GetTradePriceInput"/>
<output message="tns:GetTradePriceOutput"/>
</operation>
</portType>
|
Now, let's say that you have deployed this Web service and that a number of requestors have been written using it. However, in a code review, you discover that floats are perhaps not the best data type for representing dollar values. After all, a price is never $2.33333333333; it's always $2.33. You find that cumulative rounding errors have been causing upstream problems due to the way that trade prices are calculated on the system. As a result, the dollar values returned have sometimes been incorrect. So, you need to change the data type from a float to an integer, where it is understood that the integer value is a price in cents. You can implement this change with as little as one alteration to the schema, as shown in Listing 2:
Listing 2. Updated TradePriceResponse WSDL
<element name="TradePriceResponse">
<complexType>
<all>
<element name="price" type="integer"/>
</all>
</complexType>
</element>
|
However, now you are faced with a new problem. What will happen when all of the existing requestors that are expecting floats receive integers instead? This is the worst example of a subtle type of problem -- the XML that is output in the SOAP envelope may very well continue to be processed by the requestors, even though the answers that they are receiving are clearly not the answers they are expecting. In this case, they are receiving answers that are 100 times larger than they would expect and have no decimal portion.
The problem is that there is nothing explicit in the Web services definition (that is, in the WSDL) that will convey this difference to the requestor. If a developer makes the change above, the older requestors would fail, and the failure would be undetectable to the Web services infrastructure. You need to be able to carefully delineate the kinds of changes in a WSDL document that will not break existing requestors, as well as the kinds of changes that will. You also need a mechanism to ensure that, when the WSDL changes so much that it can break an existing requestor, there is no possibility of an older requestor calling the changed Web service.
Understanding change types
Roughly speaking, there are two types of changes in a WSDL document that cannot break an existing requestor, and several types of changes that can. In accordance with standard industry nomenclature, we will call these backwards-compatible and non-backwards-compatible changes, respectively. The types of changes that are backwards compatible are:
- Addition of new WSDL operations to an existing WSDL document. If existing requestors are unaware of a new operation, then they will be unaffected by its introduction.
- Addition of new XML schema types within a WSDL document that are not contained within previously existing types. Again, even if a new operation requires a new set of complex data types, as long as those data types are not contained within any previously existing types (which would in turn require modification of the parsing code for those types), then this type of change will not affect an existing requestor.
However, there are a host of other change types that are not backwards-compatible. These include:
- Removing an operation
- Renaming an operation
- Changing the parameters (in data type or order) of an operation
- Changing the structure of a complex data type.
So, in a broad sense, you can use two different strategies for handling the different types of changes that can occur.
For backwards-compatible changes, the WSDL document can simply be updated in the repository from which it is made available to requestors, and the existing Web service may be updated. We would recommend that every new edition of a WSDL document be stored in a version-control system, and that XML comments be used to indicate unique version IDs or a version history. However, this is purely for the convenience of the Web service provider, and is not required by the implementers of the Web service requestors.
For non-backwards-compatible changes, you need to take another approach. To solve this problem, begin by using XML namespaces to clearly delineate the versions of a document that are compatible. The mechanism by which this is done depends on whether the SOAP binding is done using the literal- or SOAP-encoded use style in WSDL. In literal encoding, the namespace is specified in the definition of the messages as part of the XML schema namespace definitions; in SOAP encoding, it can be specified within the SOAP binding element. Regardless of the mechanism chosen, a specific namespace value is sent along with every SOAP message and result. This allows a Web service implementation to correctly determine what to do with an incoming message, based on the namespace value.
So, if non-backwards-compatible changes need to be made to a WSDL document, then your first step is to ensure that the namespace for the XML elements resulting from that document is unique. To ensure that the various editions of a WSDL document are unique, we would recommend a simple naming scheme that appends a date or version stamp to the end of a namespace definition. This follows the general guidelines given by the W3C for XML namespace definitions. So, assuming that you are using a literal use parameter, your XML type definitions in the sample WSDL that we have been discussing might look something like the following:
Listing 3. Updated WSDL with unique namespace
<types>
<schema targetNamespace="http://example.com/2003/10/15/stockquote.xsd"
xmlns="http://www.w3.org/2000/10/XMLSchema">
<element name="TradePriceRequest">
<complexType>
<all>
<element name="tickerSymbol" type="string"/>
</all>
</complexType>
</element>
<element name="TradePriceResponse">
<complexType>
<all>
<element name="price" type="float"/>
</all>
</complexType>
</element>
</schema>
</types>
|
Note that in this example we have redefined the namespace to the following value:
http://example.com/2003/10/15/stockquote.xsd
|
The naming convention that this example uses is based on the standard convention followed by the W3C in identifying schemas. It consists of the company name, followed by a date stamp, followed by one (or more) identifiers that semantically (and specifically) denote the particular namespace. We are proposing a departure from the most common approach here: while most W3C namespaces have only year and month parts following the company name, we suggest that it would be worthwhile to add a day of the month portion as well, given that changes may occur more often than once a month.
So, once you have made the decision to change the namespace, you have to determine what to do with old requestors. One option is to generate a failure on the server end if a request for an older namespace is received. Another common option for dealing with this problem is to employ a Web service intermediary (a router, for instance) that determines what to do with Web service requests that come in for any particular namespace. The router could examine the date stamp on the namespace and then route requests from the older namespace to an older version of the Web service, while routing requests from the newer namespace to the new version of the Web service. There are downsides to this approach, however. For one thing, you would have to implement the router intermediary (though there are commercial products, like TalkingBlocks, that can do this). Perhaps more importantly, however, you would have to deploy each Web service twice, at least for the transitional period until all of the older requestors have moved over to the new WSDL.
 |
Service versioning vs. service binding
To further examine the versioning problem, it is helpful to ask two questions:
- What is an interface version, in Web services terms?
- Is the distinction between different interface versions and completely distinct interfaces an important one?
It can be argued that, when the interface to a service changes in a non-backwards-compatible way, in reality an entirely new service has been created. In such a case, unless implementations of the first interface continue to exist, the preexisting service is, in effect, discontinued. From the client's perspective, a service is no more than an interface and some non-functional qualities (such as trust and QoS attributes) that it may claim to exhibit; thus, if the interface to a service changes in a non-backwards-compatible way, it no longer represents an instance of the original service, but is rather a completely new service.
From this point of view, an interface version is always backwards-compatible with the preexisting interface. This can mean either that operations are added while all prior operations are maintained, or that existing operation signatures are changed in a manner that is compatible with the original interface (opportunities for such changes are limited). Further, it is reasonable to expect that earlier versions of a service are not forward-compatible with later versions. Therefore, if a service interface is changed in a manner that is not backwards-compatible, it is not a version of the earlier service; it is a new service.
As we've already suggested, identifying each service interface with a unique namespace provides a means to keep them distinct. Since backwards-compatibility is a requirement for each version, a means to distinguish between earlier and later versions of the same interface is also required. To understand the best way to express a version, an understanding of the binding mechanism is necessary; more on this in a moment.
Our second question, "Is the distinction between different interface versions and completely distinct interfaces an important one?" can now be answered -- and the answer is "Yes." This is because a binding mechanism needs to be able to identify compatible interfaces as opposed to incompatible ones, where compatible interfaces offer the common component in their namespace that the binder is looking for, along with a version identifier that is the same as or later than that of the client.
Service binding
Accepting this view of services leads to another question. What approach should be used to bind a client to a service such that the service can be versioned without breaking the binding? It stands to reason that, in order to ease the versioning of a service, you must eliminate all client-side coupling to volatile aspects of the interface. The aspects that may change are:
- The specific set of operations provided by the service interface
- The URI of the WSDL for the service
- The service interface version number
- The service endpoint.
The service endpoint may change at any time to suit the deployment needs of the service provider. In general, a binding mechanism should not assume a static endpoint.
At this point, important features of a binding approach are starting to take shape. A flexible binding approach should:
- Use indirection to acquire a service endpoint
- Avoid assumptions that any aspect of a service, beyond a compatible interface, is static
- Identify compatible services based on the interface and interface version they exhibit.
Use UDDI as a version-aware service registry
There is a growing set of best practices associated with using UDDI and WSDL in combination. They revolve around the need to publish standard Web service interfaces such that services can advertise compliance and compatibility with those standards.
At present, the best practices are specific to a single version of an interface and do not promote a best practice for service versioning. However, the UDDI data model is rich enough that the current best practices can be enhanced to include service versioning.
The current best practices are based on the prerequisite that a given version of a wsdl:portType should be represented by a unique tModel. Clients of that portType version can do a green pages search of UDDI (a simple unique key search) for services that advertise compliance by associating themselves with that tModel. This relationship is depicted Figure 1, based on UDDI version 2.
Figure 1. Associating WSDL with a service
Under the current best practice, the result of such a search would not include compatible services with later interface versions, since the best practice use case implies an exact match with a tModelKey and the best practice requires that different interfaces have different tModels.
UDDI versioning approach 1: Advertise compliance with several interfaces
However, there is nothing in the best practice (or the data model) that restricts the number of interfaces that a given service may advertise. In keeping with the best practice, a service that is compatible with both an earlier and a later version of an interface can reference the tModel for each in its tModelInstanceDetails collection.
The pros of this approach are that:
- It eliminates the need for a version number.
- There is no need to alter the manner in which green page queries are performed.
However, there are some cons to this approach as well:
- Maintenance of the version collection may become cumbersome as an interface moves through several versions.
- If several service implementations exist, then several
tModel collections must be maintained.
UDDI versioning approach 2: Introduce a version number to qualify the
interface tModel
According to best practices, UDDI bindingTemplates use
structures called tModelInstanceInfos to make reference to standard
interface tModels. tModelInstanceInfo structures may optionally contain a set
of instanceDetails structures, defined as follows:
Listing 4. XSD for UDDI instanceDetails
<xsd:complexType name="instanceDetails">
<xsd:sequence>
<xsd:element ref="uddi:description" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="uddi:overviewDoc" minOccurs="0"/>
<xsd:element ref="uddi:instanceParms" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>>
|
To quote the UDDI spec, "This element can be used when
tModel reference-specific settings or other descriptive information are
required to either describe a tModel-specific component of a service
description or support services that require additional technical data support."
In our case, the reference-specific setting would be the version number
for the interface tModel.
By introducing a version number in the instanceDetails and
altering green page searches to include consideration for the version of the
interface, green page queries become version aware.
However, a version number on its own is not enough. The current
UDDI best practice associates the interface WSDL with the interface tModel under
the assumption that all services declaring compliance will share a common WSDL definition.
Versioned service interfaces do not share a common definition, so that assumption is
not valid in the context of interface versioning. What we are looking for is a
way to decouple the WSDL from the service tModel and associate it with the interface
version number.
The required association can be accomplished by setting the overviewDoc
URL value in the instanceDetails structure (mentioned previously) to the location
of the interface WSDL. The version number and WSDL URL can be viewed as link
attributes for the reference between a bindingTemplate and a standard tModel.
These two alterations to the current best practice provide the necessary means
to accomplish interface versioning in UDDI.
To recap: the use of a version number to qualify the
published interface(s) of a Web service in UDDI requires that an
instanceDetails structure carrying the version number and the URL for the
related WSDL document be introduced in the tModelInstanceInfo for the standard
tModel reference required by the current UDDI best practice for standard
service types.
There is one important benefit of this approach:
- Service versioning can be achieved without requiring service
registrations to carry
tModel references to all of the interface versions with which they
are compatible.
However, there are some downsides as well:
- Collectively, all service implementations must agree on the
version number for a given point in the service interface evolution. This is
because the version number is not in a structure that can be shared among
several implementations. This could lead to a situation in which one service
lists a particular interface as version n, while another service advertises
the same interface as version m.
- The steps required to perform a typical green page query must
be altered to include consideration for the version identifier in the
instanceDetails structure.
- This approach is in conflict with the best practice published by
OASIS.
Which UDDI versioning approach should you use?
In most cases, you should use the first approach. There are a number of problems associated with the second approach:
- All clients must use UDDI green page queries that contradict published best practices.
- There is the chance that the version numbers associated with interfaces will not be used consistently in all implementations.
- The requirements for registering a service are more complex.
From a performance standpoint,
the first approach supports version-compatible searches using nothing more than a
tModel key; the second requires that a query analyze several
UDDI structures, a process which is likely to slow that query.
Summary
In this article, we've examined some of the most common techniques for dealing with service versioning problems in Web services. The approach you choose will depend on a number of factors, but there are enough options that you can find an effective solution to most problems. In particular, we've examined the use of unique version namespaces, as well as the use of version numbers in UDDI, to alleviate the problem. You can use the pros and cons we've listed here to determine the technique that will work best in your environment.
Acknowledgements
The authors would like to thank Dmitry Tyomkin, Boris Lublinsky and the rest of the group at CNA for their help in developing the best practices that went into this article.
Resources
- Version 1.1 of the WSDL specification describes the syntax of WSDL.
- Version 2 of the UDDI specification describes how UDDI is structured.
- A good reference on WSDL and UDDI is
Building Web services with Java: Making Sense of XML, SOAP, WSDL and UDDI
, by Steve Graham, Simon Simeonov, Toufic Boubez, Glen Daniels, Doug Davis, Yuichi Nakamura, and Ryo Neyama (SAMS, 2001).
- Check out these developerWorks articles on WSDL, SOAP, and UDDI:
- "Using WSDL in SOAP applications," Uche Ogbuji (November 2000)
- "Understanding WSDL in a UDDI Registry, Part 1," Peter Brittenham, Francisco Cubera, Dave Ehnebuske, and Steve Graham (September 2001)
- "Understanding WSDL in a UDDI Registry, Part 2," Peter Brittenham (September 2001)
- "Understanding WSDL in a UDDI Registry, Part 3," Peter Brittenham (November 2001)
- Learn more about using WSDL in a UDDI registry:
- The Emerging Technologies Toolkit on alphaWorks includes Apache Axis and the UDDI Client APIs.
- Check out the Web Services Toolkit for Mobile Devices, also on alphaWorks.
About the authors  | |  | Kyle Brown is a senior technical staff member with IBM Software Services for WebSphere. Kyle provides consulting services, education, and mentoring on object-oriented topics and Java 2 Platform, Enterprise Edition (J2EE) technologies to Fortune 500 clients. He is co-author of Enterprise Java Programming with IBM WebSphere, WebSphere 4.0 AEs Workbook for Enterprise JavaBeans (3rd Edition), and The Design Patterns Smalltalk Companion. He is also a frequent conference speaker on enterprise Java technology, object-oriented design, and design patterns. You can reach him at brownkyl@us.ibm.com. |
 | |  | Michael Ellis is a solution architect on the IBM Software Services for WebSphere team and lives in Ottawa, Canada. He specializes in Web services, XML, J2EE technology, and object-oriented architecture and design. You can contact him at msellis@ca.ibm.com. |
Rate this page
|  |