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.
The project ActiveModel::Serializers has number issues related to Versioning, one of them provided an idea how to implement versioning via Namespace modules but it was closed 2 days ago followed by one of developer's words:
So the problem with AMS versioning does exist but not solved yet.
Back to the original question:
There is a compromise between inheritance complexity with side effects VS code duplication. In case of having well-tested V1 codebase that should be locked for any modification the maintenance does mean to have no errors while running regression test suite. The version 1 development cycle finished, tests written, contract behaviour signed off. The code duplication V1-V2 makes sense and it avoids regression failures.
I don't agree it's a horrible idea, this is a trade-off between expected behaviour and imaginary convenience with development. It's also not easy to avoid spec suite to be duplicated. Controllers, models can be reused but spec codebase will be more likely duplicated to be 100% confident that new changes don't break previous API version.
Yes, this sounds good and helps to avoid application code (not spec suite though) duplication but requires an additional development efforts with maintenance. What you are trying to do called copy-on-write, only changes are copied over. This is well-known optimisation technique. Nevertheless the HTTP fallback sounds more appropriate.
Imagine you have more than 2 versions of API, and a certain API call has 2 fallback ancestors where second is broken by developer's mistake, will you intercept not only 404 but 500 exceptions as well? What if latest DB scheme version breaks backward compatibility?
This is more like architecture question rather than specific implementation. If the API tends to be big, API design patterns can help to avoid building monolith API that can be difficult to support and maintain.
What would I recommend to do:
If code duplication is not acceptable the other option is to duplicate Rails app and deploy to the same server and dispatch requests with Nginx configuration:
This particular code shows just for an example, I don't say it's a good idea to have 10 different Rails apps with each own version.
Back to original question, API versioning is difficult and for some API-clients it makes sense to have default (latest) API URL endpoint.