REST and backward compatibility

Hi,

I’m struggling with the backward compatibility of a RESTful interface.
I can’t find any documentation on the standard way to solving this. I
know that as long as you only extend the RESTful interface there is no
backward compatibility issue, but I don’t believe that is a very
realistic situation (I refactor a lot). Say you want to change an
attribute’s name, how would you solve this?

For the solution I’ve got the following assumptions:

  • The interface version should not be placed in the URL (i.e.
    www.mydomain.com/v2/books). The URL defines the resource, the
    interface version is not part of the resource in my opinion.
  • The interface version should not be placed in the parameters (i.e.
    www.mydomain.com/books?version=2). This is an option, but it would
    pollute my urls.
  • Only the first interface version can be defaulted, because changing
    the default will break existing client implementations using the
    previous default. So this default is not really usefull in the long
    run, since only version 1 can use it. But at least this would allow
    people to leave out the interface version description, if they don’t
    have to be backward compatible.

I believe that an ideal solution to this would be to place the version
in the HTTP headers. Only there is no standard defined for it as far
as I can see. But it would be like the following:

POST www.mydomain.com/books HTTP/1.1
Interface-Version: 2
Content-Type: application/xml
Accept: application/xml

For the controller we could then apply a before_filter which rewrites
the param hash to the latest interface version (after determining the
used interface version from the header). The latest interface
definition is then used in the controller actions.

I think this can be quite clean.
Does anybody have know of a standard solution for this problem?

Regards, Bas

[edit_by_author]
The third assumption is incorrect. You must default the last version,
otherwise the non webservice clients (browsers etc.) would get into
trouble. But this does create another issue that every webservice
client must use the version number from the start, also for version 1.
[/edit_by_author]

I don’t think you should bother with version numbers, it’s RPC-like. Why
do
you want to keep an old version of the service alive? I would just force
the
clients to an update, or provide a new service for the updated
resources. But
if you want to use a version number, both examples are equal. All the
information that the server needs to fulfill the request should be in
the
path. It doesn’t matter much if it’s /v2/books or books?v=2.

I would just fork the project and have it running on diffrent domains,
or
rewrite the URL on the incoming webserver to point to different
applications.
Maintaining different versions off an API is going to be hell.

Hi Matthijs,

I’m afraid I cannot just force my paying customers to change their
interfaces. I don’t think you ever can… in real life (only the
planning and synchronized execution alone would give severe
headaches).
Anyway I did some more reading and I think the HTTP Header option is
not so bad. There are X-Headers which basically are free form. So the
header would look like the following:

POST /books HTTP/1.1
User-Agent: ApacheBench/2.0.40-dev
Authorization: Basic YmFzOnRlc3Q=
Host: www.mydomain.com
X-Interface-Version: 2
Content-Type: application/xml
Accept: application/xml

The before_filter is really not so hard to implement. And making the
interface upgrades cascaded (in case of a version delta > 1) will only
require me to add a single mapping for each new interface version,
only defining the mapping with the previous version. Keeping in mind
that interface versions are only increased is case of a non-
incremental extension, I won’t expect to ever go beyond more than 3
interface versions in practice.

So I think your statement that “Maintaining different versions off an
API is going to be hell.” is not necessarily the case. As I see things
now, the backward compatibility issue with RESTful interfaces is
solvable nicely in Ruby on Rails (or in general).

The only remaining question is: Am I doing something really non-
standard and is there a better solution?

Regards,
Bas van Westing

On Feb 7, 6:55 pm, “Matthijs L.” [email protected]