Versioning is one of those things that can be done in many different ways. Almost every team I worked with came up with their own versioning schema. When starting a new project, quite often we would spend time debating how we are going to version our releases. And yet, coming up with our own versioning schema is usually a waste of time. The goals of versioning are simple. We need a unique identifier of a release as well as an indication of whether a change breaks backward compatibility. Given that others already agreed on the format that fulfills those objectives, the best we can do, just as with many other things related to software development, is to use the convention. Otherwise, we are probably wasting our time reinventing the wheel without understanding that a few things are likely going to go wrong down the line.
First of all, our users might need to know whether our release is production ready and whether it breaks compatibility with the previous releases. To be honest, most users do not even want to know that, and they will merely expect our applications always to work, but for now, we’ll assume that they do care about those things. Somebody always does. If the end users don’t, the internal ones (e.g., engineers from the same or from other teams) do. They will assume that you, just as most of the others, follow one of the few commonly accepted naming schemes. If you do, your users should be able to deduce the readiness and backward compatibility through a simple glance at the release ID, and without any explanation how your versioning works.
Just as users expect specific versioning scheme, many tools expect it as well. If we focus on the out-of-the-box experience, tools can implement only a limited number of variations, and their integration expectations cannot be infinite. If we take Jenkins X as an example, we can see that it contains a lot of assumptions. It assumes that every Go project uses Makefile, that versioning control system is one of the Git flavors, that Helm is used to package Kubernetes applications, and so on. Over time, the assumptions are growing. For example, Jenkins X initially assumed that Docker is the only way to build container images, and kaniko was added to the list later. In some other cases, none of the assumptions will fit your use case, and you will have to extend the solution to suit your needs. That’s OK. What is not a good idea is to confuse specific needs with those generated by us not following a commonly adopted standard for no particular reason. Versioning is often one of those cases.
By now, you can safely assume that I am in favor of naming conventions. They help us have a common understanding. When I see a release identifier, I assume that it is using semantic versioning simply because that is the industry standard. If most of the applications use it, I should not be judged by assuming that yours are using it as well, unless you have a good reason not to. If that’s the case, make sure that it is indeed justified to change a convention that is good enough for most of the others in the industry. In other words, if you do not use a commonly accepted versioning scheme, you are a type of the company that is hoping to redefine a standard, or you are just silly for no particular reason (other than not knowing the standards).
So, what are the commonly used and widely accepted versioning schemes? The answer to that question depends on who the audience is. Are we creating a release for humans or machines? If it’s the former, are they engineers or the end users?
We’ll take a quick look at some of the software I’m running. That might give us a bit of insight into how others treat versioning, and it might help us answer a few of the questions.
I’m writing this on my MacBook running macOS Mojave. My Pixel 3 phone is running Android Pie. My “playground” cluster is running Jenkins X Next Generation (we did not explore it yet). The list of software releases with “strange” names can go on and on, and the only thing that they all have in common is that they have exciting and intriguing names that are easy to remember. Mojave, Pie, and Next Generation are names good for marketing purposes because they are easy to remember. For example, macOS Mojave is more catchy and easier to memorize than macOS 10.14.4. Those are releases for end users that probably do not care about details like a specific build they’re running. Those are not even releases, but rather significant milestones.
For technical users, we need something more specific and less random, so let’s take a look at the release identifiers of some of the software we are already using.
I am currently running
v1.13.2, and my Kubernetes cluster is
1.12.6-gke.10. My Helm client and server (tiller) are both at the version
v2.12.1. I have
jx CLI version
1.3.1074 and Git
2.17.2. What do all those versions have in common? They all contain three numbers, with an optional prefix (e.g.,
v) or a suffix (e.g.,
-gke.10). All those tools are using semantic versioning. That’s the versioning engineers should care about. That’s the versioning most of us should use.
Semantic Versioning Explained
Semantic versioning is very easy to explain and leaves very little to interpretation. We have three numbers called major, minor, and patch. If we increment minor, patch is reset to zero (or one). Similarly, if major is incremented, both the minor and the patch are set to zero (or one). Which number is incremented depends on the type of the change we’re releasing.
Given a version number
MAJOR.MINOR.PATCH, increment each of the segments using the rules that follow.
- PATCH is incremented when we release bug fixes.
- MINOR is incremented when new functionality is added in a backward-compatible manner.
- MAJOR is incremented when changes are not backward compatible.
If, for example, our application has an API, incrementing the major version would be a clear signal to our users that they would need to adapt or continue using the previous (older) major version (assuming we keep both, as we should). A change in the minor version would mean that users do not need to adapt, even though there are new features included in the release. All other cases would increment only the patch version.
Deducing which version to increment for an application that does not contain an API is a bit harder. When a change to a client-side web application is backward compatible or not largely depends on how humans (the only users of a web app) perceive backward compatibility. If, for example, we change the location of the fields in the login screen, we are likely backward compatible. However, if we add a new required field, we are not and, therefore, we should increase the major version. That might sound confusing and hard to figure out for non-API based applications. But there is a more reliable way if we have automated testing. If one of the existing tests fail, we either introduced a bug, or we made a change that is not backward compatible and therefore needs to increment major version.
I believe that semantic versioning should be used (almost) always. I cannot think of a reason why we shouldn’t. It contains a clear set of rules that everyone can apply, and it allows everyone to know which type of change is released. However, I’ve seen too many times teams that do not want to follow those rules. For example, some want to increment a number at the end of each sprint. Others want to use dates, quarters, project numbers, or any other versioning system they are used to. I believe that in most of those cases the problem is in accepting change and not a “real” need for custom versioning. We tend to be too proud of what we did, even when the rest of the industry tells us that its time to move on.
The problem with using our own versioning schema is in expectations. New developers joining your company likely expect semantic versioning since it is by far the most commonly used. Developers working on applications that communicate with your application expect semantic versioning because they need to know whether you released a new feature and whether it is backward compatible. The end users (e.g., people visiting your Web site) generally do not care about versioning, so please don’t use them as an excuse to “reinvent the wheel”.
I would say that there two significant reasons why you should use semantic versioning. First of all, something or someone depends on your application. Those who depend on it need to know when an incompatibility is introduced (a change of the major version). Even if that’s not the case, the team developing the application should have an easy way to distinguish types of releases.
Now, you might say that you do not care for any of the reasons for using semantic versioning, so let me give additional motivation. Many of the tools you are using expect it. That’s the power of conventions. If most of the teams use semantic versioning, many of the tools that interact with releases will assume it. As a result, by choosing a different versioning schema, you might not be able to benefit from “out-of-the-box” experience. Jenkins X is one of those tools. It assumes that you do want to use semantic versioning because that is the most commonly used schema. We’ll see that in practice soon.
Before we proceed, I must make it clear that I am not against you using some other versioning schema. Please do if you have a very good reason for it and if the benefits outweigh the effort you might need to invest in overcoming hurdles created by not using something that is widely adopted. Coming up with your own way to do stuff is truly great, as long as the reason for deviation is based on the hope that you can do something better than others, not only because you like it more. When we do things differently for no good reason, we are likely going to pay a potentially high price later in maintenance and investment that you’ll have to make in tweaking the tools you’re using. The need to comply with standards and conventions is not unique to versioning, but to almost everything we do. So, the short version of what I’m trying to say is that you should use standards and conventions unless you have a good reason not to. What that means is that you should be able to defend why you are not using an industry-defined convention, and not to seek a reason why you should jump into something most of the others already adopted.
The DevOps 2.6 Toolkit: Jenkins X
The article you just read is an extract from The DevOps 2.6 Toolkit: Jenkins X.
The book is still in progress, and I do not yet have a clearly defined scope. I write about tech I’m working with and that interests me the most. Right now, that’s Jenkins X.
You can get the book from LeanPub. If you do, you’ll get updates whenever a new chapter is finished. At the same time, you can get more actively involved and send me your comments, suggestions for the next topics, bug reports, and so on. I’d love to hear back from you.