I have a Rails 3 engine which exposes API routes for around 20 controllers. Those controllers represent several different resources at various levels of nesting and are covered by over 500 rspec tests. The API is versioned at v1 using namespaces and a routing constraint based on a version header with a default to v1. This is the versioning system described in a great many blog posts and seems to be best practice.
What none of those blog posts describe is how you actually manage rolling out a new version. I have to make a breaking change to the output of a single controller. This change affects the JSON response of an object by changing the structure of one of the JSON values. This will cause breaks in the index, show, and edit views for that controller.
It's obvious that I can copy all of app/api/v1
to app/api/v2
[1]. I can then make my single change to my new v2 serializer. I've now got a large amount of duplicated code for a version 2 of an API that has made almost no changes. I need to maintain code in two places. I'll probably have to have my whole rspec suite run on the version 2 controllers as well as the version 1 ones with a tiny bit of extra testing for the v2 serializer. This sounds like a horrible idea. We could have some stub v2 controllers for each unchanged controller in the v1 namespace that inherits from the v1 controller. This doesn't sound very nice either.
The best option that I can think of is to have a single controller (in this case probably just a single serializer) inside my v2 API, with some routing magic to check if a controller for the required version exists and to fall back through previous versions until it finds one. The serializer version should also have similar magic to check if one exists for this version and fall back until it finds one. This introduces minimal extra code and doesn't instantly double the duration of my test suite. It would require being able to plug in a function directly into the rails routing logic before it could return a 404 for my missing v2 controllers. Possibly I could analyse the namespaces for all controllers based on the filesystem and generate routes at rails boot time with fallbacks but it would be difficult to manage explicitly removing routes from a previous version of the API.
It seems that we will need to continue doing this for every non-additive functionality/output format change up to the point that each previous version is deprecated and removed. We have an additional unreleased API consisting of ~75 controllers covered by ~4000 specs. What happens when we start externally documenting and releasing these?
Other than batching up API changes which is not feasible at the rate that we release features, how do other people manage this? Is the idea above possible at all? Is there a better way?
[1] Issue one. We're using ActiveModel::Serializers for producing JSON responses. ActiveModel::Serializers doesn't support API versioning, although there seems to be a way around this using ruby magic to pick the correct class.