How do you handle API versioning?

There is a video from Lonestar ElixirConf 2018 by Niall Burkley which I thought was a very interesting alternative approach to API versioning… https://www.youtube.com/watch?v=puUr9_zzTm4

Thank you, will take a look.

1 Like

Namespacing your API is thinking in a big enterprise way. You are good to go with one or maybe two versions e.g. one for legacy support and the main one. But if you are breaking constantly your API, your business will lose reliability, plus everybody hates big rewrites. You do need to make some serious breaking changes to deprecate an API, otherwise, just update the main version.

Let’s say GitHub, they currently have v3 (REST) and v4 (GraphQL), that may be another reason to have different versions (https://developer.github.com/v3/versions/), but do not get crazy about it, URL versioning is good enough.

1 Like

It also makes a big difference if you’re taking about internal api clients (like your own apps) vs. supporting external clients

I’ve thought a good deal about this at my company. We are developing both internal / external APIs and we treat them differently. I’ll go into detail about the strategy.

Here is some general advice:

  1. Very carefully consider what your API strategy will be from day 1. If you’re serious about the API, maybe you want to make it publicly available which means that your internal teams need to be careful with what a version bump means and when to release new endpoints.
  2. Introduce a way to make APIs that are private but based on the same consistent strategy as internal.
  3. Define what you are comfortable with from a versioning perspective.
  4. Create a list of what you’d want to bump in a version and only do it every so often. Avoid bumping the version a lot because it’s chaotic.

Here is some about our solution:

  1. We use URL based API versioning. This looks like https://api.salesloft.com/v2/people.json
  2. We are very very conservative with what it means to bump the version. We have a plan to do so if necessary, but are strongly careful to not need to. We only bump the version if there was absolutely no path forward to not break existing integrations. We haven’t had to do this yet, and there are usually ways to avoid needing to do it.
  3. We were not careful enough with v1 and had to completely scrap it when we went to v2.
  4. Our versioning strategy is based on the idea that the current code path should reflect the most recent version. Version changes should happen in “adapters” which are not in the main code path. We 100% avoid having duplicated API methods across versions.
  5. We don’t make API endpoints available immediately, only once we’re comfortable they won’t change. We are okay making breaking changes internally because we can account for 100% of use cases and update code before it breaks. You cannot guarantee this once an API endpoint is public.

We ported our Ruby API “framework” into Elixir and use it there. The blog post describing some concepts is at https://medium.com/salesloft-engineering/building-the-new-salesloft-api-99660c13b539. In Elixir, we end up creating Api Methods (logic) and Resources (data containers) which auto generates Swagger documentation for us.

4 Likes

We use a JSONAPI.org API at work and take the approach that foo_v1 and foo_v2 are distinctly separate resources to the consumer. This means the resources can add fields if they need, and if there’s a reason to change the semantics behind something, just bumping the resource version and creating the new one, issuing warnings on use of old versions to the consumer. This doesn’t apply to more stable APIs, e.g. from a public API like Facebook, but works very well with smaller teams.

1 Like

Just wanted to introduce my Versioning package that aims to address some API versioning issues that commonly come up. Its aim is to provide a way to easily implement a versioning scheme that is similar to the way stripe handles it.

The basic idea is that a user is “pinned” to a given version - or a specific version is requested via a header. Changes to the API are made through use of “change” modules. A change module must only concern itself with implementing changes to and from the version above/below it for a specific data type. Our change history is described through a “schema”. Using the schema, one can modify data from its “current” version to a “target” version. This allows small incremental changes to be made without major issue. It also makes testing incredibly easy.

Feel free to check out the github and documentation for further details and examples.

Its still a work in progress, but its got most basic features in. I still aim to provide a helper module for Plug/Phoenix that will make integration with it pretty easy.

Cheers

5 Likes

Websockets handles headers fine though?

I personally prefer to version specific calls rather than the URI or mimetype or so. If a call transforms then transform it’s name as well, along with potentially deprecate the old one and remove it over time if it eventually is entirely replaced or no longer relevant.

I.E. Keep the API itself once something is defined in it immutable, only add new entries and only if necessary then deprecate and remove old names.

Changing how a specific call works is horrifying though, no place should do that.

Yeah, but only when establishing connection, it’s only topics and payload afterwards, not that flexible without much on the plus side.

But to pass the version with a call you have to use something, or how do you version the calls?

ditto

I don’t, just when I define a call/endpoint/whatever like do_something then I never change it’s interface after that, it will always either exist or eventually be deprecated (and not removed unless I truly cannot support it anymore). If I need to write a replacement for do_something then I’ll name it something more descriptive like do_something_and_return_blah or whatever.

1 Like

So, no versioning at all then? Interesting :slight_smile:

There’s no need and no worry when you keep a specific action always doing the same thing. If it does ever get removed then never ever ever reuse the name. :slight_smile:

1 Like

I understand the idea, though doesn’t it get complex over time? Your endpoints would get weird and as soon as you run out of creativity and have a do_something_and_return_blah2 - you’d have a kind of chaotic versioning?

When in doubt, follow Stripe. Date based and in the URL. It works really well in practice as both a consumer and supplier.

I am implementing Multiverse in my current project.

1 Like

Never have as of yet, but if a place is changing endpoint names that often then they probably have a bigger issue elsewhere. ^.^;

Regardless, never ever append something like 2 to a name unless it actually relates to the functionality, ever!