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
library-parent in version 4.0.0. To build
component-b, we need 5.0.0
If you hardcode this into each
pom.xml every release of
will trigger commit in
component-b and if you don't do that
in an automatic fashion, people will tend to forget it.
So for development environment, we use something like this (
<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
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):
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.
library-c will become to
if we added a feature in a backwards compatible way.
Dependencies with Version Ranges
This version range of
<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>
component-b snapshot will work with any version of
This works, since maven (at least if you run it with
--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:
for everything between
<5.1.0. This will stick the
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)).
pom.xml entry for
library-c looks like this:
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
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.
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.
- 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
MINORupdate will break our app or is intentionally skipped
- fluid dependencies
- version range for
- for components, which we develop inhouse (so we control the versioning strategy)
- version range for
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?
- 2016/01/31: added hint to avoid pushing library snapshots to the remote maven repository
- 2016/01/17: added information about tagging the release