Applied SOA: Part 9–Service Versioning
This is part of a series of blog post about Applied SOA. The past blog entries are:
- SOA Basics
- Service Discovery Process
- Service Taxonomy
- Service Composition
- System Consistency
In this article, I’ll cover Service Versioning.
In SOA a Service usually has many consumers & it should be able to evolve independently of its consumers. By that I mean that when a Service evolves, consumers shouldn’t break. Otherwise we would have a hard time to evolve services.
We can do quite a bit of changes to a Service without breaking existing consumers logic. For instance:
- Changing Service implementation details, i.e. not its behaviour, e.g. tap to another Database, add logging, route information to different systems, etc.
- Adding a new Service Operation to a Service
- Adding optional data-items in message payload
What needs to remain stable for consumers to continue to operate normally is called the Service contract: Service behaviours and information exchange patterns, i.e. schemas & bindings. This definition of Service contract is broader than WCF Service Contracts.
Service Contract can be backward compatible, i.e. a new Service Contract can support the same consumer base (for instance when we add a Service Operation without altering existing ones).
We need to version Service Contracts in order to accommodate for breaking changes. When an evolution of a Service requires to introduce breaking change in its Contract, we need to support the current version until all consumers can be moved to the new version.
We want to keep track of two types of Service Versions: breaking ones and non-breaking changes. Breaking change versions require multiple versions to co-exist in order to support different consumers while non-breaking change versions are useful to determine the capacity of a Service.
A typical versioning scheme is to track breaking change versions with major version number while non-breaking versions are tracked with minor version number. This is the scheme used by Windows Azure REST services for instance.
Concurrent Service Versions
Maintaining more than one Service Contract version at the same time is notoriously hard. Typically you introduce breaking changes because either a business process or a back-end system imposes that change on you. Those changes are often hard to burry under a legacy Service Contract version.
That problem isn’t unlike its equivalent in the component world. How do you evolve a component? You version its interface and try to keep old interface backward compatible as long as you can before deprecating them.
Just a word about Service Behaviours: those are encapsulated in Service Contract along operation signatures. Behaviours are any logic surfaced to the consumer, as opposed to implementation details which aren’t surfaced.
For example, if a Service encapsulates access to a Database, a consumer won’t know which Database the Service read / writes from. The exact Database is therefore an implementation detail.
On the other hand, if a Service operation takes an integer in input and validates the input as being greater than zero by raising a fault if that pre-condition isn’t met, that becomes a behaviour. A consumer depends on that validation: if a change requires the input to be greater than 5, consumer sending values between 1 and 5 will break.
Service Behaviours are hard to document with tools. For instance, WCF Service Contracts do not capture pre & post conditions. This typically require documentation on the side.