3

I had a discussion with a colleague of mine today regarding versioning of a REST Api.

We currently have an API with support for versinong by providing the correct version in the URL. So for instance:

https://ourapi.com/api/v1/something (where v1 would be version 1)

Now we want to make a few contract changes to one of our "endpoints", and with that said.. we would like to introduce a new version of the API. The discussion we had is basically, should we version by "endpoints" or should we version the api as a whole?

Lets pretend we have three endpoints

We now make a contract change to the https://ourapi.com/api/v1/animals-endpoint and we keep the old version unchanged as version 1, and the new version (version 2) can now be called uisng https://ourapi.com/api/v2/animals

Should we now also "migrate" the other two endpoints to version 2 as well, even if we havent changed anything? So for our entire API we would have the following endpoints:

Or should we just version the changed endpoint?

The reason for versioning all endpoints would in this case be that my colleagues application currently uses a "base-url-variable" (containing https://ourapi.com/api/v1), and then for each "component" in the application he concatenates the base with the following path. So if we would simply up the version by one for all endpoints, all he would need to do is change the base-url, instead of changing the version for each component. Another "pros" for this would be that if someone visits the API documentation (swagger-ui in our case) he/she would only see a handful of possible endpoints for version 2, while many more for version 1. Which might be confusing (which one should I use?.. are the version 1 endpoints soon to be obsolete/deprecated? etc..?)

However... Im not so sure that´s the correct way of doing things.. Since after all.. we haven´t really changed anything for the other endpoints, so why should they be "upped" a version? Wouldnt that just make it unclear of whats actually changed between version 1 and version 2? Besides that, in this case, version 1 and version 2 of the people and fruits-collection would still both use the same code.. so any changes to that endpoint would be reflected to all versions. (this might be a separate problem) Is there any common pratice to this? (I guess not, but would be nice to hear your thoughts..)

5 Answers 5

4

Whether the scope of your versions covers your entire codebase or specific REST resources is up to you.

Semantically, I would expect that /v1/people expresses an application version and /people/v1 expresses a resource version. There's no technical reason for it to be this way, but generally speaking the hierarchy of your application reads from left (high) to right (low).

Whether or not you are able to version your resource endpoints individually depends on whether those resources are independent or not. If a change to one may lead to a change in another; they're not really independent, and then it seems better to stick to a single application-level version.

If both are possible, I would favor resource versions over application versions; but resource versions demand a cleaner separation than an application version would require. I don't know your codebase so I cannot make this call for you.

In concrete terms, when you upgrade an application version to v2 and the Foo endpoint hasn't changed since v1; explicitly create a new v2 endpoint for it anyway, and then have that endpoint pass the buck to the earlier version's endpoint. By default, you could use inheritance here as it cuts down on the need to boilerplate all the endpoint methods; but composition would be cleaner even though it requires a bit more effort to set up and maintain.

This setup allows you to upgrade behaviors if needed (and default to the previous version if not); and it also chains well in cases where this endpoint doesn't change across several version upgrades (v3 endpoint passes the buck to v2 endpoint, which passes the buck to v1, and so on for future versions).

2

Making URIs contain version numbers is misusing URIs. URIs are resource identifiers not representation identifiers. It means if we are addressing the same conceptual thing, it should have the same URI.

If "/v1/person" is the same conceptual thing as "/v2/person", which it probably is, then it should not have a different URI.

HTTP specifically addresses the issue you're talking about, it's called Content Negotiation, which simply means that the client tells the server what representation it can handle, and the server chooses the best one for the client's abilities.

This would move the discussion to media types. Now, the same issue is somewhat present there. Do you do media types for all resources separately or just one? However the implementation does not really change significantly regardless of which one you pick, even if you update the media types with a new version.

5
  • Ok, but I still dont quite get how the server would ever be able to tell which version the client should use.. Should the client send a special "clientid-header" and the server then does a look-up somewhere to find what that client actually needs and then matches that to which ever version is the latest? As i mentioned in a previous comment, I get that this might be possible to do for any method/verb where you can send input to the endpoint (json in my case).. but what about a GET? Would the client then in a header send which fields it expects back and the server tries to match that?
    – Inx51
    Mar 10 at 19:14
  • Read up on "content negotiation". It's a standard mechanism of HTTP. The client sends an "Accept" header with the mime-types it supports. No special/extra header necessary. The server answers with content best fit for the listed types. Server indicates selected type in the "Content-Type" header in the response. Most "rest" frameworks support content-negotiation to some degree. Mar 11 at 9:06
  • Yes, Im fully aware of how the Accept header is used for doing content negotiation. But thats for which format to respond with.. not which "fields"/properties of the contract itself to respond with. If we have an endpoint returning a "person-object" the Accept header would tell the server which format to respond the object as.. json, XML etc.. but it would not be used for telling the server which "fields" of the person-object(s) the client requires (age, name, eye-color etc..) and thats the part I dont quite get why you refer to content negotiation.. but I might be missing something :).
    – Inx51
    Mar 11 at 11:24
  • Or let me rephrase myself.. content negotiation as far as Im aware has nothing to do with versioning.. No matter the contract or the version, an API endpoint/server can use the Accept header and content negotiation to present the data for the client with the format provided in the Accept header.. the format has nothing to do with which data to present, only how to present it
    – Inx51
    Mar 11 at 11:37
  • The Accept header could say "application/vnd.company.person-v1+json" or "application/vnd.company.person-v2+json" or both even, if the client understands both. The two different versions could be considered a different "format", using your terminology. Obviously I would try to do backwards-compatible changes as far as possible, so "adding a field" would not necessarily warrant a version upgrade. Mar 11 at 13:09
1

Postel's Law says "be conservative in what you do, be liberal in what you accept from others". From that perspective, it's probably a good idea to accept /v2 even if it just responds with the same thing as /v1 did for a given endpoint. This would be the same as saying "version 2 of this particular endpoint returns the same thing as version 1 did".

0

Your endpoints shouldn’t be “people” and “fruit” but “v1/people” and “v2/fruit”, for example. And you assume you have multiple servers able to handle different versions, and you have clients able to handle different versions.

A simple method to get most up-to-date versions without negotiation: The client requests the highest version it can handle, for example v3/fruit. The server replies with the highest version that the client might understand. So it will not reply to this client with v4/fruit because it knows the client can’t handle it. It will reply with v3/fruit if it can, otherwise v2/fruit. If it doesn’t support v3, v2 etc but only v4, v5 etc. it replies “server too new”. Server is too new for the client. If the client doesn’t support the answer version, then it knows the server is too old.

Result: if there is a common version then the highest common version is used. If there is no common version, then the client knows.

There is the possibility that a client able to handle version x can handle x+1, x+2 by ignoring data that it doesn’t stand. But the client wouldn’t know that. Two possibilities: Either we agree that for example v1.1, v1.2 etc must be designed so that v1.0 can handle versions less than v2 by ignoring data it doesn’t understand. Or the server reports the actual version and the earliest version that understands the data. (Because the server would know about newer versions)

5
  • Just so I get this right.. you are suggesting that the server somehow should tell the client of which version to use? How should that be implemented? For instance for a GET there is no way for the server to know which data from the response the client actually uses.. and therefor cant validate which latest version would "fit" the client. For a POST, PATCH, PUT etc.. I guess you could validate the input and redirect to THE latest version matching the inputs contract. But then again.. the response cant be validated. Am I understanding you correctly?
    – Inx51
    Mar 10 at 8:18
  • @Inx51 I'm with you here, secretly redirecting versions is both an effort drain and prone to creating blocking issues, and I wouldn't recommend it in this case (there is some merit to is in cases where the endpoints don't expose a version number, but that's a different scenario).
    – Flater
    Mar 10 at 9:22
  • "Either we agree that for example v1.1, v1.2 etc must be designed so that v1.0 can handle versions less than v2 by ignoring data it doesn’t understand." Following semantic versioning, the golden standard as to how to version things; non-major changes (v1.1 -> v1.2) would never include breaking changes, which inherently means that they have compatible contracts. If this update did entail a breaking change, then it needs to increase the major version (v1.1 -> v2.0). Following this standard; there is no need to have all of this "secret versioned reply" logic. The endpoint exists or doesn't.
    – Flater
    Mar 10 at 9:26
  • You assume that there are different versions of the server and of the client around. The clients don’t know what the servers support and vice versa. The client asks for the latest version it understands. The server might be too old to support that version. In that case the server supplies data that it can supply and returns that with the version number. Does it tell the client what version to use? Only insofar as it can’t supply data that it doesn’t know how to supply.
    – gnasher729
    Mar 10 at 12:42
  • Ok, but exactly how would the client tell the server which data it requires? For instance.. lets say the client requires a json with the properties name and age: { "name":"xxxx", "age":35 }.. would the client then send a request with some sort of header containing those fields to the server, just as the accepts-header, but instead of application/json it would be something like: data:name,age ?
    – Inx51
    Mar 10 at 19:19
0

I tend to agree with autophage at a high-level. You can definitely use this approach. I would just add one word of caution from personal experience. If you create a new global version every time one service changes, you can end up with a lot of global versions.

Whether you run into that problem comes down to your development and release lifecycle. Do you have scheduled releases every X time units or do you release changes on ad hoc basis. If it's the former, a global version makes a lot of sense. If it's the latter and you have many services and many changes, this could become a little unwieldy. It can be hard to the consumers of the services to know when to update their dependencies when there's a new version every week.

I would also point to Robert's answer if you go with a per service versioning strategy. I'm not a restafarian fundamentalist but it is worth consideration.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.