Semantic Versioning is a release versioning convention that simplifies managing dependencies of a project.

So what is Semantic Versioning? Let’s look at an example.

A Semantic Versioning Example

Let’s imagine that you and your team are creating a PHP web application which needs to rotate some images. Your company is heavily involved in image processing so you know that you are going to need to rotate images in a few different projects so you decide to put the code that rotates the images into a separate library, that way each project that needs to rotate an image can use the library and there is less duplicated code. It’s a big win.

For the rest of this article I’ll use the following terminology: the “library” is the code which is shared across multiple “projects”, all of the “projects” use the same “library”.

The Library is Born

You get hard to work and you create a new library lovingly called “our-company/php-image-manipulation”. As you make the final commit to the library you start thinking about how your projects are going to pull in this library.

Let’s imagine that you’re in the PHP world so you use composer to bring the image manipulation library into your first project as a dependency and you begin to use it. As a side note, other ecosystems have other tools such as npm/yarn for JS, all of these tools manage dependencies, in the case of composer it will install all dependencies for us and give us a handy autoloader that will find any class in our dependencies from our code.

Here’s the line that we use to specify our imaginary image library as a dependency in a project (this will be different for other tools, for now the important concept to grasp is simply that we are depending on one library from our project):

{ "require" : { "our-company/php-image-manipulation" : "dev-master" } }

Notice that we specify “dev-master” for the version. This means that we always want the latest version of the code from the master branch. This is great for our newly hatched image library.

The library grows to version 1.0.0

After a while your company has two projects each using the image rotation library. Each project is using the bleeding edge of the library: the master branch. Things are going really well, when bugfixes are found they are fixed and when each project updates its dependencies they get the bugfixes free of charge. Things are going so well in fact that you decide that it is time to release a “1.0.0” version of the image rotation library. You do, and you change the dependency version constraint to this:

{ "require" : { "our-company/php-image-manipulation" : "~1.0" } }

This “~” constraint operator means that we want the project to use “any version above 1.0 but below 2.0”. To be specific, the first number is not allowed to increment but the last number is. This way you know that you will continue to get the bugfixes but if there is a breaking change then we don’t want it yet, so everything will continue to work without problems and we can update the dependencies freely.

We’re choosing to release version “1.0.0” because we’re happy that the library is stable and working, we’ve been using it for quite a while already and we’ve managed to find and fix most of the bugs. Up until now the library didn’t have any version number, we just used the latest commit in master. It’s common to release version 1.0.0 when the code is considered stable and unlikely to change. Before the 1.0.0 release, some teams prefer to release 0.x.x versions while others prefer to use dev-master as we have done in this example.

A bug is found and squashed

Soon after the release of version 1.0.0 somebody finds a new bug. Your team quickly fixes it but now you need to release it to all the projects that use the library. You decide to release version 1.0.1 of the library, the last number incrementing to represent a bugfix or an aesthetical change.

Because our projects are already matching “~1.0” we just need to update our dependencies by running composer update , the bugfix will automatically be pulled in because our version constraint allows “any version above 1.0 but below 2.0”. Great!

Time for version 2.0?

Time goes by and the image processing library starts to get more and more use so you and your teammates start to think about changing a few things in the library in order to make it easier to use. Up to now you’ve just made bugfixes in the library, you’ve never made any change that might break both the projects that use it. However you discuss it with your team and you decide that it’s for the best, you are going to introduce a backward-incompatible change to the library in order to improve it.

At this point let’s stop and discuss the consequences of this change. If you make this change you will need to upgrade both of the projects which use the library so that they use the new version of the library. This is the key; you won’t be able to upgrade the library and expect everything to work, instead you need to perform some manual fixes to make your code work (again) with the new version of the dependency.

Back to your companies image library, you decide to release a new version, version “2.0.0” so that all of your projects can upgrade only when they are ready. This means that when either of your projects update their dependencies nothing will break since they are pinned to “any version above 1.0 but below 2.0”. In other words, even though you have released version 2.0 of the image library which changes some public methods and therefore breaks backwards compatibility, the code that depends on it does not break because we are pinned to version 1.x. We don’t have to upgrade to the newest version immediately, we can upgrade whenever we like. Essentially we have decoupled the advancement of the library from the advancement of our project, now if the library grows (and changes) the code in our project won’t break immediately, we can allow it to grow at it’s own pace.

Recap: The Official Definition

Hopefully this example gave you a quick understanding of the versioning numbers and why they are important. The format of the numbers have actually been formalized by semver.org, here is what it says:

Given a version number MAJOR.MINOR.PATCH, increment the: MAJOR version when you make incompatible API changes,

MINOR version when you add functionality in a backwards-compatible manner, and

PATCH version when you make backwards-compatible bug fixes.

This is what we have already been doing and it is exactly this naming convention that allows us to use the version constraints effectively in our projects.

Now you should see the elephant in the room: if the libraries that we use don’t use Semantic Versioning then one day when we update our dependencies we might pull in a change to a library that is not backward compatible, and that will break our code. One of the biggest advantages to Semantic Versioning is that is decouples the growth of the libraries from the projects, so we aren’t forced to upgrade all the projects as soon as a new version of the library comes out.

A Note on Public Facing Interfaces

A quick word on public facing interfaces in the context of reusable code libraries.

Any library that will be used by other projects has a “public facing interface”, by this I mean that the code is exposing a certain set of classes and or functions for you to use. These are the functions/methods that you can read about in the documentation. A library might have lots of code in the “background” that are never meant to be called directly from outside of the library, this code is not part of the “Public Facing Interface”, only the methods and signatures that will directly be used by the client are.

This concept is important because it is only the public facing interface that needs to be taken into account when dealing with Semantic Versioning. If something in the depths of a library changes but the way in which the library is used from the outside doesn’t change, then the change can be made without any problems and any project which depends on the library will go on unaware that the change even happened.