Recently Ganesh Prasad has been thinking about that thorniest of subjects:versioning of services within a REST-based architecture. Ganesh has an example to illustrate:
Assume that there is a Resource identified by "/customers/1234". Updating the state of this customer requires a PUT. [But how can] REST handle a change to the business logic implied by the PUT[?]
Since there's no way to change the operation used (e.g., specifying PUTv2) to indicate that the service (business logic) has changed, one option would be to change the URL used for PUT, whilst at the same time maintaining the original identity, e.g., as Ganesh shows:
PUT /customers/v2/1234 and this is different from the identity of the customer, which remains at /customers/1234
This is similar to an anti-pattern that Peter Williams discussed a couple of years back and others before even that. As Peter said in 2008:
I really hate this approach as it implies that an account in one version of the API is really a different resource than the account in a different version of the API. It also forces clients into a nasty choice, either support multiple versions of the API simultaneously or break one of the core constrains of REST. For example, say a client exists for the v1 API that saves references (URIs that include the version indicator) to accounts. Some time later the client is updated to support the new version of the API. In this situation the The client can support both versions of the API simultaneously because all the previously stored URIs point at the old version of the API or it has to mung all the URIs it has stored to the point at the new API. Munging all the URIS breaks the HATEOAS constraint of REST and supporting multiple versions of the API is a maintenance nightmare.
In Peter's approach to versioning he suggests a combination of custom media types and content negotiation (for those backwardly incompatible changes that are sure to occur eventually).
This approach does lead to media types being created, but media types are cheap so we can — and should — have as many as we need. Used properly, content negotiation can be used to solve the problems related to versioning a REST/HTTP web service interface.
But back to Ganesh and the initial approach to modifying the URL, which he also disagrees with.
First of all, it's a mistake to think there are only two places where the version of business logic can be exposed - the Verb and the Resource. The data submitted is an implicit third [...]. But this example only makes me question the whole basis for versioning.
He goes on to question the need for versioning in REST and SOA, asking the questions what is versioning in the first place and why is it needed?
I would say service versioning is a mechanism that allows us to simultaneously maintain two or more sets of business logic in a consumer-visible way.
He questions why this has to be visible to customers in the first place. For instance, if a service can differentiate between types of customer then surely it can apply different business logic internally and opaquely to the invoker (client/customer)? He then posits a further question: Why do we need to maintain two or more sets of business logic simultaneously?
The interesting (and circular) answer is often that business logic happens to be consumer-visible, hence a new version of business logic also needs to be distinguished from the old in a consumer-visible way. This is often stated as the need to support legacy consumers, i.e., consumers dependent in some way upon the previous version of business logic.
The need to support legacy is to ensure that existing contracts and SLAs are not broken when services are (silently) upgraded. This then leads Ganesh to question whether or not the answer to the versioning problem is found not in the use of explicit versions but in abstracting specific details, i.e., perhaps service contracts are too specific and hence too brittle when changes occur. (Similar train of thought to the old loose coupling versus tight coupling that has been one of the principles behind SOA.)
Using an example, Ganesh then goes on to discuss how this approach may be used to tackle the versioning problem. In the example his fictional developer makes a change to a service's business logic after there are existing customers.
The first question is, can this new business logic be applied to all customers, or do we need to keep track of "old" customers and keep applying the old business logic to them? If we can "upgrade" all customers to the new business logic, there is, of course, no problem at all. The interface remains the same. The client application POSTs data to the same URI, and they are redirected in the same way to the location of the newly-created [...] Resource. The business logic applied is all new, but customers don't see the change in their interface [...]
But of course it may be impossible to do this and hence require the maintenance of multiple different instances of the business logic. Ganesh then enumerates the reasons why that may occur:
- "the data that the client app needs to submit has changed, so the change is unavoidably visible to the customer and has to be communicated as a new and distinct contract."
- "there is another business reason to tell two types of customers apart, perhaps to reward longstanding customers with better rates, and this difference between customers is not obvious from the data they submit."
- "the client app somehow "knows" the behaviour of the old version and is dependent on it. In this case, we need a new version just to keep legacy clients from breaking.
According to Ganesh, the third option is artificial and means breaking loose coupling by causing explicit dependencies to occur between customer and backend implementation.
In contrast, the first and second reasons provide their own resolution. If the type of data submitted by the client changes, that is itself a way to distinguish new clients from old ones and apply different business logic to them. In other words, we only need to tell newer customers about the change in the data they need to POST. Older customers don't need to do a thing. Also, if we can somehow derive that the customer is an existing one, even if this is not explicit in the data submitted, we can still apply different business logic transparently.
He concludes with the assertion that ...
Service versions are not really an interface detail. They're an implementation detail that often leak into the interface.
The service versioning problem is something that crops up time and again in a number of areas, not just REST. However, Ganesh believes that REST offers a better, more natural solution to the problem.