8 ms·
API versioning has no “right way” (2017)
- ainar-g 6y agoI think I've also seen resource versions being called “layers” instead of “versions”. So in theory you could use versions for the whole API, including error codes and other conventions, and layers to actually evolve and change your resources. Either as: v1.example.com/l1/users Or: example.com/api/v1/l1/users
- yamrzou 6y agoVersioning is indeed a hard topic, especially for data science/engineering projects in production. When you have a pipleine defined as a complex DAG of operations, you can't just version the entire thing, unless you have enough resources to re-compute from scratch with every change, which is wasteful. So then, you have to keep track of data dependencies and their versions if you would like to ensure reproducibility. Versioning code isn't enough when you have runtime parameters that affects output data, and you want to stay flexible by allowing experimenting and re-running computations with different parameters, to be able to iterate quickly. Which poses a lot of challenges. And there doesn't seem to be a framework that easily solves those issues out of the box. I'm closely watching Dagster (https://dagster.io https://dagster.io), as they seem to be aware of those challenges (for example for versioning: https://docs.dagster.io/guides/dagster/memoization https://docs.dagster.io/guides/dagster/memoization), but I didn't try it yet; it introduces a lot of concepts and has a steep learning curve.
- mumblemumble 6y agoI've been playing with Dagster lately. Overall, I like it quite a bit, though I'm not sure it quite solves that problem, mostly just because it has too many different kinds of input. My new pet hypothesis is that the best way to solve this problem is to stop treating configuration as its own special thing. If it's just another kind of data that you can pass as input to a solid/operator/function/whatever, then configuration changing is treated exactly like any other kind of input data changing. Which is, I think, almost always the behavior I want.
- throwaway823882 6y agoIt doesn't really matter how you version. Just pick something and be consistent. You should plan your API to be backwards-compatible, plan major version changes about 2 years apart, and put the sunset time of your version in big bold red lettering on the front of your docs. That still may not stop clients from begging for longer support, so again, the more backwards-compatible you are, the less of a pain in the ass it is to abandon old versions. And for God's sake, have the developers consider the pain of customer migration when they develop the new versions, and provide clear, feature-complete migration paths from the old version to the new. Plan on using a single non-Apex hostname (and possibly region-specific) for all your APIs, and do not change it, because again, pain of support/migration. Then put the version number in the URL, because it will be much easier to run analytics on usage from web access logs, and it works better with a shared domain. With this scheme you can always have a reverse proxy redirect specific URI prefixes to completely different backends. For simplicity, make each new major API version a fork, and run it as a separate set of backend network services. Please make all of your teams follow the same API conventions. Don't make me write 5 different API calls 5 different ways because you had 5 teams that didn't work together. When I see APIs like this, I know the company's tech management are lazy morons, which means the rest of leadership probably is too. Being backwards-compatible is different from evolving API functionality. If you just evolve everything, eventually you will reach a high level of complexity for each call that requires massive flow charts to decipher. It's better to create entirely new functions that are simpler to reason about. But don't do this if you can wait for the next major version and consolidate functionality then. Make sure you follow good distributed computing practices, like giving clear error messages when your server is too busy and the client needs to enable an exponential backoff with jitter. Please don't use gRPC, for the love of God. It's not compatible with every other API out there that (today, anyway) uses HTTP. The fact that there are multiple "gRPC proxies" is proof enough that an incompatible interface is just a future pain in the ass. Project owners / managers / team leads: Don't let developers pick the technology just because they think it's cool or modern or the best practice, and definitely don't let them choose something they've never used in production before. Consider the actual cost of the thing long-term to your organization.
- slver 6y ago> It doesn't really matter how you version. While there are many right ways to version, there are also a few wrong ones. Like putting the version in a header.
- hn_throwaway_99 6y agoI liked this article, but after trying many different approaches in the past, I've found that GraphQL's "evolvable" approach is by far the best. And while the author correctly points out that some REST APIs have used this approach for a while, there are a couple important things that make this approach really easy to use in GraphQL: 1. When you deprecate fields, they disappear from the "default" documentation in the Graphiql browser. Which means it's always easy to see the latest "version" of the API. 2. Since clients request fields, you don't feel like you're unnecessarily returning an object that's twice as big as it needs to be with all the new and deprecated fields in the same response. 3. Corollary to #2, but since clients request fields it's easy to detect who hasn't migrated and who is using deprecated fields, which makes it easier to notify these clients or at least make an informed decision about when you can remove deprecated fields.
- slver 6y agoGraphQL's evolvable approach will likely not work that well on the "mutation" (i.e. command) side, because while you can map read-only data and remap and remap until it's beaten into submission, commands tend to have semantic meaning. When the backend changes, many commands may need to change at once. Just something to consider. Otherwise I find GraphQL's approach indeed great.
- gbourne 6y agoIt is a good write up of the options, and sad to say there is no "right" answer. That being said, I think early companies (like mine with an API) need to go the "Migrations a’la Stripe" mode. Basically you take the brunt of translating old request to new ones. We've done this several times and update our docs with the new calls/parameters so new users start using the latest version. Current users have no breakage (you hope) and new user are on the latest API version. Old users are also encouraged to move to the latest version since only it contains new functionality. It works well...however, the downside it you now have a lot of translations in your code and some users are on the new and some on the old. This means you eventually have a bit of a mess on your hands. The way we plan on mitigating this is tracking the translations and over time informing our users of the deprecation. Larger companies with dozens or hundreds of hands in the code, or with legacy code, likely can't use this technique. Perhaps why Facebook over time move to the /v1/ technique.
- tomschlick 6y agoThe key to doing migrations on the request is having the migrations live in single classes/files. It makes it very clean to have the app migrate from version 3 to version 12 with the 9 or so classes handing the response from one to the other. I made a package a few years ago in PHP/Laravel that focuses on this: https://github.com/tomschlick/request-migrations https://github.com/tomschlick/request-migrations
- derefr 6y agoOr doing the migrations as load-balancer-side request rewrites. (Nginx is particularly good for this use-case; it’s what sets it apart from simpler LBs like HAProxy.) Then the cruft of old versions doesn’t have to live in your codebase at all, but can live in the same infrastructure-config repo that holds your backend-service-unifying route-map; your SSL config; your rate-limiting setup; etc.
- tomschlick 6y agoMy main concern there would be the management and orchestration of the releases for that since its detached from the codebase that is changing. It sounds very performant though. Have any examples of this setup?
- k__ 6y agoTalk to your users. It's nice if you follow some strict rules, but even if your customers violate them, you could still be inclined to help them. After all, they are the source of your income.
- travisjungroth 6y agoAn interesting talk about versioning by Rich Hickey: https://www.youtube.com/watch?v=oyLBGkS5ICk https://www.youtube.com/watch?v=oyLBGkS5ICk
- cletus 6y agoI have to disagree somewhat: versioning objects or endpoints is never the right solution. I'd go so far as to say that versioning the whole API is simply the least bad option. What constitutes a major version? Simple: as soon as existing clients break it's a new major version. That means you can, for example, add new endpoints because that's not a breaking change. You simply can't remove or change any of the existing endpoints, fields or objects. It also means that a client can't mix-and-match what major versions to use ie you can't use /v1/customer and /v2/account. Why? Because clients will do that as a quick hack and you need to save them from themselves. So why not version endpoints or objects? Because of environment bifurcation. Let's say you have 2 API versions (v1 and v2). To verify your API you can test each independently. That's relatively easy. But imagine you have 10 endpoints and each is versioned separately. Now you have to test 2^10 possibilities. Clients will do stupid things like use a different version as a quick hack or even when they don't need to and you'll be debugging those things forever. Don't give your API users footguns.
- hinkley 6y agoOne very powerful way to socially engineer people into upgrading to the new API is to make sure that the newest one is the most efficient. Too often, what happens is that version 1 is 'faster than possible' - it ignores some sanity checks that turn out to be important, and so the new version has more logic in it, making it 'slower'. This fact lets people rationalize that they should stay with the oldest version of the API that is still available, but also results in preemption every time you retire a deprecated API. And they will likely 'blame' this interruption on you. If instead the old API becomes a wrapper around the new API, now the oldest API is the slowest, because it bears both the costs of all of the sanity checks and the thunking layer. Now the motivation is to upgrade sooner. Essentially you have 'migrations' for you API. Yes, this constrains the sorts of changes you can make in some dimensions, but being able to accelerate retirement of old APIs I feel 'pays for itself' by unsticking you in other dimensions.
- ghoward 6y agoI had some of the same thoughts reading the article, but I could not articulate them as well as you did. A good resource for the details about what constitutes a breaking change is Rich Hickey's talk about Clojure's Spec. [1] [1]: https://youtube.com/watch?v=oyLBGkS5ICk https://youtube.com/watch?v=oyLBGkS5ICk
- nofunsir 6y agoBenign app: MANDATORY UPDATE!!! YOU MUST UPDATE YOUR APP NOW! THERE’S NO CANCEL BUTTON BECAUSE BAD THINGS WILL HAPPEN IF YOU DON’T IMMEDIATELY UPDATE. Release notes: Bug fixes and performance improvements. Teehee.
- simonw 6y agoSomething that does help a little bit here is requiring clients to specify the fields that they need (as seen in GraphQL) rather than doing the equivalent of "select *" and giving them back everything. This is useful because it lets you turn on detailed logging in order to understand exactly what fields are being used by which clients (identified by their API key or similar). If you want to make a backwards compatibility breaking change like removing a field you now at least have a way forward: announce the field is going away, then watch your logs to see if it's still being used and who is using it. Then you can actively reach out to clients that use the deprecated feature and eventually make an informed decision about the overall impact when you finally remove it.
- cryptonector 6y agoYou can also use headers for API version negotiation, for non-browser UAs anyways.
- thehappypm 6y ago"If clients don’t request a specific version, should they get the earliest supported version, or the latest?" (in reference to MIME versioning) This one feels like the "right" answer to me, doesn't it? If the header isn't supplied, reject and demand one!
- jopsen 6y agoYes, but if your service didn't do this initially then you're struck.. Of course you can always migrate to URL versioning :)
- lbriner 6y agoAs others have said below and my own experience of running production APIs, the important thing is that you consider deprecation before you have deployed your first API so that you know how you will eventually re-version things. Individual versioning of endpoints is hell on earth so no dice there. Future versions might be completely differently shaped, hosted on different platforms, written in different languages etc. These changes might be for good reasons but will be a pain for your customers (depending on whether you APIs are 1st-class netizens or some back-channel/low traffic thing), so setting the tone and habits early on, including considering how you expect customers to migrate to different requests/responses etc. saves you the awkward question later. You can't support version 1 forever in most cases.
- deleted 6y ago[deleted]
- ChrisArchitect 6y agobah all the different urls for these old posts. https://medium.com/apis-you-wont-hate/api-versioning-has-no-right-way-f3c75457c0b7 https://medium.com/apis-you-wont-hate/api-versioning-has-no-... https://blog.apisyouwonthate.com/api-versioning-has-no-right-way-f3c75457c0b7 https://blog.apisyouwonthate.com/api-versioning-has-no-right...