Web Services
Strategies for Versioning Web Services
Environment and shifts in roles or responsibilities
Nov. 1, 2007 01:00 PM
As architects and developers continue to design and implement Web Services in distributed environments, they are faced with a versioning issue - namely, how do you deprecate, evolve, and continue to use different versions of the same service with multiple service consumers. This article explores the implementation issues and identifies versioning patterns and strategies that can be employed to resolve them. Practical code examples using Java API for XML-based Web Services (JAX-WS) can be downloaded from the online version of this article at http:soa.sys-con.com.
The versioning issue isn't unique to SOA and Web Services and is an integral part of any software development. API, object, and class versioning have been around for a long time. Unique to the problem space is the environment and shift in roles or responsibilities. In distributed SOA environments control is decentralized - the service producer doesn't necessarily control the implementation technology or design of the service consumer (and vice versa). Moreover, the service producer may not be the same as the deployer and may have no control over the eventual deployment environments. The glue holding the two together is the service contract and the service level agreement. The former is usually the interface (i.e., the sum of WSDL, schemas, policies, etc.) and the latter is usually an out-of-band negotiated agreement defining the quality of service requirements (i.e., the sum of the "illities" - scalability, reliability, availability, etc.) that the implementation provides. Architecturally the contracts can be producer- or consumer-driven.
Given this framework the architectural problem space is something like this. Consider that a service S1 is developed and consumed by clients C1. Business or technical requirements changes to the original service implementation result in service S2. The consumer space for this service is C2 as shown in Table 1.
If the consumer space for Service Sn were limited to Cn there would be no problem. Only new clients use new services. However, if this space is a defined by set (C1U Cn) or (C1 U Cn+1) - then versioning needs to be addressed.
The WC3 Technical Architecture Group (TAG) categorizes versioning schemes that mitigate coupling issues into four categories that we can adapt to our problem space:
- None - Service Sn doesn't distinguish between different consumers and must therefore tolerate all changes - Service S1 handles request from C1 U Cn
- Backwards-compatible - Service Sn handles requests from C1 U Cn
- Forwards-compatible - Service S1 handles requests from C1 U Cn
- Big Bang - Service S1 aborts processing for requests from C2 U Cn
Which of these approaches should to be applied to a given service architecture is purely a function of the application and the business use case. In complex enterprise environments, the extremes of the scale - i.e., handling all versions or not handing any versions implicitly - limit the value of the system and adversely affect coupling, manageability, and extensibility and affect the long-term return on investment. For example, with the Big Bang scheme, since S1 is coupled to C1 any change to it will ripple through all the consumers or require the development and deployment of a new S2 service that now can only be consumed by consumers C2. The backward- or forward-compatible schemes and the change associated with them provide the most value and flexibility for a SOA.
To decompose the problem further, let's distill the meaning of this change. Given a Web Service contract, change can be incremental, decremental, or transparent.
- Incremental change: Involves adding - new optional headers, operations, input or output messages (as long as they result in a new operation), new schema types or adding to existing types, or introducing new policy assertions.
- Decremental change: Involves removing or renaming required headers, operations, input or output messages, or their order from operations, schema types, structures from existing types, or removing policy assertions.
- Transparent change: There's no change in the interface or contract with the consumer however the service implementation or behavior is changed.
In general incremental changes are backward-compatible and decremental changes aren't. Deprecating parts of a service contract is different from decrementing it - decremental changes result in breaking backward-compatibility and have to be addressed out-of-band. Compatible changes would be the same as those applicable to a programming language. For example, the Java Serialization specification lists compatible changes as changing the implementation of a method, adding new methods, adding new instance variables and incompatible changes such as changing the type of a variable or moving a class in an inheritance hierarchy. (
Figure 1)
From an architect's perspective, given incremental or transparent change, backward/forward-compatible versioning schemes for service implementation and deployment can be broadly be put into two main categories that can be characterized:
- Invocation context-based versioning
- Invocation content-based versioning
We'll examine these from the service producer and service consumer perspectives.
Invocation Context-based Strategies
These strategies rely on discriminating versions of Web Services based on the context under which the services are invoked. An invocation context can be construed either from a deployment context, physical endpoint location, or combination of both.
The advantages of context-based strategies include flexibility to introduce intermediaries and additional layers of value-added services like content-based routing, the flexibility to provide separate infrastructures to manage scalability, reliability, and availability, or introduce network-based SOA appliances.
Implementation Scenario
From a service consumer's perspective the different versions are:
Both these strategies rely on deriving the context based on combinations of host, port, and URL pattern in the endpoint address. The latter separates version concerns by deploying on a different host or opening additional ports. This is generally suitable when a version of the code is being completely retired, maintained by different groups/teams, or has significant underlying dependency differences. It may also be suitable for certain containers where the deployment model for EAR files is port-based.
From a service developer's perspective both these service endpoints can be implemented by the same class or a class hierarchy if the operations are being overloaded (effectively introducing incremental change). However, separating the endpoints allows the implementation to determine the consumer version at runtime by deriving it either implicitly or explicitly from the invocation context.
About Sameer TyagiSameer Tyagi, an enterprise architect, focuses on strategy, design, and implementation of large-scale enterprise applications. His publications include several books and periodicals on technology. He can be reached at http://sameertyagi.blogspot.com.