There are already a lot of great resources out there on the whys, whens, and hows of REST API versioning, and we seem to be settling (more or less) on some general best practices. Likewise, there abounds a ton of info on the advantages, strategies, etc. for taking an API-first approach with OpenAPI (formerly Swagger).
Surprisingly, however, there's not a lot out there on the intersection of the two - i.e. what's the best way to version REST APIs when doing API-First with OpenAPI? I'm just getting familiar with OpenAPI, but in this post I hope to take a stab at this question.
Do you really need to version?
Probably the best place to start first is to make sure versioning is actually needed for a particular service. Independent of OpenAPI, a good versioning solution takes a non-trivial amount of forethought and development effort, and if you can avoid having to version altogether you can save a lot time and energy. Versioning an API is only necessary when either:
- You don't own the clients calling your API (e.g. you're providing an API to others)
- You do "own" the clients, but for whatever reason (there are too many, etc.) you aren't able to coordinate updates of the service/API with updates of the clients
Basically, if you're building a publicly available API, then versioning is of course necessary. Or even if you have some internally available microservice but it's called by many other clients (microservices, UIs, etc.) from other departments on different release schedules, etc., then versioning might also be crucial since it would be tough to force the clients to update in step with the service.
If what you have, however, is a service that has just a handful of other clients, all of which are owned by your development group and could conceivably all be upgraded/released in step, then it might be better to skirt the versioning issue altogether and just a support a single, canonical but evolving API. In other words, when the API changes, either do it in a way that doesn't break existing clients, or if a breaking change is necessary then go and update all the clients as well to adjust to the API changes, and release them altogether. Sure there are short-term coordination and development costs to doing this, but they may be less than the long-term costs that come with creating and sustaining a versioning strategy and all that comes with that (backward compatibility, deprecation, etc.).
TLDR; versioning is hard, and if you don't have to do it, don't!
When you do need to version
Ok, with all that out of the way, if it is determined that you need versioning, then how do you do it using OpenAPI and API-first? The central concept in API-first is obviously is the API spec itself, from which everything else is generated (clients, server stubs, documentation), so the big question is what to do with this? Specifically, how many API specs should there be (e.g. one per version, etc.)? And what do they encompass? It seems to me like there are two main options: (1) put all versions in one API spec, or (2) create a new API spec per version.
The first option, putting all versions of an API into a single api spec, does seem to have a few advantages. First, everything is in one yml file (i.e. paths for all versions of resources) and from this single spec, everything can be generated (e.g. clients, server stubs, etc.). Clients, services, stakeholders, etc. just need to look to one artifact for what they need, regardless of what versioning they're using.
Additionally, this allows for easily versioning at a resource-level, and not an API-level. For example, say there are just three resources:
/resourceA /resourceB /resourceC
...and say only one of the three changes, resourceA. With a single API-spec it's possible to only version the one that changed, resourceA, such that it would have both a /v1 and /v2, but the other resources, resourceB and resourceC, just have a /v1.
There are a couple major downsides though. First, having all the different versions in one spec will eventually get muddled in that even though it's easy to create new paths for each version of a resource, it's likely different versions of the components (data classes) will need to be created too, which are very similar to each other (e.g. Resource1V1, Resource1V2, etc.). A little messy. Additionally, if you're using the spec to generate clients, then clients could wind up with all versions of paths to choose from (unless tagging or whatever is used to generate a client with only v1). Tagging can be helpful here to only generate some portion of the API spec for a given client, but this could be tricky.Lastly, the generated classes (server stubs, component objects, etc.) for all the versions would end up in the same package, resulting in a bunch of classes like Resource1V1Controller and Resource1V2Controller, etc. Very messy.
The alternative is to create a new API spec for each version, and within this there are two sub-options: version at a resource-level or version at an API-levelVersioning at a resource level is possible, such that if resourceA changed from v1 to v2, but resourceB and resourceC stayed the same, then only resourceA could be moved to the api-v2.yml. This makes it clear what changed, but without some generation gymnastics, might require a client to import/bring in both clients for both v1 and v2.
The alternative here is enforce that the API is versioned altogether. From the perspective of the organization of the API spec, this seems much cleaner, but there will probably be a lot of redundancy across versions. For example, again if only resourceA changed from v1 to v2, and resourceB and resourceC stayed the same, you'd have to copy-paste over the entire specification for resourceB and resourceC into the v2 spec document even though they didn't change. This duplication just feels wrong, but if you don't like the other options, it might be a necessary evil though.
It's probably important to mention here that there is a version property in the API spec itself, and while it would seem that this should define the version of the API itself, it actually is intended to define the version of the API specification document (a subtle nuance that was a little confusing to me).
There's plenty more to discuss, but I just wanted to broadly paint the different options. The take-away for me is that versioning is not easy, and when you can, it's better to evolve an API either in a way that is non-breaking for clients or to evolve the API and clients at the same time. Creating an entire new version of an API should only as a last resort. I'd love to hear anyone else's thoughts!