Versioning in SOA

 

by Boris Lublinsky

Summary: Service-oriented architecture (SOA) is taking a center stage in enterprise architecture. SOA enables parallel development by multiple disparate teams, each with its own delivery and maintenance schedule. In this article, I will examine service-versioning approaches, allowing service implementations to evolve without breaking existing consumers, leading to more loosely coupled SOA implementations.

The basic idea of service versioning is fairly simple, but its implementation requires strict governance. I will discuss units of versioning; service changes, constituting a new version; service version life-cycle considerations; and version deployment/access approaches. The method-based service versioning proposed here allows one to minimize the impact of versioning and to reduce amount of deployed code. Semantic messaging for service-interface definitions makes service implementation more resilient to change. (12 printed pages)

Contents

Introducing Service Versioning
Units of Versioning
Version Definitions
Service Version Life-Cycle Considerations
Version Deployment/Access Approaches
Versioning of Service Infrastructure
Sidebar
Conclusion
Resources

If there is a constant in IT implementation, it is change. Business conditions change, consequently requiring IT implementations to change. New techniques and patterns allow for better implementation of qualities of service, such as load balancing and failover, security, and so forth. Information technology itself changes continually with the introduction of new operating systems, programming languages, and application servers, for example, that simplify creation and maintenance of new solutions. In fact, a driving force in the implementation of an IT solution is its ability to cope with these inevitable changes.

In the era of monolithic applications, changes were dealt with on an application-by-application basis. Implementation of change, whether for a new business or infrastructure—for example, the introduction of a security policy or requirement, or moving an application to a new software platform—was done for an application as a whole, consuming significant amounts of time and money to complete. On the other hand, because each application was developed by a single team and independent, this approach allowed changes to be contained. As a new version of an application was introduced, the previous version came out of use and could be disposed.

One of the main advantages of a service-oriented architectural style is its ability to efficiently deal with changes. SOA is based on a decomposition of enterprise IT assets and separation of "stable" IT artifacts (services) from "changeable" artifacts (business processes), orchestrating services into IT solutions (processes). As a result, business requirements changes can often be satisfied by either changes to existing processes or creation of new enterprise business processes based on the existing services. This approach allows for much better (faster, cheaper) support for required changes through (re)assembling of a solution based on the reusable enterprise services. Business services become resources, shareable by multiple business solutions, enabling massively parallel autonomous development of services by multiple disparate teams, each with its own delivery and maintenance schedule.

Because every service, in this case, is used simultaneously in multiple enterprise solutions, a change in a business service can have a significant impact on many existing implementations and may, consequently, require changes in each of them. This is not only extremely expensive (requiring a tremendous amount of coordination between development teams and testing, ensuring that none of the multiple service consumers are affected), but also goes against one of the fundamental SOA tenets: Services are autonomous. Autonomy is the fundamental concept behind service orientation. Services should be deployed and modified/maintained independently from each other and the systems that use them.

The problem of dealing with shared, potentially changing components is not a new one. For example, Windows operating system and Windows-based applications rely on the shareable COM/ActiveX components. In the COM case, components are considered immutable and any functional change requires introduction of new component with a new globally unique identifier (GUID). Such approach allows for simultaneous coexistence of multiple components/versions.

In this article, I will discuss service-versioning approaches, allowing service implementations to evolve without breaking existing consumers.

Introducing Service Versioning

One the most popular approaches for dealing with changes is versioning. Versioning assumes simultaneous existence of multiple (different) implementations of the same thing, with every implementation distinguishable and individually addressable. In the case of SOA, service versioning equates to coexistence of multiple versions of the same service, which allows each consumer to use the version that it is designed and tested for (see Figure 1). In this case, a new version of a service is created based on the requirements of one or more consumers, which can start using this new version immediately. The other consumers of this service do not need to switch to using the latest version immediately, but can continue to use the versions of the service they were designed for and tested with. They can switch to the latest version of service, based on their own development and testing schedule. Multiple coexisting versions of the same service in the system allows for the independent life cycles of services and their consumers and minimizes the overall impact of the introduction of changes. Although the necessity of such versioning mechanism may be obvious to anyone who has ever dealt with services, this topic still has not penetrated the mainstream of SOA publications and implementations. The basic idea of service versioning is fairly simple and straightforward, but its implementation requires definition of the following:

  • Units of versioning
  • Service changes, constituting a new version
  • Service version life-cycle considerations
  • Version deployment/access approaches

Bb491124.jour11version01(en-us,MSDN.10).gif

Figure 1. Coexistence of multiple service versions

Units of Versioning

There are two major options for defining units of versioning, each with its own benefits and liabilities.

Based on the current Web Services practices (the most popular SOA implementation technology today), many practitioners propose versioning of the service (including all its operations) as a whole. Although this approach is well aligned with the current object-oriented (OO) and component-based development (CBD) practices, it does not always seem to be appropriate in the case of coarse-grained services. (For further comparison of SOA and OO, see "Defining SOA as an Architectural Style," under Resources.)

When people talk about versioning in their everyday conversations, they usually talk about changes and versioning of the methods, not a service itself. For example, consider an account service that implements three operations: withdrawal, deposit, and transfer. Typically the conversation revolves around changes of the individual operations (in other words, withdrawal), not the account service itself.

Therefore, another option is to define individual service operations (methods) as a unit of versioning. Versioning service operation independently has the following advantages:

  • It allows for immutable services. The service can provide additional methods (versions of methods), some of which can be deprecated over time, but the service itself, its name and classification, never changes. This scheme resembles versioning approaches in popular programming languages, such as Java or C#, where methods on classes are added and deprecated quite often while existing classes that are widely used rarely change.
  • It minimizes the impact of the service changes to consumers. Only consumers using a particular method are affected by a change, instead of all service consumers.
  • It minimizes the overall amount of deployed code. Only methods with a new version get redeployed in the process of introduction of the new version. The code implementing methods that have not changed remains unchanged.

It also has the following liabilities:

  • It calls for deploying each method independently with its own endpoint address(es). (Although such a deployment approach is not mainstream today, it has some advantages, such as providing different service-level agreements (SLA) for different methods within the same service.)
  • It requires a different, slightly more complex, service invocation addressing schema. Instead of specifying a service that it needs to invoke, the service consumer, in this case, needs to explicitly specify the service, the operation, and the version of the operation that it requires.

Despite the requirement for nonstandard service invocation/routing, method-based service versioning provides enhanced flexibility and better aligns service versioning with the versioning practices prevalent for programming languages. It also minimizes the amount of code that has to be redeployed to support a new version. These characteristics make method-based versioning a powerful service-versioning approach.

Version Definitions

Defining what constitutes a new version of the service (method) requires analyzing the possible changes, their potential impact on the consumer's execution, and identifying the ones that will "break" it. Any change in the service, whether it is a change in the interface or implementation that might impact consumer execution, should lead to creation of the new version of service (method). Although the above definition is not very precise and open for interpretation, it provides a good starting point for decision on creation of the new version.

I will further examine the major components of service definition (interface and message definitions) and implementations to determine particular situations that can lead to new version creation.

Service-Interface Changes

Following the adoption of the semantic messaging model, the service method signatures never change (all changes are reflected in the semantic model changes). The service method's interface in the case of semantic messaging is:

servicemethod (XML in, XML out)

Consequently, the service method's interface never changes and should not be considered in versioning definition. Because every method is individually deployed and addressed in a method-based versioning scheme, additional methods can be introduced for the service without impacting existing service consumers. Finally, because all the interface changes are contained in the messaging model, method removal (deprecations), in this case, is equivalent to elimination of some of the enterprise functionality and should happen very rarely. From the consumer point of view, this situation requires (potentially significant) modifications, which entail either internal implementation of the required functionality or usage of a completely different service. In this case, the service method is defined as deprecated and is kept around, while each of its consumers will be able to stop using it (see Service Version Life-Cycle Considerations, later in the article).

Message Changes

As defined earlier, in the case of semantic messaging, the changes in the service interface are contained in the semantic messages changes. These messages are defined using schemas describing their content. Usage of the messaging schemas for defining potential interface changes aligns this approach with XML schema versioning techniques (see "Versioning Support in XML Schemas," under Sidebar), and thus allows for direct representation of versioning in the service messages. Changes in schemas can be broadly defined in three major categories:

Revisions represent document's schema changes with no semantic meaning. For example, a change in white space, formatting, non-normative documentation, comments, and so on. A revision of an already published version must not impact the functionality of either service implementations or consumers.

Additionally, the initial incremental development of a semantic schema, before it is published for production use, can also be treated as revisions of the same version.

Minor changes represent backward-compatible changes to the document schema. Examples of minor changes to the schema include:

  • Changing the optionality of a local element or element reference from required to optional
  • Adding a global element or type
  • Adding optional elements to the existing type
  • Changing type of a global or local element to the new type, derived from the original by adding/restricting optional elements.

Major changes represent non-backward-compatible changes to the document schema.

Examples of major changes to the schema include:

  • Changing the type of a local or global element to the new type, derived from the original by adding/restricting optional elements.
  • Changing the optionality of a local element or element reference from optional to required
  • Adding or removing an enumeration value
  • Removing or renaming a global type or element.

Based on the above definitions, both revisions and minor changes in the messaging schema provide backward compatibility and will not "break" the service contract. As a result they will not impact either service or consumer implementation and do not require service versioning. In contrast, major changes require versioning of messaging schema and, consequently, services.

Implementation Changes

A common misconception is that, because services are defined through the interface—not implementation—changes in the service implementation will not affect service consumers and do not require versioning.

In reality, adherence to the interface does not constitute replaceability of implementations. Replaceability is not defined by interface alone, but instead by the contract—which includes interface, pre- and post-conditions, and certain SLAs—on which the service consumer relies. Consequently, versioning is required when service implementation changes affect the contract on which a particular service consumer relies. Because different service consumers can rely on different parts of the service contract, every service implementation changes should be validated against all existing service consumers to ensure that no contracts will be "broken."

Here are some of the examples of how changes in service (method) implementation can lead to the contract changes:

  • New service implementation supports exactly the same interface (functionality), but changes execution timing (SLA). If a service consumer is invoking this service (method) synchronously, such change can significantly impact service consumer execution. As a result, service versioning might be required. (Interestingly enough, even redeployment of existing service, with changes to its implementation, can lead to the same results.)
  • New service implementation supports the same interface, but changes incoming parameters validations (preconditions). In this case, some requests that had been processed successfully will now be rejected, requiring introduction of a new service.
  • New service implementation introduces a new security implementation (precondition), which often is not reflected in service interface, but done through the configuration settings. (See Web Services Handbook for WebSphere Application Server Version 6.1, under Resources.) In this case, existing service consumers will require sending security credentials; implementation change and creation of the new version is required.

Every one of these scenarios requires analysis of existing service consumers and potentially extensive testing. As a result, a simpler way to decide whether a new version of service (method) needs to be created when the service implementation changes is to maintain a definition of service (method) contract and creating a new version when the contract changes, regardless of which consumers rely on which parts of the contract. When in doubt, it is usually simpler to create a new version.

Service Version Life-Cycle Considerations

One of the important versioning considerations is defining the length of time for which a version of service (method) will be preserved. Extending this period leads to the necessity of maintaining excessive amount of service versions. Shortening the period, on another hand, constrains the time for service consumers to implement required upgrades. The appropriate life cycle for service (method) versions varies significantly and is defined by organization's ability to cope with changes.

Version Deployment/Access Approaches

There are two common approaches to the deployment of service versions:

Covenant or Version Parameter. A covenant is an "if-then-else" agreement ("if you do this then I will do that"). In this case, there is a single endpoint address for all versions of the service (method), as shown in Figure 2.

Bb491124.jour11version02(en-us,MSDN.10).gif

Figure 2. Implementation of versioning using covenant

The covenant effectively implements context-based routing, taking an incoming message and routing it (based on a version parameter, embedded in the invocation message) to the appropriate service version. The benefit of this approach is that it simplifies service addressing from the consumer point of view. The consumer, in this case, uses a single endpoint address to access all versions of a given service (method) and encodes the required method in the invocation message. An endpoint address implements a routing support, invoking a required version.

Although the covenant approach minimizes the impact of introduction of new versions on the service consumers, it introduces the complexity of packaging together multiple versions of a service method. This can lead to class name collisions, database names collisions, and so on. Furthermore, this approach effectively requires a versioning strategy not only for services themselves, but also for the components used for service implementations. Considering the tighter coupling between components, this problem can be even more complex than services versioning.

Further improvement can be achieved by replacing the local router dispatching between the service versions implementation with an external broker (mediator). In this case, all versions can be deployed independently and it is a responsibility of mediator to dynamically resolve endpoint address of the desired service version and dispatch all messages accordingly. However, although intermediaries (mediations) are often touted by ESB publications as a cure for most routing/transformation problems, encountered in service-oriented architecture, there are costs associated with them. Typically, it lowers performance. It also must support the most stringent SLA of all the services accessed through it, which could be a very strong requirement.

Multiple endpoint addresses. In this case, similar to the mediator implementation, every version of a given operation is deployed at its own endpoint address. The difference here is that every endpoint address is directly exposed to a service consumer (see Figure 3).

Bb491124.jour11version03(en-us,MSDN.10).gif

Figure 3. Implementation of versions using directly exposed endpoint addresses

Multiple endpoint addresses assume that a service consumer can resolve endpoint address (typically using service registry) for a required version based on the service/method/version information. The advantage of this scheme is a complete separation of multiple method versions deployment. The drawback is a more complex addressing paradigm, requiring service registry support for resolving endpoint address.

The multiple endpoint address approach typically provides better scalability (one less network hop) and lowers the coupling between multiple versions of the same operation.

Versioning of Service Infrastructure

A variant of service versioning is versioning of the service infrastructure, which can involve the following:

  • Transport changes—for example, switching transport from HTTP to Java Message Service (JMS)
  • Message encoding changes—for example, upgrading proprietary enveloping with Simple Object Access Protocol (SOAP)
  • Changes in addressing schema—for example, introduction of the Web Services Addressing for reply-address specification

In this case, it is always desirable to implement "backward compatibility," ensuring that new infrastructure can "understand" and support messages produced by the old infrastructure and produce messages compatible with it. In reality, it might be too expensive, or even technically impossible.

Moving all of the existing service implementations and consumers to the new infrastructure is typically fairly expensive and time-consuming, requiring versioning support that provides interoperability between two different service infrastructures. The most popular solution to this problem is a service adapter (see Figure 4).

Click here for larger image

Figure 4. Interoperability between service infrastructures (Click on the picture for a larger image)

In this case, when the service consumer supported by service infrastructure 1 invokes the service provider supported by the service infrastructure 2, the invocation goes through the adapter mediating between the service infrastructures. From the point of view of the service consumer, the adapter acts as a service provider. The adapter then invokes the service provider acting as a service consumer.

This implementation can be combined with service-versioning deployment topology (see Figure 4) to provide versioning support for both services and service consumers between different infrastructures.

Versioning Support in XML Schemas

The simplest way of denoting versions in XML Schema is usage of an (optional) attribute at the xs:schema element - version. The content model permits Dewey notation of major.minor version numbers.

Because XML parsers are not required to validate instances using version, it is possible to implement custom representation of version, enabling the parser to include it in the validation process. Using this technique typically requires introduction of a versioning attribute as a fixed, required value for identifying a specific schema version. However, this approach for schema versioning is not very practical. There are several disadvantages:

  • An XML instance will be unable to use multiple versions of a schema representation because versioning occurs at the schema's root.
  • XML schema validation tools are not required to validate instances using the version attribute; the attribute is provided purely for documentation purposes and is not enforceable by XML parsers.
  • Because XML parsers are not required to validate using the version attribute, additional custom processing (over and above parsing and validation) is required to ensure that the expected schema version(s) are being referenced by the instance.
  • Marshaling/unmarshaling of XML documents is very rarely done using direct manipulation of the DOM tree. The prevalent approach to marshaling is generation of the classes, supporting "automatic" marshaling, using tools like WSDL2Java, Castor, EMF, SDO, XSD, XSDObjectGenerator, and so on. In this case, classes are generated in the packages in Java or namespaces in C#, based on the schema namespaces, not the schema version.

Another option for denoting schema version is usage of XML Namespaces. In this approach, a new XML Namespace is used for all major version releases. This approach is well aligned with generation of marshaling/unmarshaling code by allowing generating code in different packages (namespaces), thus enabling a single service consumer to work with several major releases of schema simultaneously.

Yet another option is to keep XML Namespace values constant and add a special element for grouping custom extensions. This approach wraps extensions to the underlying vocabulary within a special extension element. This technique is favored by several industry-standard schemas. For example, the Open Application Group's Business Object Documents (OAG BODs) include a <userarea> element defining custom information that may not be part of the base vocabulary. This approach maximizes the extensibility of the schema constructs (schemas can be both forward- and backward-compatible) without introduction of new namespaces. There are two disadvantages to this approach:

  • It introduces significantly higher levels of complexity into the schema.
  • It does not allow implementation of multiple extensions across different portions of the XML instance, because all extensions must be grouped within the extension "wrapper."

The most scalable approach to versioning of schemas is as follows:

  • Componentization of the overall schema in logical partitions using multiple namespaces, thus containing changes.
  • Defining a new namespace (reflecting the major version information) for every major version of each schema.
  • Denoting every minor version as a schema version in a major version namespace. Because minor versions are backward-compatible, generated marshaling/unmarshaling code also will be backward-compatible.

Conclusion

Dealing with changes is never simple and the complexity usually grows with the size of implementation. Larger implementations typically have more interdependent moving parts, and consequently, the effects of changes are more pervasive. SOA implementations, especially enterprise-wide implementations, are no exception.

Service-versioning approaches, described in this article, leads to the creation of much more loosely coupled SOA implementations. Introduction of simultaneously deployed multiple service versions allow both service consumers and providers to evolve independently, on their own development and deployment schedules. Independent versioning of service methods minimizes the impact of versioning and reduces amount of deployed code. Finally, usage of semantic messaging for service-interface definitions makes service implementation more resilient to changes.

Acknowledgements

The Author thanks Jim McKune, Dmitry Tyomkin, Kiran Achen, and Luc Clement for their contributions.

Resources

 

About the author

Boris Lublinsky has over 25 years of experience in software engineering and technical architecture. For the past several years, he focused on Enterprise Architecture, SOA, and Process Management. Throughout his career, Dr. Lublinsky has been a frequent technical speaker and author. He has over 40 technical publications in different magazines, including Avtomatika i telemechanica, IEEE Transactions on Automatic Control, Distributed Computing, Nuclear Instruments and Methods, Java Developer's Journal, XML Journal, Web Services Journal, JavaPro Journal, Enterprise Architect Journal, and EAI Journal. Currently, Dr. Lublinsky works for the large Insurance Company, where his responsibilities include developing and maintaining SOA strategy and frameworks. He can be reached at blublinsky@hotmail.com.

This article was published in the Architecture Journal, a print and online publication produced by Microsoft. For more articles from this publication, please visit the Architecture Journal Web site.

© Microsoft Corporation. All rights reserved.