Maven by Example - 8.3. Optimizing Dependencies |
|
If you look through the various POMs you notice a lot of duplication that you can remove by moving parts into a parent POM. Just as in your project’s source code, any time you have duplication in your POMs, you open the door a bit for trouble down the road. Duplicated dependency declarations make it difficult to ensure consistent versions across a large project. When you only have two or three modules, this might not be a primary issue, but when your organization is using a large, multimodule Maven build to manage hundreds of components across multiple departments, one single mismatch between dependencies can cause chaos and confusion. A simple version mismatch in a project’s dependency on a bytecode manipulation package called ASM three levels deep in the project hierarchy could throw a wrench into a web application maintained by a completely different group of developers who depend on that particular module. Unit tests could pass because they are being run with one version of a dependency, but they could fail disastrously in production where the bundle (WAR, in this case) was packaged up with a different version. If you have tens of projects using something like Hibernate Annotations, each repeating and duplicating the dependencies and exclusions, the mean time between someone screwing up a build is going to be very short. As your Maven projects become more complex, your dependency lists are going to grow, and you are going to want to consolidate versions and dependency declarations in parent POMs. The duplication of the sibling module versions can introduce a
particularly nasty problem that is not directly caused by Maven and is
learned only after you’ve been bitten by this bug a few times. If you
use the Maven Release plugin to perform your releases, all these
sibling dependency versions will be updated automatically for you, so
maintaining them is not the concern. If Problems occur when developers merge changes to the POM and interfere
with a release that is in progress. Often a developer merges and
occasionally mishandles the conflict on the sibling dependency,
inadvertently reverting that version to a previous release. Since the
consecutive versions of the dependency are often compatible, it does
not show up when the developer builds, and won’t show up in any
continuous integration build system as a failed build. Imagine a very
complex build where the trunk is full of components at Someone, let’s call them Mr. Inadvertent, had a merge conflict in
component A, and mistakenly pegged component A’s dependency on
component B to Fortunately, dependency duplication and sibling dependency mismatch
are easily preventable if you make some small changes. The first thing
we’re going to do is find all the dependencies used in more than one
project and move them up to the parent POM’s dependencyManagement
section. We’ll leave out the sibling dependencies for now. The
<project> ... <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.5</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>3.3.0.ga</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement> ... </project> Once these are moved up, we need to remove the versions for these
dependencies from each of the POMs; otherwise, they will override the
dependencyManagement defined in the parent project. Let’s look at only
<project> ... <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </dependency> </dependencies> ... </project> The next thing we should do is fix the replication of the
<project> ... <properties> <hibernate.annotations.version>3.3.0.ga </hibernate.annotations.version> </properties> <dependencyManagement> ... <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-commons-annotations</artifactId> <version>${hibernate.annotations.version}</version> </dependency> ... </dependencyManagement> ... </project> The last issue we have to resolve is with the sibling dependencies and
define the versions of sibling projects in the top-level parent
project. This is certainly a valid approach, but we can also solve the
version problem just by using two built-in
properties — <project> ... <dependencies> ... <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-weather</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>simple-persist</artifactId> <version>${project.version}</version> </dependency> ... </dependencies> ... </project> Here’s a summary of the two optimizations we completed that reduce duplication of dependencies:
|