Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify the versioning strategy for APIs #406

Open
extesy opened this issue Mar 6, 2015 · 61 comments
Open

Clarify the versioning strategy for APIs #406

extesy opened this issue Mar 6, 2015 · 61 comments

Comments

@extesy
Copy link

extesy commented Mar 6, 2015

I think it would be useful to standardize the approach to versioning the API. For example, when I want to change the request or response format for some API endpoint, which is backwards incompatible. How should the client tell which API version to use?

There are many different approaches to this: put version in url like /api/1/action or /api/v1/action or in the header, like "Content-Type: application/vnd.company+json.v1" or "X-API-Version: 1".

How about explicitly including the strategy to versioning API in this specification?

@tkellen
Copy link
Member

tkellen commented Mar 6, 2015

This seems like it falls outside the scope of json-api. Any of those options seem fine,
though!
On Mar 5, 2015 7:55 PM, "Oleg Anashkin" notifications@github.com wrote:

I think it would be useful to standardize the approach to versioning the
API. For example, when I want to change the request or response format for
some API endpoint, which is backwards incompatible. How should the client
tell which API version to use?

There are many different approaches to this: put version in url like
/api/1/action or /api/v1/action or in the header, like "Content-Type:
application/vnd.company+json.v1" or "X-API-Version: 1".

How about explicitly including the strategy to versioning API in this
specification?


Reply to this email directly or view it on GitHub
#406.

@extesy
Copy link
Author

extesy commented Mar 6, 2015

Why is it outside of the scope? It's quite an important aspect of API design.

@extesy
Copy link
Author

extesy commented Mar 6, 2015

By the way, at least one technique (using Content-Type header) conflicts with existing json-api spec which requires using specific "Content-Type: application/vnd.api+json" header value.

@dkubb
Copy link

dkubb commented Mar 6, 2015

@extesy I have an API where the clients use Accept: application/vnd.api+json; version=1 to select the api version. The version corresponds to the app's versioning scheme, not necessary json-api's version. If I make a backwards incompatible change, like removing or renaming a relationship, I bump the version up. I've been using versioncake to manage negotiating the proper view template based on the version.

Hope that helps.

@hhware
Copy link
Contributor

hhware commented Mar 6, 2015

While it is up to the implementation to decide how to communicate the API version (in URL, headers or elsewhere), would it be reasonable to include a recommendation to use a specific strategy for versioning APIs? IMHO, semantic versioning is a good candidate for this.

@bintoro
Copy link
Contributor

bintoro commented Mar 6, 2015

It would seem to make a lot of sense to come up with a common convention for advertising a specific JSON API version. I mean why not? Doesn't have to be decided before 1.0 though.

I have an API where the clients use Accept: application/vnd.api+json; version=1 to select the api version. The version corresponds to the app's versioning scheme, not necessary json-api's version.

@dkubb That doesn't look very safe, since it's not out of the realm of possibility that JSON API might want to use the same parameter in the future.

@extesy
Copy link
Author

extesy commented Mar 6, 2015

That doesn't look very safe, since it's not out of the realm of possibility that JSON API might want to use the same parameter in the future

And that's one of the reason why I proposed to have the spec define the strategy. Otherwise, some custom solutions conflict either with existing spec already, or might conflict with some future version. To avoid these conflicts, spec should define the only one "blessed" approach.

@steveklabnik
Copy link
Contributor

I'm for adding a reccomendation, but we don't need this in the actual text. Will post more later.

@ColtonProvias
Copy link
Contributor

It seems like each version of an API should be on its own in its own directory. Thus any changes in format won't affect the previous at all. Then you can have something like this:

/api/1/users returns an XML resource
/api/2/users returns a binary resource from that time when you got drunk and thought this would be best.
/api/3/users returns a JSON-API resource.

The JSON-API base exists in the folder and thus the prefix (/api/3) falls outside of the spec. Essentially JSON-API starts at the resource type in the path and covers everything to the right of it. Everything left of the resource type is your own design. If you want your API to be https://catfacts.io/annoy/some/users, go right ahead.

@extesy
Copy link
Author

extesy commented Mar 10, 2015

@ColtonProvias That's just one of the ways to do api versioning, but not the only one. There are even solutions other than path and header, for example see how Stripe is doing it: https://stripe.com/docs/upgrades

@ColtonProvias
Copy link
Contributor

True. Even though it is deprecated, maybe a x- header would do the trick.

X-API-Version: 1

The question still exists of if this should be part of the standard or if it is outside of the spec. Some sites probably have old versioning schemes in place that if we specify a particular scheme, it may conflict. Keeping backwards compatibility then would be a major issue.

@visualasparagus
Copy link

Would it be an idea to have either a namespaced version parameter to avoid collisions with the jsaonapi spec? So something like this? Not certain if the dot is allowed as part of a parameter name though.

Accept: application/vnd.api+json;version.com.my.api=1.1

@ceyko
Copy link

ceyko commented Mar 28, 2015

@visualasparagus A dot is indeed allowable as part of a token as per RFC 2616 §2.2

I'd argue in favor of namespaced versions for the same reason. With the exception of reversing the order:

Accept: application/vnd.api+json; com.example.api.version=1.1

More generally, namespaces SHOULD be used for any media type parameters. Including those used by json-api.

@krainboltgreene
Copy link
Contributor

I needed this information from the jsonapi spec but it's not in the jsonapi spec, is there a reason? I really really like using the key/value store in the Accept header's parameters.

@Art4
Copy link
Contributor

Art4 commented Jun 16, 2015

@ceykooo Your solution looks best to me. But than I noted this in the spec:

Clients that include the JSON API media type in their Accept header MUST specify the media type there at least once without any media type parameters.

So I ended up with something like this:

Accept: application/vnd.api+json, application/vnd.api+json; com.example.api.version=1.1

sebilasse pushed a commit to redaktor/json-api that referenced this issue Sep 14, 2015
please see / should fix
json-api#867
json-api#851

partially fixes
json-api#406
---> see my following answer in
json-api#867 regarding versioning
@Perni1984
Copy link

Accept: application/vnd.api+json, application/vnd.api+json; com.example.api.version=1.1

is this the recommended way of api versioning now?

@steveklabnik
Copy link
Contributor

@Perni1984 no, that's the version of JSON API itself. Which is what media type parameters are for: to parameterize the media type.

I personally recommend "versioning" through URLs.

@Perni1984
Copy link

@steveklabnik: thanks for the explanation, I think I did not communicate well. I understood that the spec says to send the following header to announce JSON API compatability:

Accept: application/vnd.api+json

What I don't understand is how I can add the version of our companies API, as the spec explicitely states to _NOT add any media type parameter_ (see @Art4 comment above), if I read it correctly. So I am not really sure that this is a valid way according to the specs:

Accept: application/vnd.api+json, application/vnd.api+json; com.example.api.version=1.1

If that header is valid according to the spec, perfectly fine, I have my solution. But if I am not missing something com.example.api.version=1.1 is an media type parameter, so the above statement violates the spec. Correct?

I really would like to version the company api through accept headers for various reasons and avoid using URLs for versioning.

@steveklabnik
Copy link
Contributor

I really would like to version the company api through accept headers for various reasons and avoid using URLs for versioning.

This goes against the RFCs, though, and if we want JSON API to get through the IETF, we'll have to abide by them.

@Perni1984
Copy link

would it be possible to send multiple accept headers? E.g.

Accept: application/vnd.api+json;
Accept: application/vnd.mycompany.v1+json

@Art4
Copy link
Contributor

Art4 commented Feb 17, 2016

@Perni1984 My comment has already multiple accept headers

Accept: application/vnd.api+json, application/vnd.api+json; com.example.api.version=1.1
// or
Accept: application/vnd.api+json
Accept: application/vnd.api+json; com.example.api.version=1.1

Afaik this fits the spec, because it specifies "the media type there at least once without any media type parameters".

@Perni1984
Copy link

@Art4: Ah, now I got it, thanks!
@steveklabnik: Well now I think it is perfectly valid inside the spec - you agree?

@Art4 & @steveklabnik: What about specifity? Wouldn't it be better to put the more specific accept header on top? E.g.

Accept: application/vnd.api+json; com.example.api.version=1.1
Accept: application/vnd.api+json

@steveklabnik
Copy link
Contributor

I still think this isn't correctly using the media type. You're trying to request two media types, and always getting one back, but using parameters from the other media type, the one you're not using.

@Art4
Copy link
Contributor

Art4 commented Feb 17, 2016

@steveklabnik This is the recommendation at #673, 3rd point from @ethanresnick

It accounts for the fact that the Accept header can take multiple values, e.g. Accept: application/vnd.api+json;ext=bulk, application/vnd.api+json, and that the header is only problematic if every JSON-API option it lists contains parameters.

@ethanresnick
Copy link
Member

@Art4 @Perni1984 Just to clarify...

The spec says that clients' Accept header must contain at least one unparameterized instance of the media type because, if JSON API defines a parameter in the future, old clients will still need to handle (i.e. Accept) the unparameterized version.

But, just because we're making it possible for JSON API to add media type parameters in the future, that doesn't mean that the media type can be used now with parameters that aren't part of the JSON API spec (or part of its IANA registration). Using a media type with parameters that it doesn't define is a violation of the general RFCs governing media type use, which was @steveklabnik's point.

So, if you want to version your API, my recommendation would also be to use different URIs. If you don't want to use different URIs, you can add another header, like @ColtonProvias suggested. That said, if you don't want to use URIs, I'm really curious why, so please let me know!

@Perni1984
Copy link

@ethanresnick @steveklabnik: I am still learning REST and Hypermedia, and I am open to improve myself, but basically I don't like the idea of "polluting" the URL with a version number, if there is another clever way of solving the versioning issue and being compliant with all the standards. I also dislike the idea of adding a custom header and then adding this custom header in the Vary statement to be standards compliant and having no problems with caches and therelike - I don't think this is an "universal" solution.

I understand that if you want to get the spec through the IETF you have to abide by their rules. So what do you think about putting the company api version in a custom extension media type. E.g.

Accept: application/vnd.api+json; ext=com.example.api.version+2.1.1

So I could serve v1 of my company API aswell as v1 of JSON-API when no additional media type parameter is given:

Accept: application/vnd.api+json;

What do you think about it?

@masterspambot
Copy link
Contributor

@Perni1984 👍 I really like your approach. Looks very clean.

@steveklabnik
Copy link
Contributor

I still think that using the media type to version is just plain incorrect. Media type parameters are for the media type, not for the underlying service.

@Perni1984
Copy link

I still think that using the media type to version is just plain incorrect. Media type parameters are for the media type, not for the underlying service.

I understand your point, but in the lack of better suited alternatives for my use case I would prefer this solution. FYI I still consider this option being better suited for my use case than URL versioning or custom headers for the above mentioned reasons.

Could you eventually let me know if you see any problems with that not being standards complaint?

@ethanresnick
Copy link
Member

@Art4 I appreciate the thorough rundown. One point about URI based versioning, though, to add to what @steveklabnik said...

In REST/HTTP, a resource is a concept, right, like "Today's weather in SF". Then that concept is identified by a URI and takes on different representations over time.

So, you can think of URI versioning as legitimately creating new resources if you think of the concepts behind each URI as different. That is, the resource identified by https://api.example.com/v1/x can be thought of "The x part of our v1 service" while https://api.example.com/v2/x can mean the distinct idea of "The x part of our v2 service".

Imagine, for instance, that in v2 you switch your service to accept a different kind of PATCH format, or you switch some parts of v2 to not supporting some methods. In that case, "Part x in v1" and "Part x in v2" actually work differently; it's not just the representation that's changing; and they are distinct concepts that you might (e.g.) want to be able to link to individually.

This is what I meant when I said earlier:

Meanwhile, if you keep the URI the same but only change the media type, making certain changes across major versions would be weird semantically, because media types are only supposed to control the format of the representation. For example, if you remove support for an HTTP method in v2 that was there in v1, you really want two distinct URIs, so you can have two distinct Allow headers and return 405 appropriately.

So, the downside of approach 3 isn't just violating the media type rules (which come from the IETF in combination with our particular IANA registration), it's that it limits the kinds of across-major-version changes that you can appropriately express semantically. URI based versioning has no such limitations, and doesn't need to be seen as violating REST principles.

To your point about URI-based versioning forcing consumers to do weird stuff to keep links up-to-date... the solution there is probably more REST (i.e. more inline links and less client URI construction). JSON API already has limited features for this, and we plan to add more in the future

@bintoro
Copy link
Contributor

bintoro commented Mar 10, 2016

Several comments in this thread mention media type "rules" and IANA and RFCs... But I'm curious, where does it actually say that using proprietary parameters with a registered media type is not OK?

I'm aware that RFC 6838 requires (or recommends, depending on the tree) that standard parameters for a media type are registered along with the type itself. But nowhere does it prohibit clients and servers from tacking on additional, mutually understood parameters, and neither does HTTP.

@ethanresnick
Copy link
Member

But nowhere does it prohibit clients and servers from tacking on additional, mutually understood parameters, and neither does HTTP.

I think it's inherent in, or at least strongly implied by, the definition of a media type parameter. From RFC 2046, which defines what a media type is: "Parameters are modifiers of the media subtype... The set of meaningful parameters depends on the media type and subtype." So, the concept of a parameter whose meaning is determined by an external contract between client and server, rather than by the media type, isn't consistent with the definition of a media type parameter in the first place.

Note: RFC 2046 also says that mime implementations are supposed to ignore parameters they don't recognize. That should make it technologically possible to tack on extra parameters, but it doesn't change the fact that doing so makes no sense semantically.

@steveklabnik
Copy link
Contributor

Thank you @ethanresnick , you said it better than I.

@bintoro
Copy link
Contributor

bintoro commented Mar 10, 2016

RFC 2046 doesn't say parameters can only be derived from media type definitions. The quoted passage simply means there are no universal parameters common to all media types.

If JSON API wants to prohibit media type parameters, that's obviously its prerogative, but pinning it on the IETF or the IANA registration doesn't seem warranted.

For example, the application/json spec doesn't prohibit applications from using proprietary parameters, yet the media type has been registered just fine.

@ethanresnick
Copy link
Member

@bintoro Maybe I'm missing something, but "[t]he set of meaningful parameters depends on the media type and subtype" seems to mean precisely that "parameters can only be derived from media type definitions".

But let's back up a second... suppose a parameter can be used. It would still only be valid for indicating certain types of changes (those that effect the representation only), so we'd have to a recommendation that says: "You may use URI versioning or a media type parameter to indicate breaking changes, except in cases where more than the representation changes; then you must use a URI change." Isn't that just unnecessarily complex compared to simply recommending URI versioning? And what are the odds that people who start off using media type parameters to version really switch to URI versioning when it becomes semantically appropriate to do so? (I'd put those odds very low.) Meanwhile, I still don't really understand the supposed benefits of media type parameter based schemes, other than keeping the URI "clean" looking. Imo, though, that benefit isn't worth the complexity.

@cziegenberg
Copy link
Contributor

@ethanresnick From my view you always need a mix of URL versioning and media type versioning.

URL versioning is the best method for API structure changes. Every API can require such changes, so I'd always add a version to the URI. Of course this means that some resources can be accessed with different URIs in different versions - but this cannot be avoided, if you want to be able to restructure your API later and still want to offer your API consumers the old API and give them time to switch. This is very important, because it takes time for clients to change and otherwise you would have to break all clients which cannot immediately switch, or you would have to extend everything on base of the old API structure, which could result in an illogical structure and cause problems later.

On the other side we have different resource representations, which are totally independent from the API structure version. When I request an XML representation I want to get XML, when I request a JSONAPI representation, I want to get JSONAPI. And when I request a special version of this representaion format (which can be JSONAPI in a given version or with special extensions) I want to get it, no matter which API version I use (but of course a new API structure versions can redefine the requirements and drop support for old representation versions). And this is IMHO best done with content negotiation, because this enables the client to accept multiple supported versions at the same time, using the same URI for the request. Also - as mentioned earlier - it would simplify the switch to newer JSONAPI versions for clients and servers (and solve a chicken/egg problem here), because no URL or logical changes are requiered. If clients and servers support the newer version, they can automatically use it.

Just my point of view...

@krainboltgreene
Copy link
Contributor

I've been doing HATEOAS API development for the last few years and I had always been on the side of "Version are a part of content negotiation and media representation!"

@steveklabnik just blew that out of the water for me though.

When I see a change from /v1/activities to /v2/activities I know that all my assumptions about the data and behavior have to change. When I see the Content-Type: application/lw.api+json; api-version=1 change to Content-Type: application/lw.api+json; api-version=2 I know my assumptions about the content and interface have to change.

@masterspambot
Copy link
Contributor

Please take into consideration, that in most cases, company version their API facade through headers or urls like api/v1 mostly. So that to avoid collisions I'd still recommend what @Perni1984 said with accept header:

Accept: application/vnd.api+json; ext=version+1.1.0

This allows detecting and changing JSON API semantic version without necessarily changing company's API facade version (even though in most cases should, but we should allow people to make that decision for themselves). And also is very friendly for handling errors.

@bf4
Copy link
Contributor

bf4 commented Apr 13, 2016

Looks like this should be closed per #1020 ?

@pixelhandler
Copy link
Contributor

pixelhandler commented Oct 12, 2016

@steveklabnik I like using the Accept header for the version of an API endpoint, I agree with this http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned

application/vnd.api+json;version=1

Here is a tongue and cheek article on the subject: https://www.troyhunt.com/your-api-versioning-is-wrong-which-is/ and another https://blog.pivotal.io/pivotal-labs/labs/api-versioning

The Accept header generally allows media type parameters, see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

I'm curious how the JSON API spec concluded without any media type parameters

Clients that include the JSON API media type in their Accept header MUST specify the media type there at least once without any media type parameters.

@steveklabnik
Copy link
Contributor

@pixelhandler if you read the first sentence of that post

Since I've posted this, I've refined a few of my positions on things. Everyone learns and grows, and while I still stand by most of what I said, I specifically don't agree that versioning the media type is how to properly version APIs. Hypermedia APIs should not actually use explicit versioning, but I'd rather see a version in the URI with HATEOAS than no HATEOAS and versioned media types.

@ethanresnick
Copy link
Member

I'm curious how the JSON API spec concluded without any media type parameters…

@pixelhandler Please read the thread, and comment back with any specific questions. The decision on media type parameters is well documented above, I think.

@pixelhandler
Copy link
Contributor

@steveklabnik ah I see thanks. @ethanresnick yeah reading it thanks.

I think that https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html allows for accept-extension and accept-params which afford custom params.

       Accept         = "Accept" ":"
                        #( media-range [ accept-params ] )
       media-range    = ( "*/*"
                        | ( type "/" "*" )
                        | ( type "/" subtype )
                        ) *( ";" parameter )
       accept-params  = ";" "q" "=" qvalue *( accept-extension )
       accept-extension = ";" token [ "=" ( token | quoted-string ) ]

Could accept-extension afford appending the Accept header with ;version=1 ?

@ethanresnick
Copy link
Member

@pixelhandler

Re accept-extension params, from upthread:

To @bintoro's point... there is a second type of parameter that the Accept header allows, called an extension parameter, which is distinct from media type parameters. So you could probably use that instead of using the URI or waiting on JSON API to add a suitable media type parameter. However, I've never actually seen anyone use an Accept extension parameter in real life, so I would expect interoperability issues, with some parsers mistakenly treating them as media type parameters or ignoring them entirely.

@bf4
Copy link
Contributor

bf4 commented Apr 18, 2017

Was the conclusion here that Accept: application/vnd.api+json; version=3.0 is a legit way of saying my mime type is JSON:API and my api is at version 3? If so, how would JSON:API version 1.1 look?

@krainboltgreene
Copy link
Contributor

I used to be on the train of doing api versioning in headers.

A. Tooling doesn't expect it, welcome to writing your own stuff.
B. It's not actually semantically correct (see @steveklabnik's words)
C. It's much easier to do path versioning (and have others understand): http://www.jbarnette.com/2009/04/07/http-apis.html

@ethanresnick
Copy link
Member

I'd like to close this, as I think everything that can be said on the topic has been at this point. However, I would like to get an official recommendation onto the recommendations page before closing this. Would anyone want to take a stab at drafting such a rec?

@jamesplease
Copy link
Contributor

@ethanresnick , I’d be happy to write this up sometime over the next few days. How’s that sound to you?

@ethanresnick
Copy link
Member

That would be awesome. Thanks James!

@mblackritter
Copy link
Contributor

mblackritter commented Nov 3, 2017

Actually, the only confusing part of JSON:API for us was to see that there's not even a suggestion about versioning, but instead a rather surprisingly "rich" thread about it, and yet no resolution. 🙃

We went for the double accept header like

Accept: application/vnd.api+json;
Accept: application/vnd.api+json; version=1.0

By my experience that's accepted as by the specs by any implementation of JSON:API. 🤗

@mrhwick
Copy link

mrhwick commented Nov 29, 2017

URI Versioning

Versioning in the URI means that I'm referring to two different resources, because having a different path does mean that it is a different resource. In practice, you will probably have some resources that are identical to the previous version when you make a new version this way. That means your API isn't guaranteeing that they are the same resource, just that the new resource in question behaves the same way as the old resource. You can trust that they are interchangeable in that sense.

For example, api/v1/user and api/v2/user are actually different resources, but I can choose to implement the api/v2/user resource as an identical representation to the api/v1/user resource, so you can use them interchangeably. You aren't guaranteed that they are identical, but I would probably document it as such and everything would be fine. In this scenario, you aren't guaranteed that a resource you create in v1 will appear in v2 since they are technically different resources, but I can (and probably would/should) implement it that way.

Header Versioning

Versioning in headers indicates I am retrieving the same resource with a different representation. When a new version is introduced, the path remains the same (which means it is the same resource), but I can now request a new version of the resource if I'd like to do so. It's less visible, for sure, but it gives the client the guarantee that I'm accessing the same resource, just at a different representation version. In practice, I don't see much difference between the tradeoffs here since you are probably going to give your clients the guarantees they need anyways.

I think there is also a body of thought that suggests having a different media type for each of your resources, in which case header versioning actually makes a lot more sense. In that case you would be requesting a specific media type at a specific version. I have never seen this in practice.

Brief note about semver and restful APIs

I also want to make a brief note that semantic versioning introduces more structure than is necessary for API versioning from a practical perspective. Compatible changes (minor and patch) don't require a version change in practice since clients can keep using the representations just fine without changing their expectations. Incompatible changes demand a new version, otherwise you are putting clients at a high risk of breaking if they don't make changes to their behavior to accommodate.

@jelhan
Copy link
Contributor

jelhan commented Jan 30, 2022

@ethanresnick , I’d be happy to write this up sometime over the next few days. How’s that sound to you?

@jamesplease Do I assume correctly that you haven't had time to do so?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests