Managing changes to APIs is hard. That is no surprise to anyone who has ever maintained an API of any sort. Web services, being a special case of API, are susceptible to many of the difficulties around versioning as other types of APIs. For HTTP based REST style web services the combination of resources and content negotiation can be used to mitigate most of the issues surrounding API versioning.
Let’s assume you have a REST/HTTP web service that has some account resources. Say you can make a request like this:
===>
GET /accounts/3 HTTP/1.1
Accept: application/vnd.mycompany.myapp+xml
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.mycompany.myapp+xml
<account>
<name>Inigo Montoya</name>
</account>
First, you probably noticed that my example uses a
vendor MIME media type to describe the representation. Using a
more generic MIME media type like application/xml is much more
common, at least in my experience. Using generic media types is
perfectly legal but a bit silly. You are not really asking for any
old XML document, but rather an XML document that has a quite
specific schema. Aside from my idealistic rantings, using a specific
media type has some strong practical benefits which are at the core of
this post.
Backwards compatible changes
Often changes will need to be made to expose new behavior of the
system that do not negatively impact correctly implemented clients.
Say, for example, you want to start tracking email address for
accounts. If the application/vnd.mycompany.myapp+xml format
documentation is clear that elements that are not recognized should be
ignored you can simply add a email element to the account
representation.
<account>
<name>Inigo Montoya</name>
<email-address>mailto:prepare-to-die@youkilledmyfather.example</email-address>
</account>
Any client that was created before the addition of the email element will simply ignore it’s presence. Problem solved.
Incompatible changes
Unfortunately, not all changes can be implemented in a way that is backwards compatible. For example, a couple of months after adding email to accounts the sales team sign a deal for 1 bazillion dollars. But the new customer demands that each account be allowed to have more than one email address. After thinking for a while, you decide that the best way to expose that is by changing the account representation as follows.
<account>
<name>Inigo Montoya</name>
<email-addresses>
<email-address priority='1'>mailto:prepare-to-die@youkilledmyfather.example</email-address>
<email-address priority='2'>mailto:vengeance@youkilledmyfather.example</email-address>
<email-address>
</account>
That, of course, will break any clients that are expecting the old
format — so pretty much all of them. This is a place where we can
bring content negotiation to bare. You can simply define a new media
type — say application/vnd.mycompany.myapp-v2+xml — and associate
new multi-email format with it. Clients can then request whichever
format they want. Older clients don’t know the new media type so they
get served the older single email format.
===>
GET /accounts/3 HTTP/1.1
Accept: application/vnd.mycompany.myapp+xml
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.mycompany.myapp+xml
<account>
<name>Inigo Montoya</name>
<email-address>mailto:prepare-to-die@youkilledmyfather.example</email-address>
</account>
Newer clients do know the new media type so they can have access to the new functionality.
===>
GET /accounts/3 HTTP/1.1
Accept: application/vnd.mycompany.myapp-v2+xml
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.mycompany.myapp-v2+xml
<account>
<name>Inigo Montoya</name>
<email-addresses>
<email-address priority='1'>mailto:prepare-to-die@youkilledmyfather.example</email-address>
<email-address priority='2'>mailto:vengeance@youkilledmyfather.example</email-address>
<email-address>
</account>
Everyone gets what they need. Easy as pie.
Alternate approaches
The most commonly proposed approach for versioning REST/HTTP web service interfaces today seems to be to mutilate the URIs by inserting a version. For example,
http://foo.example/api/v1/accounts/3
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.
Conclusion
Changes to REST/HTTP web service interfaces come it three basic flavors, changes to the properties associated with a type of resource, additions of new types of resources and deprecation of obsolete types of resources. If you are following the HATEOAS constraint of REST the approach described here can be used to safely handle all three scenarios.
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.
Related Posts
If you’re interested in REST/HTTP service versioning be sure not to miss the rest of the series.
Comments 16
You hit the nail on the head, Peter. This is the right way to do it(tm), and the right way to use content negotiation. An incompatible (must-understand) change produces a new document type. Old and new clients are supported simultaneously based on their different Accept headers.
Just make sure that every client always supplies an accept header from the very beginning of your project, otherwise you are stuck when you do need to make that incompatible change.
In the long term you should still be aiming to minimise backwards-incompatible document-type change, and build widely-understood document types. Whatever problem domain you are in, you are sure to go through more than a couple of legacy content types before you settle on something that will stand the test of time.
Posted 23 May 2008 at 6:38 am ¶Hi Peter,
I really like this your approach.
I used another approach to the same problem in my current application, by adding an “X-Restful-Interface-Version” header to the HTTP headers.
But your solution makes much more sense.
Regards, Bas
Posted 17 Jun 2008 at 8:24 am ¶I created REST web service.I retrive the xml data using get method by servlet.how to send the data using post method by servlet.
Posted 18 Aug 2008 at 10:07 pm ¶Fundamental to the solution is the version support negotiation using the http:accept header. This will work fine for the GET operation.
Posted 16 Sep 2008 at 3:38 pm ¶But how does this address the ‘create’ scenario? A newer clients may want to create a new resource, but happends to interact with an server only supporting older resource representations.
Since the client is the resource provider in this case, the negotiation is not a part of the request…
Well, if the client is following the HATEOAS principle it will never attempt to create a resource that is not supported by the server. If you are using the links generated by the server to determine next steps, you can never end up making requests against unsupported resources.
Formats should not be a huge problem either. Media type includes not only a specific format but a particular set of semantics around that format. Those semantics includes the format of requests from the client. For example, part of the HTML semantics are that forms are, by default, submitted as
application/x-www-form-urlencoded, therefore a (non-read-only) client should not request thetext/htmlmedia type unless it is prepared to make POST request using that format.A custom media type should have similar semantics regarding the formats of POST and PUT requests. If some incompatible change is needed in the request body formats then a new media type would be required to support such a change.
Posted 17 Sep 2008 at 9:46 am ¶Straight caching through all layers (cdn for ex.) does not work in your approach. The url solution is cachable.
Cheers
Posted 13 Oct 2008 at 5:38 am ¶I like the approach, but wouldn’t it be cleaner to add a ‘version’ (or something similar) parameter to the Content-Type, so you don’t end up with an effectively unique Content-Type for each new version? ‘Accept: application/vnd.mycompany.app+xml; version=1.0′ would be just as expressive, but cleaner.
Posted 13 Oct 2008 at 7:51 am ¶Sounds reasonable, but you should probably include a “Vary” header in the response to indicate to caches that you’re switching on the “Accept” request header.
Posted 13 Oct 2008 at 9:31 am ¶“Managing changes to APIs is hard. That is no surprise to anyone who has ever maintained an API of any sort.”
…and yet there are surprisingly few people discussing these issues. Thanks for doing so.
Posted 13 Oct 2008 at 10:21 am ¶I’m pretty new to REST, but I thought that one URI always had to return the same data? So if I specify I accept any content-type (not sure if that’s possible - but if not, substitute ‘any’ by ‘all’), which version should be returned? The first version ever?
And even if you do that, it still means that with content negotiation you get different representations for the same URI - I was under the impression one of the principal goals behind REST was to have only one representation for one URI.
Comment #6 does have a point about caching, which is one of the reasons I thought one URI == one representation.
(Please note I’m not criticizing you post - it’s extremely interesting (and very well-written) and seems like a great solution to the versioning problem, but I’m confused as to whether it fits the REST goals - please enlighten me.
Also, am I correct to understand from comment #5 that specifying a mimetype (like you would with a POST) is also possible with a PUT?
Posted 14 Oct 2008 at 3:39 am ¶LudoA, A URI names a single resource which may have many representations. Say you had chart of last years earning by month. This chart might have a URI
http://foo.example/charts/42. Serving the PNG, JPG and Flash versions of that chart from the same URL fits perfectly in to the REST model. Each of those formats represents the same thing, the earning chart. The various formats are just different representations of the same information. To support this multiple representation approach we have content negotiation. It allows the requesting agent to tell the server in what format it would prefer the information. And it may specify more than one, each with it’s own precedence.Specifying Accept as
*/*is vile kludge if requesting agent is going to actually interpret the representations it is getting. But to answer your question, if the requesting agent claims it can accept any content type the server should serve which ever representation it prefers. That might the most comprehensive version, or the one that is fastest to generate, or whatever. The requesting agent has said “I don’t care what sort of representation you give me”, so the server is free to do whatever it wants.Comment #6 is either wrong or CDNs are a lot dumber than I would have expected. Martin Atkins (comment #8) is right, in real life the responses should set the Vary header to include Accept so that caches can do the right thing — I left it out to reduce the complexity of the examples. If that header is set correctly it is fairly straight forward to implement caching that works correctly with content negotiation, whether it is used for versioning or for some other purpose. The HTTP spec describes the caching algorithm for handling HTTP traffic in some detail.
And yes, you can specify the content type of the body of a HTTP PUT.
Posted 14 Oct 2008 at 9:28 am ¶Thanks a lot for the very clear(!) explanations - it’s often hard to explain a very simple thing in simple terms, but you succeeded completely :-)
It makes some sense that a URI refers to resource and not to a representation — as I originally thought it did –, given the ‘R’ in URI ;-)
I wasn’t aware of the Vary header - good to know though. The people who designed HTTP really thought this through, apparently.
Posted 15 Oct 2008 at 2:13 am ¶Hi,
I like the approach you describe here, but the majority of changes I’ve seen to web services have been of a non-versionable variety: often they are made necessary by sub-surface application changes that make the old forms impossible to serve. In your example, perhaps the application no longer wishes to expose e-mail addresses at all, or the service provider decides to start supporting a new authentication scheme, both for security reasons? In neither of these cases would it be advisable to offer anyone the old representation.
I definitely share your distaste for sticking version numbers in the URL. The first place I saw this antipattern was in the Del.icio.us API back in 2004. They’re still hosting it from the old domain, still promising to “deprecate early versions aggressively”, and still at version 1, where they will stay for as long as the service remains online.
We had this debate when we were designing Digg’s API, and decided early that version numbers made no sense because they were only defensible for the most inconsequential of changes. The old Del.icio.us is no longer available, you can’t rev back to last season’s TV schedule, stores don’t honor outdated coupons, and web services start and stay at version “now”.
Posted 15 Oct 2008 at 10:47 pm ¶I like the idea, seems fundamentally cleaner than putting version info in the request URL, for the reasons stated above. Some questions/issues:
1) Seems the service is now locked into supporting multiple representations of the same object. Doesn’t this get difficult to manage over time?
2) This quote seems overly simplified: “Well, if the client is following the HATEOAS principle it will never attempt to create a resource that is not supported by the server.” I think the question was valid (post #4 above), though I don’t have a very good answer, either, other than to just say always keep the server newer than the client…
Thoughts?
Posted 16 Oct 2008 at 4:28 pm ¶Michael Case,
This approach assumes that the application wants to support multiple versions of it’s API simultaneously. As Michal Migurski pointed out you might decide that is not acceptable. In my experience truly non-versionable API changes are actually fairly rare, but deciding that you will not invest the effort to support multiple versions is quite reasonable. Assuming, of course you are willing to accept that some changes you might want to make to your API will break many, or all, of the clients.
As for point 2 I don’t think that is oversimplified. If the client is only using URL it has extracted from the server responses and it is following the documented semantics of the server responses, it should not be a problem. For example, if the semantics of the API say you can update an item by PUTing a modified version of the representation to the URI found at
Posted 16 Oct 2008 at 5:41 pm ¶./link[rel='edit']/@hrefof the original representation, the only way that can go wrong is because of a bug.Very well said.
The only thing I’d add is that new versions should be rare; the real value of REST (and the rest, ahem, of the Internet architecture) comes from stable, well-understood formats, not localised ones that change every week.
Posted 03 Dec 2008 at 12:04 am ¶Trackbacks & Pingbacks 12
[…] my previous post on this subject I described an approach to versioning the API of a REST/HTTP web service. This approach has […]
[…] Peter Williams - Versioning REST Web Services (tags: restful versioning webservices architecture) […]
[…] RESTful Web Services 18 05 2008 Peter Lacey posts here and here on how to version RESTful Web Services using custom MIME media types and I find this very […]
[…] Dubray takes issue with my approach of using content negotiation to manage service versioning in HTTP. I actually hesitate to respond to Mr. Dubray because the overall tone of his piece is […]
[…] poring over versioning issues on RESTful web services i stumbled upon Peter Williams post proposing the use of vendor MIME media types to get the versioning under control. I really like […]
Avoid Versioning - Please…
Peter Williams made some good posts suggesting content-negotiation as a means to advertise and ask for version support. This is in contrast to using version identifiers in URIs as is commonly done. This is a neat idea. Peter’s post prompted some debat…
[…] one of our RESTful web services to support versioning of the resources. I came across a post, Versioning REST Web Services by Peter Williams that describes a way to use Accept headers and content negotiation as a way of […]
[…] I’m planning on a bigger post about exactly what our JSON document means, and our mime-types, and everything. For now, a good explaination of the reasoning behind our mime-types can be found over on Peter’s blog. […]
[…] Versioning REST Web Services Managing changes to APIs is hard. That is no surprise to anyone who has ever maintained an API of any sort. Web services, being a special case of API, are susceptible to many of the difficulties around versioning as other types of APIs. For HTTP based REST style web services the combination of resources and content negotiation can be used to mitigate most of the issues surrounding API versioning. (tags: rubyonrails restful versioning) […]
[…] Peter Williams - Versioning REST Web Services (tags: webdev xml rest) […]
[…] Peter Williams - Versioning REST Web Services […]
[…] Versioning REST Web Services […]
Post a Comment