How to Version an API
fly.io
fly.io
An API version says to me that one day the entire API may introduce a major change that is separate from the current API. If you plan to never introduce a major change like this, you may not need a "/v1" in the URL.
Kudos to the author for putting together a nice article :)
Some APIs will not change a version number even if new objects/fields are added, others will, it really depends by what space you are in and your customer needs and it should be taken into account when designing your API.
It allows you to version your json Jbuilder views by adopting the file naming convention (e.g. show.json.v1.jbuilder, show.json.v2.jbuilder, etc).
There's fallback to most recent, compatible version of the API.
Is this true? I haven't looked at very many web APIs, but I haven't run across anyone using Accept in this way, and it strikes me as a pretty bad mis-use of that header.
[0] https://developer.github.com/v3/media/ [1] http://blog.steveklabnik.com/posts/2011-07-03-nobody-underst...
The proper solution here is to just define a new header, like X-API-Version (note: the article picked a header API-Version, but since this is a non-standard vendor-specific header it should be prefixed with X-).
> that header isn't just used for the response, it's an agreement about what the specific message format in both directions is
No, it's not. The Accept header is strictly about the response.
If I change my API so that "POST /widgets" does something different on the backend but returns the same response, then from a client compatibility perspective this isn't a breaking change.
OK, if you want to be that strict about it, then you use both the ContentType and Accept headers to specify what version of the request and response you're making/wanting.
I'm also not sure why anyone would even want to use Accept for this, given that if you can set Accept, you can also set something like X-API-Version.
Yes, you could use a custom header, many do, but then you're stepping outside of the HTTP standard, the Accept header is supposed to define the response format, not some custom one you make up, and version is another facet of the response format and must imply the request format matches the required data of the requested output format.
> Yes, you could use a custom header, many do, but then you're stepping outside of the HTTP standard
No you're not. The HTTP standard allows for custom headers, and nothing in the HTTP standard restricts servers from using customer headers to influence how they process the request.
> Accept header is supposed to define the response format
Yes, like application/json. It's not supposed to define the request content, just the format, and it's definitely not supposed to affect the way the request is processed.
> version is another facet of the response format
No it's not. JSON is a response format. XML is a response format. "API version 5" is not a response format.
[0]: https://www.infoq.com/articles/roy-fielding-on-versioning/
I think Rich Hickey (the creator of Clojure) would agree. He said something to this effect in the "Spec-ulation" keynote at Clojure/conj 2016. https://www.youtube.com/watch?v=oyLBGkS5ICk
The hostname thing might work for huge, world-changing re-writes, but for small, incremental changes, I think something like what GitHub does w/ the Accept header is far more appropriate.
[edit: ...and resources. And be able to comprehend resource record conventions]
I just don't see any generic operations w.r.t. discovery that are … useful.
I'm not trying to say that you shouldn't, e.g., use URLs in your resulting resources; e.g., if a query for a widget returns:
{
"widget_name": "NextGen Enterprise Widget",
"suppliers": "http://bigcorp.example.com/widget/123/suppliers"
}
That's fine. But you're still going to need to know how to parse the result of …/suppliers, or it's just opaque bytes. /v1/2017-06-08/blah
The idea is that you write in the date that you wrote the implementation against the API. This is just like Stripe's API, but explicit rather than implicit. Then everything under that will continue to work as-is.There's a few reasons I like to do this, but the main one is that I often struggle to decide whether a given change is really "worthy" of a major version change.
The only rule/caveat we have is: within a given client's implementation, you should only use one date at a time. Mixing them creates a small possibility of compatibility issues.
The initial /v1/ is just in case I decide to move away from the date thing at some stage.
/v2014
I assume I won't make multiple versions in a year. I have a current system with /v2014, /v2016 and /v2017I like to smash dates into all my version strings
Mine are a bit more granular: /vyymm, e.g. /v1503, /v1610, /v1706. This should work well for this century ;-)
I know it's slightly off-topic, but when one of the early points of an article contains an incorrect piece of information, that really makes me not want to read on further
Restful API's are a recent "fad" that, even though_do_ have their place in mainly basic CRUD apps, RPC still reigns very popular. See [1] for a pretty good explanation of REST-styled vs RPC-styled APIs.
[1] https://www.linkedin.com/pulse/rest-vs-rpc-soa-showdown-josh...
Example from a past life: https://github.com/lavab/api-client-js
> 3: RESTful. It should speak the language of HTTP verbs: GET, PUT, POST, PATCH, UPDATE, DELETE.
Oh, it was talking about web interfaces. Oh well.
API used to mean headers, types, Java interfaces… stuff programmers use to link code together. And now the web is hijacking the term to a point where regular APIs can't even be named. I mean, can web developers even think about this kind of API now?
I bet this explains part of the microservice madness we've seen lately. If the only kind of API you know goes through a web server, of course you'd use not-so-micro-services to modularise your application.
No. Marking something as deprecated helps during development. It doesn't help if it's a bunch of deployed code running in production that nobody has the time to maintain for third-party changes. That code will keep making those requests until they fail.
GraphQL inherently doesn't address versioning. You'd have to version each attribute and query separately by prefixing or suffixing their names. But then you'd lose the succinctness GraphQL users love it for.
The official answer is "Just add new attributes" but that means you're stuck coming up with new names for slightly different versions of existing attributes (or queries). If you try to "be bold" and make things up as you go along, your API will be littered with the equivalent of `2017-06-09_docs_final_v2_new_copy.pdf` if you take backwards compatibility serious.
Seeing how Relay is slowly turning GraphQL APIs into RPC APIs ("client sends complex query blob with parameters to server" has become "client sends query ID with parameters to server", server-side GraphQL is now an implementation detail only relevant during development), maybe the right question to ask is:
How do I version an RPC API?
What in the heck is UPDATE? It sounds like a non-technical person found out REST had CRUD.
And then the API endpoints illustrated don't obey HATEOS. So there's nothing RESTful about the article either.
I've thought about doing this a couple of times but it seems like a lot of overhead if you need to have separate production instances running for each version of your API.
Is there a better approach to organize this in a more scalable manner?
Ex:
mysite.com/api/UUID_for_api_v1/cars/1
and mysite.com/api/UUID_for_api_v2/automobiles/11. Versioning an entire web service or RPC API is relatively expensive in terms of engineering resources. Wherever possible, avoid having more than one version of an API, in favor of a single API version that evolves gradually in backwards-compatible ways. Having multiple API versions is a recourse to employ when you've realized that the original API isn't ideal, and you need to break compatibility to get it right. Strive to avoid this.
2. Establish ground rules with your clients about what kind of changes will be considered backward-compatible: what kind of changes they are expected to tolerate. For example, adding new fields to existing structures is something that clients should tolerate from the service. Pay careful attention to enumerations, sum types, and exceptions.
3. Version upgrades are expensive. Upgrading from one API version to another typically requires engineering time from every single client application. Any time that clients spend on version upgrades is time taken away from more useful activities. The total cost of a version upgrade grows in proportion to the number of clients. Don't gratuitously release new API major versions; strive to minimize them.
4. As a service provider, it often seems convenient to just release a new API version rather than put in the hard work to provide a feature in a backward-compatible way. (I see this more often in libraries than in services, where it's difficult to know 100% for sure that your change to the library won't break anyone.) Resist this temptation. Just because the upgrade costs your clients pay are hidden from you doesn't mean they don't exist. Version upgrades are disruptive and frustrating.
5. It's sometimes possible to implement backwards-incompatible changes through a series of smaller backwards-compatible or non-disruptive changes. For example, say that you want to rename an API element -- normally this would be a backward-incompatible change. However, you can break the change up into multiple steps: first, add the new element with the desired name and announce that the old name is deprecated. Next, work with your clients to upgrade them all to the new name. Once clients have migrated, you can remove the old name without impact. (How hard this is and how long it takes depends on the situation; it varies from relatively easy to almost impossible. A key factor is your clients - the more you can coordinate with and influence your clients, the easier this is.)
6. Releasing changes in backward-compatible or non-impacting ways is especially useful because it reduces the need for a service provider and client to directly coordinate. The service provider can announce a feature or change and clients can update to account for it on their own timeline. The service provider can then implement any backward-incompatible steps once all clients are ready.
7. When running any kind of migration, such as renaming an element or moving clients from one major version to another, it's helpful to have statistics about what people are using. If your V1 API isn't getting any traffic because users have moved to V2, or alternatively if it's continuing to receive traffic, then that's important to know if you're planning to deprecate it.
8. Versioning can usually be added after-the-fact if it's needed. It's often not necessary to worry about it too much up-front. I have yet to run into a situation where the lack of a version field caused a problem (because its absence can be interpreted as Version=1).
9. Most of what I said above matters when you have a large number of clients, or when you have clients that are outside your influence. This is big company, big-usage thinking. If you're e.g. a small company building services for your own consumption, don't worry about API versioning until you begin to notice friction. Instead, focus on making changes in backwards-compatible ways. If you need to make a backward-incompatible change, then talk to the people whose software will be affected. There might be an easier way to manage the change than maintaining two parallel API versions.
Well, we took the opportunity to change provider and are very happy now :-)
So you'd have: service.com/api/v1/do_thing/v2/param
That way, you wouldn't have to try to snap new features to entire numbered revisions,and could open new parameters / results up to clients who are interested in the meantime. Seems like it would be the best of both worlds, but I've never had to design a serious API.
My opinion for a better structure:
domain.tld/api/module/version/
"The Graph API has multiple versions available"