Controlling the Maven release cascade

In my previous post, I showed one of the pitfalls of maven's transitive dependency mechanism.

Luckily, there is a good workaround to this that will allow your team to stay focussed on development without getting bogged down too much into the release-cascade trap.

The thing to do is to create another level of indirection between your framework and the clients using it, this can be accomplished by providing a POM dependency.

For our framework it would look something like this:


<project>
<modelVersion>4.0.0</modelVersion>
<groupId>my.framework</groupId>
<artifactId>client-components</artifactId>
<version>2.0.1</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>my.framework</groupId>
<artifactId>main</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>my.framework</groupId>
<artifactId>A</artifactId>
<version>0.6</version>
</dependency>
<dependency>
<groupId>my.framework</groupId>
<artifactId>B</artifactId>
<version>0.8</version>
</dependency>
<dependency>
<groupId>my.framework</groupId>
<artifactId>C</artifactId>
<version>0.2</version>
</dependency>
<dependency>
<groupId>my.framework</groupId>
<artifactId>X</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>my.framework</groupId>
<artifactId>Y</artifactId>
<version>0.5</version>
</dependency>
</dependencies>
</project>
Notice how this POM dependency is nothing more than a flattened dependency tree. We've pulled all our dependencies into one POM and we control their versions from there. This works pretty well, and all our clients have to do is to declare it like this:
<groupId>my.framework</groupId>
<artifactId>client-components</artifactId>
<version>2.0.1</version>
<type/>pom<type>
Using this, we can avoid the release cascade for module Y, our work is effectively reduced to:
  1. release module Y
  2. change version of Y in client-components POM
  3. release client-components POM
That's IT. Swift and fast like it should be.

The POM dependency can even be used from assemblies and the like. It behaves like a normal dependency in all aspects. Additionaly, it is a great way of quickly eliminating rogue dependencies: just exclude them in there and worry about tracking it down later.

That's it for now, I promise my next post will not take 4 months this time :-)

The Maven release cascade

Imagine you've got the following component structure in your framework:

  • main component (the included by your clients), let's call it MAIN
  • MAIN depends on functional components A,B,C
  • There are 2 helper components, X and Y, used by most modules
A rough graph of above situation could look like this:


Now, the great thing about maven's transitive dependency resolution is that when a client wants to include your framework it can do so by just declaring one artifact in their POM, namely that of the MAIN component:

<dependency>
<groupId>my.framework</groupId>
<artifactId>main</artifactId>
<version>1.0</version>
</dependency>

Maven will then automatically add modules A,B,C,X and Y to the client's project, neat !! The root component here effectively serves as a dependency abstraction, clients do not need to know anything else when they want to use my framework, this is a Good Thing really.

Imagine now that I (the framework maintainer) make a small one-line fix to module Y and I'ld like to do a new release. There were minimal code changes, and I only changed one module, so I expect the release process to go fast and swift and in a few minutes my clients will be able to use the 1.0.1 release:

<dependency>
<groupId>my.framework</groupId>
<artifactId>main</artifactId>
<version>1.0.1</version>
</dependency>

Right ?

Wrong.

Here is what maven typically will force you to do:
  1. Release module Y
  2. Update the dependency of Y in module X, release module X
  3. Update the dependency of X in module A, release module A
  4. Update the dependency of X in module B, release module B
  5. Update the dependency of X in module C, release module C
  6. Update the dependency of A B C in MAIN and (finally) release MAIN
In other words, I would need to coordinate a new release for 5 modules !! The effort is just not justifiable for that oneliner fix, so I decide to wait until I've got something more substantial to release. This is a problem.

If the effort for a release turn-around becomes far too great then this can impact your project in different ways:
  • no more release-early release-often
  • each release will contain more code changes
  • regressions become much harder to detect
  • clients get frustrated because of the slow release cycle
  • developers get frustrated by their inability to "get something out of the door quickly"
These symptoms are to be taken very seriously. They end up diverting so much energy away from the team's core business, namely to develop all those features that will make the framework dominate the world !

(This post is getting too long already, so I'll offer a solution to this in a next post.)