Question

We have a modular project with about 10 artifacts:

parent
 +- artifact1
 +- artifact2
 +- artifact3
 +- ...
 +- artifact10

Furthermore, some of the artifacts have dependencies among each other:

artifact1
 +-> artifact2
 +-> artifact3
 +-> ...
 +-> artifact10

artifact2
 +-> artifact3

artifact4
 +-> artifact3

artifact4
 +-> artifact5

artifact5
 +-> artifact6

Our current setup looks like this:

  • parent is an artifact containing the parent POM.
  • This parent POM defines all necessary dependencies (like Spring, JPA, ...) in .
  • All of our artifacts are defined in , too.
  • Our artifacts reference the parent artifact as - stating the obvious - parent.
  • Only the parent POM defines versions. All other POMs don't.

We use a versioning scheme with three numbers:

<major version>.<minor version>.<patch level>

For example:

0.1.0-SNAPSHOT (a young artifact in development)
0.1.0 (the same artifact once it has been released)
0.1.1 (the same artifact after a hotfix)

The problem:

Once we change the version of an artifact (e.g.: 0.1.0 => 0.1.1), our parent artifact version (12.7.3) needs to be updated because it references the old artifact version (0.1.0). Since we change this reference in the parent POM (0.1.0 => 0.1.1), we need to increase the parent POM's version, too (12.7.3 => 12.7.4). Now, our artifact still references the previous parent version (12.7.3), i.e. we need to update it again... That's circular.

What is the best way to resolve such circular parent-child relationships? We could remove our own dependencies from the parent POM and define their versions in all of the other artifact's POMs but this implies that we would need to update all artifacts once a dependency changed.

EDIT

A simplified directory structure that contains our artifacts:

.
├── [api:0.14.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java ...
│       │   └── webapp ...
│       └── test
├── [dao:1.21.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [parent:0.11.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [pdf-exporter:0.2.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [docx-exporter:0.3.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
├── [exporter-commons:0.9.0-SNAPSHOT]
│   ├── pom.xml
│   └── src
│       ├── main ...
│       └── test ...
└── [security:0.6.0-SNAPSHOT]
    ├── pom.xml
    └── src
        ├── main ...
        └── test ...

The artifact directories (in square brackets; along with the artifact version) are independent from each other, i.e. they are only in a common root directory (".") for convenience. Every artifact has its own git repository. "api" is the artifact that gets deployed on an application server. All artifacts reference "parent" like this (during development):

<parent>
    <groupId>com.acme</groupId>
    <artifactId>parent</artifactId>
    <version>0.11.0-SNAPSHOT</version>
</parent>

<artifactId>api</artifactId>
<version>0.14.0-SNAPSHOT</version>

Scenario:

  • exporter-commons gets an update: 0.9.0-SNAPSHOT => 0.9.1-SNAPSHOT.
  • docx-exporter and pdf-exporter reference exporter-commons without version, i.e. no change is necessary.
  • parent needs to be updated to reflect the update of exporter-commons: 0.11.0-SNAPSHOT => 0.12.0-SNAPSHOT.

Problem: api:0.14.0-SNAPSHOT references parent:0.11.0-SNAPSHOT. api:0.14.0-SNAPSHOT is then updated to reference parent:0.12.0-SNAPSHOT. api:0.14.0-SNAPSHOT becomes api:0.15.0-SNAPSHOT. But the pom.xml in parent:0.12.0-SNAPSHOT references api:0.14.0-SNAPSHOT. => Vicious circle.

(Note: The artifact names are made up for simplicity.)

Was it helpful?

Solution

Suggestion

For simplify dependency configuration use versions ranges.

For example artifact A needs artifact B with version 0.1.0. Configure dependency as range <version>[0.1.0, 0.2.0)</version>.

This mean that A requires B with version greater or equal than 0.1.0 and less than 0.2.0 (so all hotfixes are good for this artifact).

This helps, because when hotfix is released, there is no need to change artifact A dependencies. Just rebuild parent project and hotfixed B will be attached to project A

This technique requires releasing parent project when hotfix is released By parent project I mean somethink like WAR with libraries or EAR, or Distribution Archive with all artifacts inside.

More: 3.4.3. Dependency Version Ranges

OTHER TIPS

A major source of confusion in Maven is the fact that parent pom can actually encompasses 2 distinct types of relationships:

  • parent-child relationship:
    • declared once per child inside its <parent> tag
    • inherit plugins, dependencies, properties, versions, etc declared inside pom.xml
  • aggregator-submodule relationship:
    • declared once at top-level pom via <modules> tag
    • goals passed in via cmd line (eg mvn clean install) are passed along to submodules

This distinction is irrelevant if all modules remain on the same version (ie always perform releases from top-level). But as soon as the versions start to fragment (ie release one submodule but not the other) then it becomes necessary to create 2 separate poms for each task.

project/
  parent/
    parent_pom.xml      # declare dependency versions as ranges [0.1.0, 0.2.0)
  children/
    aggregator_pom.xml  # <modules> section lists api/dao/etc
    api/
      pom.xml           # declare parent_pom as parent
    dao/
      pom.xml           # declare parent_pom as parent

Why is this complex structure needed?

Why not just go with MariuszS suggestion of using ranges at top level parent?

Imagine one of the basic components, say the api, is extremely stable. You don't want to rebuild or re-release it if it's avoidable.

Meanwhile, let's say 2 other components that depend on each other like pdf-exporter and docs which you release and/or branch quite often so that you frequently change version ranges: 0.1.x -> 0.2.x -> 0.3.x etc.

Then you would would be forced to modify and release your parent pom to reflect the relationship between pdf-exporter and docs quite often, but you wouldn't necessarily want to release api since it doesn't care about these changes. Hence the need to put parent to the side and ensure releasing it doesn't trigger an unnecessary re-release of api submodule.

If there is a common root module (the “parent”) that all the other modules (the “children”) depend on, none of the children should be depended upon by the parent.

This is because Maven works in quite a simple way: to build a module, you first build all of the modules on which it depends (using a transitive closure model) that are not already built. If you've got a circular dependency, you end up in the situation where in order to build A, you've got to first have A built. That's crazy and obviously won't work in a useful way.

However, you can also have a parent have its children be sub-modules. That's a different, non-inheritable relationship, and it causes the sub-modules to be built after the super-module. That works. It's also entirely reasonable to make the sub-modules of a super-module have no other relationship on the super-module than that simple containment.

In short, whatever way you're arranging the modules on disk and in your repository, do not introduce circular dependencies. (I've seen it argued that circular dependencies should logically turn a group of modules into a single module due to that being the only way to sanely define the operation of the proper-containment operator. I'm not sure I quite agree with that, but it is not entirely wrong…)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top