Versioning

Versioning helps us to create cornerstones in a project so that we can understand the project state. In this part versioning and semantic versioning will be discussed.

Semantic Versioning

Why do we need it?

As the bigger our systems grow, and the more packages we integrate into our software; then releasing new package versions can start to become problematic:

  1. Version Lock: If we specify dependencies too strict then we can’t upgrade a package without upgrading all packages dependent on it.
  2. Version Promiscuity: If we specify dependencies too loose then we are being wishful about compatibility between different versions.

Semantic versioning allows us to convey what have changed from one version to the next, that way we can create clever dependency specifications and keep developing and releasing new versions of our packages without unnecessarily being have to update all dependent packages.

What is it?

Semantic versioning offers a scheme as below:

[MAJOR.MINOR.PATCH]

Version Incrementing Rules

Increment the:

  • MAJOR version when you make incompatible API changes. It MAY also include minor and patch level changes. Minor and patch version reset to 0 when major version incremented.
  • MINOR version when you add functionality in a backwards compatible manner. It MAY include patch level changes. Patch version reset to 0 when minor version incremented.
  • PATCH version when you make backwards compatible bug fixes.

Remarks

  • Software using semantic versioning MUST declare a public API. It should be precise and comprehensive.
  • MAJOR version 0 is for initial development. The public API should not be considered stable.
  • A bug fix is defined as an internal change that fixes incorrect behavior.

Reference: Semantic Versioning Specification

Semantic Versioning in Action

In this part we will develop a hypothetical application gradually and use semantic versioning along the way. Let’s say you’re building an application that is called network-manager. It has one initial requirement, it should return the IP address of network interface wherever it runs and that functionality should be available under /get-ip-address endpoint.


NOTE

There are several ways to communicate with the application. We can use arguments/options, listen for keyboard strokes, start a server inside and communicate via a client application. We’re not going into details but the idea is that an application one way or another presents interface to interact with the world around it and that part is called public API. It’s important to define which part of your software constitutes public API to use Semantic Versioning correctly.


After adding this functionality we can create a tag as v0.1.0 to point initial version.

If you run the application and reach for /get-ip-address endpoint, it responds as below:

{
    "interface": "enp0s3",
    "ip_address": "192.168.10.2/24"
}

After a certain amount of time you realized that it takes too much time to respond. You find out the reason and send a bugfix. Time to pump the version. This change was backward compatible because the application still responds the same way. Also we didn’t add new functionality. So no need to bump major or minor units, just increasing patch unit will be enough. So the new version is v0.1.1

It’s time to bring new functionalities to the table ! You realized that it would be a good idea to show MAC address and it should be reachable via /get-mac-address endpoint. After developing this functionality time to check if everything works fine.

So you run the application and ask for MAC address via /get-mac-address, here is the response:

{
    "interface": "enp0s3",
    "mac_address": "0a:0b:0c:0x:0y:0z"
}

It looks fine and we should give a new version. For now we have two different endpoints (/get-ip-address and /get-mac-address). Since we didn’t touch /get-ip-address endpoint, it still behaves as before and because we didn’t have any other than /get-ip-address, it’s okay to say this change was backward compatible. Yet this is a new feature, so we should bump minor unit. The new version is v0.2.0

Network-manager returns the first interface it reaches currently and because we only got one network interface in our workstation that was okay. But there might be more than one network interfaces where our application runs. The new goal is returning all IP and MAC addresses if applicable. After few changes, time to run basic checks in a different environment where we got two physical network interfaces:

/get-ip-address responds with:

[
    {
        "interface": "enp0s3",
        "ip_address": "192.168.10.2/24"
    },
    {
        "interface": "enp0s4",
        "ip_address": "192.168.11.2/24"
    } 
]

/get-mac-address responds with:

[
    {
        "interface": "enp0s3",
        "mac_address": "0a:0b:0c:0x:0y:0z"
    },
    {
        "interface": "enp0s4",
        "mac_address": "1a:1b:1c:1x:1y:1z"
    } 
]

Before this change endpoints were returning inside JSON objects, but now it returns JSON objects inside an array. This means consumer of the network-manager should change their code to be compatible with new version of network-manager and our change was not backward-compatible. We have to bump the major unit. The new version is v1.0.0