AsyncAPI versioning is easy, right?

Jonas Lagoni Avatar

Jonas Lagoni

ยท7 min read

As GamingAPI is using AsyncAPI designing the system, I need to figure out how I want to do versioning.

Because others can interact with the services provided, it can have "catastrophic" ripple effects downstream if I don't follow a reliable and consistent strategy. Imagine I accidentally create a small version change but it breaks downstream services? Yea, let's not do that if we can avoid it.

However, there are no well-defined resources for how to handle versioning in AsyncAPI (yet). Therefore we will have to do a bit of investigation as to how to handle this.

As it turns out, versioning is by no means easy as the title hints at... The version strategy you use with AsyncAPI depends on many different variables that affect the pros and cons of the type of strategies you can use. Let's try and determine what strategies are there and what affects the decision.

TLDR: No one strategy fits all - There is no silver bullet! Know the different choices and weigh the possibilities to the best of your ability and learn as you go.

Possible version strategies

As OpenAPI defines REST APIs, there might be some learning from them we can take into account.

Many resources explain versioning, both with use-cases and concrete theories, however, I want to highlight two of them. Phil has written a very nice post about how there is no silver bullet for versioning (like anything in life). Supplementing that with Mark's post about API evolution it gives you a pretty good idea about how to achieve it for OpenAPI.

One thing that the other resources do not mention is schema versioning. As far as I can figure out, it's not a thing in OpenAPI, however, it is for us because products have emerged to produce such features for event-driven architectures, i.e. schema registry.

Global version

1asyncapi: 2.3.0
2info:
3  version: x.x.x

In AsyncAPI you can define the version for the AsyncAPI document (as for OpenAPI), which is the global version of the API. Resource versioning as Phil mentions is local for that specific resource and document version is global. I can't find much information about how people currently version this in practice, but I imagine it's the same as any other library.

One thing to notice is that there is different versioning types here. The most used (that I know) of version types are calendar versioning and semantic versioning, which both have benefits and drawbacks.

The way I see the difference between the two is that calver is always a new major version in semver regardless of what changed.

Things you might want to consider

Now that you have the basic rundown of the different ways to do versioning, let's look at what might affect your decision making.

Product requirements

The requirements for the product you develop can have influence on what version strategy to use.

Do you have control over all the applications which interact in the system? Can you decide when to update producers and consumers?

For example with GamingAPI, I plan to make the underlying events completely public, and have NO control over producers and consumers, and therefore as to which versions are used.

Protocol specific

Each protocol has specific quarks and features that might be leveraged with a specific version strategy.

For example with NATS, you have the possibility to granularly fine-tune access control on subjects through permission maps. Through this if I use resource versioning for the topic, I will be able to control who and when new versions are made available.

Event-driven architectures

There exist many different event-driven architectures that can affect the decision on a version strategy.

Consider event-sourcing, where you have a single source of truth of what happened in your system. There already exist version strategies that can be adapted, however, how do these relate to AsyncAPI and the rest of the affecting variables?

The same of course goes for regular Pub/Sub messaging and other architectures.

Product and tooling

The products and tooling might also affect your decision-making.

Say semver is more supported in tools/products than calver, if you went with calver it would probably mean you would be forced to build some of the tools yourself.

By products I mean external platforms and tools not publicly available, and what those are depends on the technology stack you use.

GamingAPI

So what will it be? A or B? Honestly I found it hard to choose, but eventually, I settled upon the following strategy.

Semver

1asyncapi: 2.3.0
2info: 
3  version: x.x.x

I am going with semver as the backbone of the versioning strategy, because:

  • As much as possible I want to limit breaking changes and when it affects the users of the network. This means that there are some very strict limitations on what may change in the AsyncAPI document.
  • In tooling, I want to utilize sematic release and I assume that there is just better tooling support for this than calver based on my experience.
  • As I do not plan to release the API one time each year, but continuously, it makes more sense to stick to semver.

Resource versioning

1asyncapi: 2.3.0
2info: 
3  version: 1.2.0
4channels: 
5  v1/game/server/started:
6    ...
7  v2/game/server/started:
8    ...

I am going to include a major version number in the topic alongside using semver for the document version of the API.

My reasons for using it is:

  • I want to control when a resource is breaking, meaning that I can start to work on the new version and integrate it as I go, without having to break the entire API. Eventually, I can clean everything up and break the entire API once I fine-tune it.
  • As mentioned earlier regarding NATS (as I plan to use), I want to control who have access to new versions through permission maps.

Furthermore, to explain the version part, v1 only refers to the topic itself and the versioning herein. This means that if I make something that can be considered a breaking change of the event, I would need to increase the version number to stay compliant. What the system will consider a breaking change can be read below.

If I wish to change the topic to something different, say I want to go with server/started instead of game/server/started (v1/game/server/started). The new topic would be v1/server/started, because it's the first time this topic enters the system, hence v1.

Breaking changes

So what is considered a breaking change, and why is it important we discuss it?

Consider the following channel payload:

1channels:
2  v1/game/server/started:
3    publish: 
4      message:
5        payload:
6          type: object
7          required: 
8            - test
9          properties:
10            test:
11              type: string

Say I wanted to make the test property optional, do you consider that a breaking change?

The reality is that it's both, and it depends on your perspective. For anyone who produces the event, because if it was required before, old producers will always include the property, for new producers they potentially include it. So from that perspective, there are no issues.

But what about your consumers? If they expect the property to always be present, and it's suddenly is not? Well, that's a breaking change for them.

You can reverse this example as well, so it's a breaking change for the producer instead of the consumer (This is also a subject for discussion in the AsyncAPI specification itself).

Therefore, there are very few things you are allowed to change (I don't think this is the full list, but this is what comes to mind at the moment):

  • Adding new optional properties and enum values
  • Changing meta-information (such as description, etc)

Anything else is not allowed to change in any way without it being considered a breaking change.

Next

At least that is my thoughts at the moment, will they change in the future? Maybe... The next question becomes, how do you use the version strategy in practice?

Related resources about the subject:

Photo by Chris Lawton on Unsplash