Versioning interface protocol APIs

As I’m working on a new interface protocol, I’m remembering the headaches I’ve had in the past when trying to refactor the protocol (key / value pairs, not the code API in front of it) due to bugs, new features on the interface (e.g., moving from single to multiple requests), or wanting to use new Juju features (e.g., app relation data) while still retaining backward compatibility and also trying to not make the code a mess. Because both sides of the relation might have different levels of support, you also have to take in to account differences on both ends. I’ve repeatedly found myself wishing the original author of the interface protocol had the foresight to include an API version in the relation data and that we had a good pattern to follow for negotiating a commonly understood version to be able to talk.

I’d really like to see something like this built into the framework, even if it’s just a set of helpers that we strongly encourage developers to use. I generally like the way the Juju controller & client handle Facade versions and would like to see a pattern like that made easy to use with charm interfaces, but that does require that both sides be aware of the version negotiation from the start, and a lot of existing interfaces may want to “upgrade” to a versioned pattern but still have to support charms built against the unversioned protocol.

For now, at least, I’m going to ensure that my new interface protocols include and block on a version number in the app-level relation data.

Thoughts? Any existing patterns or plans that I’m not aware of?

From a charm author perspective, it would be neat if we could borrow this version syntax:

https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html

So in metadata.yaml, I could specify versions like this:

requires:
  foo:
    interface: foo
    version: ">=2.0, < 3.4"
provides:
  foo:
    interface: foo
    version: "~3"

And Juju takes care of figuring out that the two charms can talk using foo==3.3 as the latest version, and uses that.

Yeah, I’ve been informed that “epochs” are something being considered for this case. I don’t think they need to be as complicated as the Rust syntax, since it sounds like it’s mostly just a way to enforce that if a charm includes a breaking interface protocol change, you have to upgrade the other side in tandem. It still leaves some significant questions for the interface library authors, such as how to handle upgrading from one epoch to the next, but it would certainly make things a lot better if you know you only need to support a transition path from the previous epoch.

I’m not sure versions defined in the charm metadata would be super useful as there are no definitions for what a protocol for a various interface is. For example two charms could say they support the http interface but aren’t able to communicate.

The first step might be to define what a various set of popular interfaces should be and then version those.