Maven can be used to manage everything from simple, single-project
systems to builds that involve hundreds of inter-related
submodules. Part of the learning process with Maven isn’t just
figuring out the syntax for configuring Maven, it is learning the
"Maven Way"—the current set of best practices for organizing and
building projects using Maven. This section attempts to distill some
of this knowledge to help you adopt best practices from the start
without having to wade through years of discussions on the Maven
mailing lists.
3.6.1. Grouping Dependencies
If you have a set of dependencies which are logically grouped
together. You can create a project with pom packaging that groups
dependencies together. For example, let’s assume that your application
uses Hibernate, a popular Object-Relational mapping framework. Every
project which uses Hibernate might also have a dependency on the
Spring Framework and a MySQL JDBC driver. Instead of having to include
these dependencies in every project that uses Hibernate, Spring, and
MySQL you could create a special POM that does nothing more than
declare a set of common dependencies. You could create a project
called persistence-deps (short for Persistence Dependencies), and
have every project that needs to do persistence depend on this
convenience project:
Consolidating Dependencies in a Single POM Project.
<project>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>persistence-deps</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>${hibernateVersion}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>${hibernateAnnotationsVersion}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-hibernate3</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysqlVersion}</version>
</dependency>
</dependencies>
<properties>
<mysqlVersion>(5.1,)</mysqlVersion>
<springVersion>(2.0.6,)</springVersion>
<hibernateVersion>3.2.5.ga</hibernateVersion>
<hibernateAnnotationsVersion>3.3.0.ga</hibernateAnnotationsVersion>
</properties>
</project>
If you create this project in a directory named persistence-deps ,
all you need to do is create this pom.xml and run mvn
install . Since the packaging type is pom , this POM is installed in
your local repository. You can now add this project as a dependency
and all of its dependencies will be added as transitive dependencies
to your project. When you declare a dependency on this
persistence-deps project, don’t forget to specify the dependency type
as pom.
Declaring a Dependency on a POM.
<project>
<description>This is a project requiring JDBC</description>
...
<dependencies>
...
<dependency>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>persistence-deps</artifactId>
<version>1.0</version>
<type>pom</type>
</dependency>
</dependencies>
</project>
If you later decide to switch to a different JDBC driver (for example,
JTDS), just replace the dependencies in the persistence-deps project
to use net.sourceforge.jtds:jtds instead of
mysql:mysql-java-connector and update the version number. All
projects depending on persistence-deps will use JTDS if they decide
to update to the newer version. Consolidating related dependencies is
a good way to cut down on the length of pom.xml files that start
having to depend on a large number of dependencies. If you need to
share a large number of dependencies between projects, you could also
just establish parent-child relationships between projects and
refactor all common dependencies to the parent project, but the
disadvantage of the parent-child approach is that a project can have
only one parent. Sometimes it makes more sense to group similar
dependencies together and reference a pom dependency. This way, your
project can reference as many of these consolidated dependency POMs as
it needs.
Note
Maven uses the depth of a dependency in the tree when resolving
conflicts using a nearest-wins approach. Using the dependency grouping
technique above pushes those dependencies one level down in the
tree. Keep this in mind when choosing between grouping in a pom or
using dependencyManagement in a parent POM
3.6.2. Multi-module vs. Inheritance
There is a difference between inheriting from a parent project and
being managed by a multimodule project. A parent project is one that
passes its values to its children. A multimodule project simply
manages a group of other subprojects or modules. The multimodule
relationship is defined from the topmost level downwards. When setting
up a multimodule project, you are simply telling a project that its
build should include the specified modules. Multimodule builds are to
be used to group modules together in a single build. The parent-child
relationship is defined from the leaf node upwards. The parent-child
relationship deals more with the definition of a particular
project. When you associate a child with its parent, you are telling
Maven that a project’s POM is derived from another.
To illustrate the decision process that goes into choosing a design
that uses inheritance vs. multi-module or both approaches consider the
following two examples: the Maven project used to generate this book
and a hypothetical project that contains a number of logically grouped
modules.
First, let’s take a look at the maven-book project. The inheritance
and multi-module relationships are shown in Figure 3.4, “maven-book Multi-module vs. Inheritance”.
When we build this Maven book you are reading, we run mvn package in
a multi-module project named maven-book . This multi-module project
includes two submodules: book-examples and book-chapters . Neither
of these projects share the same parent, they are related only in that
they are modules in the maven-book project. book-examples builds
the ZIP and TGZ archives you downloaded to get this book’s
example. When we run the book-examples build from book-examples/
directory with mvn package , it has no knowledge that it is a part of
the larger maven-book project. book-examples doesn’t really care
about maven-book , all it knows in life is that its parent is the
top-most sonatype POM and that it creates an archive of examples. In
this case, the maven-book project exists only as a convenience and
as an aggregator of modules.
Each of the three projects: maven-book , book-examples , and
book-chapters all list a shared "corporate" parent —
sonatype . This is a common practice in organizations which have
adopted Maven, instead of having every project extend the Super POM by
default, some organizations define a top-level corporate POM that
serves as the default parent when a project doesn’t have any good
reason to depend on another. In this book example, there is no
compelling reason to have book-examples and book-chapters share
the same parent POM, they are entirely different projects which have a
different set of dependencies, a different build configuration, and
use drastically different plugins to create the content you are now
reading. The sonatype POM gives the organization a chance to
customize the default behavior of Maven and supply some
organization-specific information to configure deployment settings and
build profiles.
Multi-module Enterprise Project
Let’s take a look at an example that provides a more accurate picture
of a real-world project where inheritance and multi-module
relationships exist side by side. Figure 3.5, “Enterprise Multi-module vs. Inheritance” shows a
collection of projects that resemble a typical set of projects in an
enterprise application. There is a top-level POM for the corporation
with an artifactId of sonatype . There is a multi-module project
named big-system which references sub-modules server-side and
client-side .
What’s going on here? Let’s try to deconstruct this confusing set of
arrows. First, let’s take a look at big-system . The big-system
might be the project that you would run mvn package on to build and
test the entire system. big-system references submodules
client-side and server-side . Each of these projects effectively
rolls up all of the code that runs on either the server or on the
client. Let’s focus on the server-side project. Under the
server-side project we have a project called server-lib and a
multi-module project named web-apps . Under web-apps we have two
Java web applications: client-web and admin-web .
Let’s start with the parent/child relationships from client-web and
admin-web to web-apps . Since both of the web applications are
implemented in the same web application framework (let’s say Wicket),
both projects would share the same set of core dependencies. The
dependencies on the Servlet API, the JSP API, and Wicket would all be
captured in the web-apps project. Both client-web and admin-web
also need to depend on server-lib , this dependency would be defined
as a dependency between web-apps and server-lib . Because
client-web and admin-web share so much configuration by inheriting
from web-apps , both client-web and admin-web will have very
small POMs containing little more than identifiers, a parent
declaration, and a final build name.
Next we focus on the parent/child relationship from web-apps and
server-lib to server-side . In this case, let’s just assume that
there is a separate working group of developers which work on the
server-side code and another group of developers that work on the
client-side code. The list of developers would be configured in the
server-side POM and inherited by all of the child projects
underneath it: web-apps , server-lib , client-web , and
admin-web . We could also imagine that the server-side project
might have different build and deployment settings which are unique to
the development for the server side. The server-side project might
define a build profile that only makes sense for all of the
server-side projects. This build profile might contain the database
host and credentials, or the server-side project’s POM might
configure a specific version of the Maven Jetty plugin which should be
universal across all projects that inherit the server-side POM.
In this example, the main reason to use parent/child relationships is
shared dependencies and common configuration for a group of projects
which are logically related. All of the projects below big-system
are related to one another as submodules, but not all submodules are
configured to point back to parent project that included it as a
submodule. Everything is a submodule for reasons of convenience, to
build the entire system just go to the big-system project directory
and run mvn package . Look more closely at the figure and you’ll see
that there is no parent/child relationship between server-side and
big-system . Why is this? POM inheritance is very powerful, but it
can be overused. When it makes sense to share dependencies and build
configuration, a parent/child relationship should be used. When it
doesn’t make sense is when there are distinct differences between two
projects. Take, for example, the server-side and client-side
projects. It is possible to create a system where client-side and
server-side inherited a common POM from big-system , but as soon as
a significant divergence between the two child projects develops, you
then have to figure out creative ways to factor out common build
configuration to big-system without affecting all of the
children. Even though client-side and server-side might both
depend on Log4J, they also might have distinct plugin configurations.
There’s a certain point defined more by style and experience where you
decide that minimal duplication of configuration is a small price to
pay for allowing projects like client-side and server-side to
remain completely independent. Designing a huge set of thirty plus
projects which all inherit five levels of POM configuration isn’t
always the best idea. In such a setup, you might not have to duplicate
your Log4J dependency more than once, but you’ll also end up having to
wade through five levels of POM just figure out how Maven calculated
your effective POM. All of this complexity to avoid duplicating five
lines of dependency declaration. In Maven, there is a "Maven Way", but
there are also many ways to accomplish the same thing. It all boils
down to preference and style. For the most part, you won’t go wrong if
all of your submodules turn out to define back-references to the same
project as a parent, but your use of Maven may evolve over time.
|