SOA Series Part 7: Versioning Your APIs

This is the seventh in a series of seven posts on service-oriented architecture derived from a workshop conducted by Cloves Carneiro and Tim Schmelmer at Abril Pro Ruby. The series is called SOA from Day One – A love story in 7 parts.

Change happens! You will encounter new requirements for your service. What to do when you have existing clients that would break when you changed your API to meet the new requirements? Most of the time, “lock-step upgrades” of your service and all your clients are simply not an option.

The answer: versioning!

You can make changes to an existing service by bumping the version number on its API endpoints. Existing clients will keeping functioning, while new clients can use the updated and new features of our “API V2”.

Where does the version go?

There are many options for specifying a version for the API. Here are the most common approaches we have seen:

In a special API header

The first approach for versioning is to accept an application-specific header in requests destined for your service. An example for use accepting (and including in service responses) a header like 'X-LivingSocial-API-Version: 2'.

RESTful ‘purists’ will probably go this route, as they do not think a version of a resource should ever be part of the resources URI, as it is in essence still the same resource you are describing. A disadvantage convenience for quickly testing an API endpoint from a browser, as most browsers make it hard to add headers when making service requests.

In a query parameter

Another approach is to accept the version as a query parameter, à la /api/cities/123.json?version=2.

Some API designers choose this approach, but we don’t like it, as it seems less ‘obvious’, and muddies the waters around parameters for representations, search terms, and the like.

In a path parameter

The other approach to make the version part of the URI is to include it in the path, e.g. /api/v2/cities/123.json.

While not uncontroversial, the authors usually use this approach, as it is simple, “browser-compatible”, and seems the most intuitive handling of versions to us.

What numbering scheme should I use?

Most implementation we have observed tend to either use consecutive integers (v1, v2, v3, …), or dates (v20140424, v20130811, …). Whatever you do, we encourage using something that allows for easy and efficient ‘comparisons’ to understand which API is the later version. I.e., we discourage using schemes like “v_new”, “v_old”, “vEasterEdition”, or even “v08112013” (to say: if you use dates, use the ISO format, where later dates yield larger integers).

Deprecating APIs

Any API you publish will live for a long time, … especially public APIs! Invest some though pre-launch to consider your deprecation policy.

A very important step is to make sure you have a way to identify your clients for any given version of your API. One approach to help with this we are taking is to require a client_name parameter (preferably as a special header) for every call to our internal services. If you do so, make sure to log such client information. You will find numerous opportunities to use it while your service is in production … even if it is just to perform usage pattern analysis.

We also advise to think about how to contact and notify your clients about any updates to your APIs. Do you have email addresses or other reliable contact details of all client app owners? Internally, we use mailing list for communicating changes, bug fixes, etc.

Along with any API, be sure to publish a deprecation and support policy. That way, client applications can plan ahead, … and you have a better position to tell them get off a version for which you are about to drop support.

For internal APIs, be sure to set expectations and divvy up responsibilities around which team is responsible for upgrading your client applications. Is the client application team responsible to act on it, or will the service team be on the hook? (At LivingSocial, it’s usually the service team that issues pull requests for all client applications to update their usage of the service APIs.)

Walk-through of an example

Let’s look at the context of our sample SOA, and assume that someone in our company’s business department thinks it’s a great idea to not ony show a list of inventory items filtered by category (know as ‘tags’ to us techies), but also allow for presenting the customer a list of cities that our business department would like to categorize (… again, we as techies hear “tagging”).

The engineers get together and think that we can probably best implement this by allowing things other than just inventory items to be tagged inside the existing tags-service.

Let’s walk through the changes to tags-service (in the features/tag_entities branch) that are necessary to allow for tagging cities (or in principle, arbitrary ‘entities’), and exposing these capabilities as a V2 API. We need to do this while keeping the V1 API unchanged so as to not require all other clients of the tags-service to be updated in lockstep with the breaking changes introduced in version 2. The goal is to cause no service interruption for inventory-service’s usage of the v1 API endpoints:

  • The #index, #create and #destroy actions of the TaggedItemsController now need to accept an item_type parameter respectively for filtering, or uniquely identifying, the type of the tagged items for which tags should be listed, created, or deleted.

  • TagsController#index now also accepts a new parameter to filter the tags to be listed by item_type (supporting both inventory_item and city values for this parameter)

  • New V2 versions of the presenters for tags and tagged items are created (and automatically used due you presenter choosing logic inside the ApplicationController). The new presenters include the new item_type, as well as the correct URI for the tagged resource, based on the item type.

  • New Swagger JSON specifications for the v2 APIs are added, and they can best be viewed in tags-service‘ swagger UI by pointing it at this API spec:

1
http://tags-service-development.herokuapp.com/api_docs/v2/api-docs.json

By the way: you can also checkout out these changes we made to cities-service (in the features/tag_cities branch) to have it call tags-service for tagging information about a given city, so that these tags can be displayed in the city JSON representation.

Exercise “API Versioning”

1) Use tags-service’s API V2 to tag some cities with (existing, or newly created tags). * hint: curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v2/tags/bacon/tagged_items.json' -d '{"id":1, "item_type": "city"}'

2) Add a simple “show me all cities tagged with <tag_name>” page to the deals application, using data from cities-service and tags-service

Previous articles in this series

Comments