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:
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:
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.)
One solution is the one you already mentioned, to create another project. Another one would be to just move some classes from B to C or vice versa when this helps. Or sometimes it is correct to merge project B and C to one project if there is no need to have two of them.
Avoiding circular dependencies by refactoring The NestJS documentation advises that circular dependencies be avoided where possible. Circular dependencies create tight couplings between the classes or modules involved, which means both classes or modules have to be recompiled every time either of them is changed.
A circular dependency occurs when two classes depend on each other. For example, class A needs class B, and class B also needs class A. Circular dependencies can arise in Nest between modules and between providers. While circular dependencies should be avoided where possible, you can't always do so.
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
A major source of confusion in Maven is the fact that parent
pom can actually encompasses 2 distinct types of relationships:
<parent>
tag<modules>
tagmvn clean install
) are passed along to submodulesThis 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 you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With