10

Similar question: How do you handle versioning in a multi-sided project?

Since the question above was asked almost 4 years ago, I was wondering if any new ideas have emerged .

-

We have a situation where we develop a platform that will have multiple instances. For example:

In general, all instances should have a closely synced version lifecycle, but sometimes we may want to have a instance with the early-access version intended for testing.

In addition to above, we are going to have Android/iOS apps which will connect to some instance (user has to select which one). It is important to note that all instances operate on a different database - no replication should exists between any of these.

The problem:

Once we make changes to server-side, we are going to release new version of both server and client version.

For servers, it should be straightforward as we control which version is installed on which instance.

For clients, as soon as we release a new client app version, users can pull it from respective store and get it installed, but what happens when they actually use the instance which is still running the old server version? Or when client is unable to update due to outdated Android/iOS version?

Any idea on how do we handle that?

One thought I had was to provide an endpoint /version.json so the client app can get the server version and choose how to operate. But this, imho, could quickly lead to massive code bloating/duplication. What do you think?

Jovan Perovic
  • 219
  • 1
  • 7
  • version EVERYTHING – Ewan Aug 24 '18 at 16:57
  • @Ewan: Of course, but while we can control which server gets updated, we have not such thing on client side. Their devices will pull the new version automatically from the store, which complicates things :-/ – Jovan Perovic Aug 25 '18 at 21:46
  • Have you not accepted an answer yet? I thought they were pretty clear. As long as you know the version its only a question of how long you keep the old ones running – Ewan Aug 25 '18 at 21:52
  • 1
    How about less versioning? or Defer It for as Long as Possible. The pre-condition of this is the readers/clients are tolerant enough(https://martinfowler.com/bliki/TolerantReader.html). Then the next question is when to introduce version. Till breaking change. Try consumer driven contract out as a tactic thing (https://martinfowler.com/articles/consumerDrivenContracts.html). – ivenxu Aug 27 '18 at 00:15

3 Answers3

5

Version your server API. The client doesn't need to know what the current version of the API is, the client needs to know what version of the API your instances support.

There are two general options for requesting a versioned resource. You can include the version you are requesting from in the URI, or you can include it in an HTTP header:

Example including versioning in URI:

GET http://instance{x}.myplatform.com/v1/{resource}

Example including versioning in headers:

GET http://instance{x}.myplatform.com/{resource}
X-API-Version: v1

GET http://instance{x}.myplatform.com/{resource}
Accept: application/json+v2

Of course, you could also choose to support both methods. Notice I'm only including the major version number. If you're adhering to semantic versioning, the client really only needs to be aware of the major version number it is using (since that changes only when backwards compatibility breaks). You can choose to supply the entire version number if you like, but it may increase the logic you would need to handle on the server-side in order to determine what code needs to be executed. The client knows what version of API it was developed against, so it should be able to have that information readibly available when making requests.

This does mean that you need to have older versions of your API hanging around, but it makes sense since you have older versions of your client out in the wild with no control over when they get updated. You can monitor usage of your API by version and make the decision to pull the plug and completely remove them whenever it drops below some threshold.

Your /version.json approach could be useful if your API instances can ever be in a state where they have different major versions they support. In that case, before the user selects which instance they wish to connect to, you would have to interrogate them and determine if they support the version of the API that the client requires. If they don't, then the client would not want to connect to them.

Jeff Lambert
  • 559
  • 2
  • 9
  • Initially I was against the idea of having the version number within the URI, but I actually like the approach with custom header. Needless to say, my server-side will be able to extract that info and either tweak the behavior or just switch to another implementation if the difference between versions were substantial. Thanks! – Jovan Perovic Aug 25 '18 at 21:38
4

Focus on ensuring that the messages passed between different versions of the protocol can be understood.

Broadly, this means

  • You never add a new required field to a message
  • Optional fields can be added, but a default value must be specified in the protocol
  • Fields are never re-used for a new meaning.
  • Consumers must ignore unrecognized optional fields

So if you send to me a message that has optional fields that I don't know about, I ignore it. If your message is missing an optional field that I expect, then I can use the message protocol to determine what default value should be used.

One reference that covers this fairly well is Greg Young's Versioning in an Event Sourced System.

When XML was ascendant, Must Ignore semantics were a common topic. See David Orchard.

You might also look at Eric Wilde: Patterns for Robust Extensibility.

VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79
  • Since separate customers will use their instances, this represents an issue. If one customer decides to lock into `v1.0` and in the meantime we release `v1.1` through `1.5`, we will not be able to introduce new fields required fields without risk of breaking the client app. Even so, if we release `v2.0` (both server and client), clients of `v2.0` client will problably have hard time talking to `v1.0` server. :( So, does that basically boils down that only new major version can have BC breakings? Thanks! – Jovan Perovic Aug 25 '18 at 21:44
4

Custom proxies and stubs, which handle version adaptation

So clearly to make this work well, you need to version your API. Each different version of the API which may see action in the field requires a new version.

Then, I create a library (for any language that will access this WS API), which is the native language bindings for that API.

note - some languages provide tools to automatically create proxies and stubs. While these can be used internally, they should not be published directly for this purpose (client/stub library for your service API).

The published library API should always match the LATEST version of the protocol (as closely as possible, very rarely you may need to generalize slightly to accommodate different versions).

This proxy/stub API, at its start of communications, can establish the version of the peer its talking to, and downgrade communications as needed.

note - as typically, for most methods, this proxy/stub layer can be pretty simple, but it provides a layer where all protocol translation takes place.

Then, all the rest of your server/client code (again, in whatever programming langugage) - is just written to the library API - which matches the latest (at the time that server/client code is written) version of the protocol.

I've used this approach on multiple projects, with C++, python, C#.net, and javascript.

Lewis Pringle
  • 2,935
  • 1
  • 9
  • 15