Reputation: 35
So my question concerns the dependencies which are used in a Maven project. A small project with just one pom I could build using the maven-shade-plugin
. So it contains all the dependencies I used with it and also can be build as an executable JAR. But what happens with bigger project with structures like:
+- myProject
+- pom.xml ... <packaging>pom, aggregator (<modules>) of frontend & backend
|
+- frontend
| +- pom.xml ... <parent>myProject, <packaging>pom, aggregator (<modules>) of window & ***
| |
| +- window
| | +- pom.xml ... <parent>frontend
| |
| +- ***
| +- pom.xml ... <parent>frontend
|
+- backend
+- pom.xml ... <parent>myProject, <packaging>pom, aggregator (<modules>) of database & ***
|
+- database
| +- pom.xml ... <parent>backend
|
+- ***
+- pom.xml ... <parent>backend
Where window
and database
are just example projects in frontend
and backend
which could contain many more projects.
When I build something like this, I could also use the shade
approach, but I have issues with that. Wouldn't that mean that when in the parent myProject
are some dependencies set (with dependencyManagement) which get used by many different projects inside frontend
and or backend
for example, that every one of those projects when build to a JAR contains those dependencies respectively(JUnit for instance , and all contain it themselves)? And when some projects depend on each other for example window
depends on database
, that because databse
is a dependecy it gets build into the JAR of window
which then both seperately would contain the their own dependecies(both have JUnit jar) although they could use the same reference instead of having their own? Am I confusing something or would that be a bad idea to build it this way?
Is there a preferred or "better" way to approach the build of something like this?
Upvotes: 0
Views: 736
Reputation: 97349
First this approach is called a multi module build which is a perfect fit for such things.
Wouldn't that mean that when in the parent myProject are some dependencies set (with dependencyManagement) which get used by many different projects inside frontend and or backend for example,
There is a misunderstanding of what dependencyManagement
means. Defining an artifact in dependencyManagement
means that you can define a dependency
without giving the version number in one of your modules (window, database).
The intention of dependencyManagement
is to "manage" the list of used dependencies in a central location (usually the root of your multi module build (myProject
).
So let us begin with an example. You define an artifact like the following in your dependencyManagement:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
Now you would like use this artifact (in other words the classes within the jar file). This will not add a dependency to your project. To none of them.
So now to really use it you have to define a dependency
in one of your pom.xml
file. For example window
that would look like this:
<dependencies>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
</dependency>
</dependencies>
This will do the real trick. This means during the building of your module window
this dependency (the jar file) will be available on the class path and you can compile your classes which use classes of that dependency. You can now add this dependency to several of your modules (keep in mind without defining the version).
After a time of development you want to upgrade the version of this artifact. This can easily being achieved by simply changing the version in the dependencyManagement
location (only one location) and over all modules that version will be used.
So second misunderstanding. A jar which is build by Maven (as well by others) does NOT contain it's dependencies.
A jar which contains dependencies is called a fat-jar
, shaded-jar
or jar-with-dependencies
or executable-jar
(little bit different) which is usually not being created and should never being used as a dependencies to be consumed by others.
And when some projects depend on each other for example window depends on database , that because databse is a dependecy it gets build into the JAR of window which then both seperately would contain the their own
As explained before a jar does not contain dependencies by default (and never should).
If you make a dependency between modules of your multi module build that's ok and the result is technically that the dependencies are found on the classpath during build and/or testing.
So another thing is about testing dependencies as you already mentioned. I use assertj library as an example. The artifact itself will be defined as you might already guessed in the dependencyManagement
as described before. This means we add this artifact to the dependencyManagement.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
So now you would like to use the assertj
parts in one of your modules but only for testing. We enhance the dependency
Part of the module ... because we have to write some tests which looks like this:
<dependencies>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
So the most important thing you will see here is the <scope>test</scope>
. This means the dependency is only "visible" during the unit tests. That means by default for test code which lives in src/test/java
. So in each module you have to use assertj you have to add this dependency (with scope test).
So finally you would like to build an executable jar of your frontend and of your backend.
The usual way is to create a separate module frontend-app
and backend-app
which produces an executable jar file. The first thing you have to define the used modules in your frontend-app
. This would look like this:
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>window</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>...</groupId>
<artifactId>supplemental modules for frontend</artifactId>
</dependency>
...
</dependencies>
It's very important to see that the version given is done by placeholders ${project.version}
which is replaced automatically with the defined version of the project. The usage of ${project.version}
is an indicator that the dependency is build by your own multi module build.
Those dependencies are absolutely necessary to have a correct build order of your modules. By using the dependencies Maven can automatically determine the correct build order.
And of course you have to do the configuration to create the executable jar. This can be done via maven-shade-plugin or maybe maven-assembly-plugin (based on the use case). If this a kind of Spring Boot project you should use the spring-boot-maven-plugin for that purpose.
If you need to write some integration tests in this module you have to add the appropriate dependencies for that as well.
The usual modules will not produce a shaded/fat jar by default. There is usually no reason to do so.
Upvotes: 2