While reading in Chapter 13 "Managing Components and Dependencies" of Continous Delivery (July 2010, Addision Weslay) I asked myself how would one implement this if you implement the components (parts of the system, which change often) and libraries (internal/external parts, which change very seldom) in your own project.

A while back (1) for example contributing to PHPUnit was kinda tricky. You had to git clone the main repository and 11 different component repositorys. If you wanted to change something in these components, you could do that there. If you run continous integration, every change to those components needed to be in git master of those components. But how does one do a release then? Lots of tooling and in the end the release version number was hardcoded (2) and committed.

I am pretty sure that you know of some java project with dependencies, which work in a similar fashion.

When discussing this with my co-workers at exozet, we have been thinking how one approaches these concerns of lean development and safe releases with Java and Maven (or gradle). Of course we are using SemVer 2.0 as semantic versioning strategy. So I want to present an approach, which I infered out of the books example and would like to hear your opinion!

The expected release

Let's say our application is split up into two components, one library (the parent pom) and another library, which is used by one component. This is how a release looks like:

library-parent 4.0.0 component-a 2.0.0 (parent: library-parent 4.0.0) component-b 3.0.0 (parent: library-parent 4.0.0) deps: library-c 5.0.0

We will ship component-a in 2.0.0, component-b with 3.0.0 and both inherited from the library-parent in version 4.0.0. To build component-b , we need 5.0.0 of library-c .

If you hardcode this into each pom.xml every release of library-c and library-parent will trigger commit in component-a and component-b and if you don't do that in an automatic fashion, people will tend to forget it.

Development with -SNAPSHOT qualifier

So for development environment, we use something like this ( component-a/pom.xml ):

<artifactId> component-a </artifactId> <version> 2.0.0-SNAPSHOT </version> <parent> <groupId> com.example </groupId> <artifactId> library-parent </artifactId> <version> 4.0.0-SNAPSHOT </version> <relativePath/> </parent>

This enables us to regularly deploy SNAPSHOTs to the maven repository and other developers will get the latest version of each project as soon as they build.

According to Oracle the SNAPSHOT Qualifier the SNAPSHOT suffixed version number is considered as "as-yet-unreleased" version, so it minimizes the afford to update the integration build with a new version, without increasing the version number to often.

Building a Release

When ever we do a release we bump the PATCH versionnumber to a new value, but do not commit it. But we tag it!

So if we are working on library-c :

<artifactId> library-c </artifactId> <version> 5.0.0-SNAPSHOT </version>

and we want to build 501. First we tag the Version in the Git Repository:

$ git tag 5.0.501 $ git push --tags

Then the pom.xml becomes (but we don't commit this change):

<artifactId> library-c </artifactId> <version> 5.0.501 </version>

This can be done with the versions-maven-plugin's versions:set goal by executing:

$ mvn versions:set -DnewVersion = 5.0.501

And afterwards we deploy:

$ mvn package deploy

so, 5.0.501 of library-c is available from nexus and in the git repository. So if we want to get back to this sourcecode, we can do

$ git co 5.0.501

and are back at this version. With git co -b 5.0 5.0.501 we can even create a maintenance branch called 5.0 (if we really need one).

Increasing Major or Minor Version number

So if we did according to SemVer 2.0: MAJOR.MINOR.PATCH . A breaking change (increase MAJOR++ ) or a feature addition in a backwards compatible way (increase MINOR++ ), we have to bump the version number.

So <version>5.0.0-SNAPSHOT</version> in library-c will become to <version>5.1.0-SNAPSHOT</version> if we added a feature in a backwards compatible way.

Dependencies with Version Ranges

Since we are using SemVer 2.0, we can use Version Ranges for sticking to a specific range of versions for a dependency.

This version range of component-b to library-c :

<artifactId> component-b </artifactId> <version> 3.0.0-SNAPSHOT </version> <dependencies> <dependency> <groupId> com.example </groupId> <artifactId> library-c </artifactId> <version> [5.0,6.0) </version> </dependency> </dependencies>

defines, that component-b snapshot will work with any version of library-c >=5.0.0 and <6.0.0 .

This works, since maven (at least if you run it with -U or --update-snapshots parameter), will always favor the higher version from the remote repository for a range (except one of your other components expects a lower version of course).

If you want to go for a specific MINOR version, you can even use:

<version> [5.0,5.1) </version>

for everything between >=5.0.0 and <5.1.0 . This will stick the component-b only to incrementing patch versions (which in terms of SemVer should be the right thing to do).

Don't push SNAPSHOTs to the Repository

To ensure that the snapshots are resolved locally, you have to set your version in the pom.xml to a very high number at PATCH level (e.g. 65535)). So our pom.xml entry for library-c looks like this:

<version> 5.0.65535-SNAPSHOT </version>

because with 5.0.65535-SNAPSHOT the library's local snapshot version is preferred in maven over the 5.0.501 release on the remote maven repository (if your version range is (5.0,6.0] ). This wouldn't work if you have 5.0.0-SNAPSHOT in the pom.xml .

If you use this way for libraries, it's very important, not to publish snapshots to the maven remote repository. But this way makes it possible for you to keep your pom.xml unmodified, if you develop the library locally.

Conclusion

With this approach, my co-workers can develop locally with SNAPSHOTs and use for example eclipse's local resolution of dependencies to develop/maintain all components at the same time.

Additionally we have reproducible builds, since the versions are concrete enough to build a .jar of the library/component for nexus.

And since we use SemVer 2.0 and the wording of Chapter 13 of Continous Delivery (July 2010, Addision Weslay) we have:

static dependencies exact version number

for external libraries, which are not under our control guarded dependencies version range only for patch updates

for components/libraries, if we know a MINOR update will break our app or is intentionally skipped fluid dependencies version range for MINOR and PATCH updates

and updates for components, which we develop inhouse (so we control the versioning strategy)

Thanks for reading! What do you think? How do you handle versioned components in your projects or do you have some interesting links on this matter?

Changelog