The Prelims
There are a lot of efforts and words spent around making sure we account for the changes of business in our RESTful services. One of those ‘contingencies’ we often account for is versioning of resources and/or representations. Historically, versioning recommendations were often the following:
- Place a version number, something like ‘v1’, in the URL, presumably high in the URL node hierarchy. For example, api.example.com/v1/users/{id}
- Version early–as in right from the start. Meaning that the initial release should have a version in the URL.
This does have some advantages. First up, it’s obvious which version is in play. It could easily be described as “the path of least surprise” as it essentially makes the version part of every request. It makes for easy testing via a browser, curl or JavaScript client (such as JQuery) since there are no additional settings to adjust before making the request.
However, as our APIs mature and become increasingly linked using hypermedia concepts (HATEOAS), version numbers in the URL cause issues when one API links to another and each doesn’t get a version bump at the same time. It’s almost impossible to support a cohesive migration strategy when linking is involved. With a lot of services in play, this option gets completely untenable. Additionally, from a completely academic REST standpoint, a URL is supposed to be the identifier for a resource. So, conceptually, a URL with a version number in it doesn’t accurately identify a resource–the URL serves more-than a single purpose of identification of a resource and instead also identifies the representation “shape.”
This technique flies in the face of the REST constraints as it doesn’t embrace the built-in header system of the HTTP specification, nor does it support the idea that a new URI should be added only when a new resource or concept is introduced–not representation changes. Another argument against it is that resource URIs aren’t meant to change over time. A resource is a resource.
The Question
In the spirit of agile development, I have a question for you… is a discussion on how to version even relevant? Can we claim YAGNI (“You Aren’t Going-ta Need It”) and encourage people to version as late as possible or not at all? Before you click the ‘back’ button, hear me out…
The Proposal
I’ve been hanging out with a bunch of smart people at API Craft in Detroit this week. A lot of passionate folks are talking about versioning and coming to this conclusion:
Evolve Instead of Version!
With many of the techniques and technologies we’re using today, maintaining backwards compatibility is more possible than ever. With a little forethought and planning, we can leverage these to create RESTful APIs that don’t require versioning–maintaining backwards compatibility.
Use of JSON gets us a long way there as most JSON-parsing libraries support the concept of new properties in responses not causing parse issues for clients. So as long as we don’t change the semantics of existing properties or remove existing properties, our consumers shouldn’t break.
Additionally, by leveraging a linking strategy, clients can eliminate the use of their own hard-coded links in favor of rendering widgets on the UI based on the links in your response payload. For example, if your UI is displaying user details, the JSON representation would contain user properties. In addition, the UI can decide whether to render an ‘Edit’ button based on whether an ‘edit’ link exists in your returned response. Also, the button would leverage the URL exposed by that ‘edit’ link instead of relying on its own hard-coded URL value to perform the edit. The same is true for ‘pagination’ links, supporting ‘first’, ‘last’, ‘next’, and ‘previous’ operations on large collections of data.
While possibly harder to create a client of this nature, the style is a lot more resilient and dynamic, relying on the underlying response in a way that enables changes to the underlying service without breaking the client.
The Stop-Gap (or “Plan B”)
Inevitably there will come a time when an API requires a change to its returned or expected representation that will cause consumers to break and that breaking change must be avoided. Versioning your API is the way to avoid breaking your clients and consumers. And, as mentioned above, the URI should be simply to identify the resource–not its ‘shape’. Therefore, another concept must be used to specify the format of the response (representation).
That “other concept” is a pair of HTTP headers: Accept and Content-Type. The Accept header allows clients to specify the media type (or types) of the response they desire or can support. The Content-Type header is used by both clients and servers to indicate the format of the request or response body, respectively.
For example, to retrieve a user in JSON format:# RequestGET http://api.example.com/users/12345Accept: application/json; version=1
# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=1
{“id”:”12345”, “name”:”Joe DiMaggio”}
Now, to retrieve version 2 of that same resource in JSON format:
# Request
GET http://api.example.com/users/12345
Accept: application/json; version=2
# Response
HTTP/1.1 200 OK
Content-Type: application/json; version=2
{“id”:”12345”, “firstName”:”Joe”, “lastName”:”DiMaggio”}
Notice how the URI is the same for both versions as it identifies the resource, with the Accept header being used to indicate the format (and version in this case) of the desired response. Alternatively, if the client desired an XML formatted response, the Accept header would be set to ‘application/xml’ instead, with a version specified, if needed.
What version is returned when no version is specified?
There are basically three forms of thought on this…
- Return the latest (the most recent) version by default.
- Return the earliest supported version (the oldest supported version) by default.
- Require a version specified in the request and return an error if not specified.
The first option, returning the latest version by default has the most risk for breaking clients, as releasing a new version instantly introduces breaking changes to clients without them knowing it. This could be a bad thing.
The second option, returning the oldest supported version also has the potential to break clients, though more rarely. The potential only exists when an older version gets deprecated and removed from service–when the oldest supported version gets dropped and a new version is now the oldest supported version.
The final option, requiring a version specified in the request, makes things explicit but has the downside of making every request more complex.
For my use cases, I favor option #2. Perhaps it’s a case-by-case discussion, but I highly recommend being consistent. Pick one and stick with it.
Summary
Straight-up, versioning is hard, arduous, difficult, fraught with heartache, even pain and extreme sadness–let’s just say it adds a lot of complexity to an API and possibly to the clients that access it. Consequently, be deliberate in your API design and make efforts to not need versioned representations.
Favor not versioning, instead of using versioning as a crutch for poor API design. You’ll hate yourself in the morning if you need to version your APIs at all, let alone frequently. Lean on the idea that with the advent of JSON usage for representations, clients can be tolerant to new properties appearing in a response without breaking. If you must version a representation, do it as late as possible and use the Accept and Content-Type header combo to accomplish your goal.
What are your experiences and thoughts? Submit a comment below!