We have several projects that are microservices, every project is independent (running on separate spring boot server, exposing rest services, using separate DB schema...)
We use maven to manage the dependencies.
Is it a good idea to have a parent pom declaring each microservices as modules? And so helping to manage the common dependencies (like the lib servlet-api witch is used in every project, to remove it of all of them and declare it in only the parent pom)
The 'problem' with a multi-module parent pom is that, without complicated profiles, it locks the modules in the same release cycle (assuming you're using the Release Plugin, which you should be).
The way I work with Maven is to have a parent pom that declares:
common dependencies (logging APIs, JUnit, etc).
common plugins.
all dependencies in the dependencyManagement section.
all plugins in the pluginManagement section.
Each module delcares the parent pom as its parent but the parent knows nothing about the modules.
The benefit of this comes from the last to two bullets above, the 'management' sections. Anything contained in a 'management' section needs to be redeclared in a module that wants to use a particular dependency or plugin.
For example the parent might look like this:
<project>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0.00-SNAPSHOT</version>
...
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>2.1</version>
</dependency>
</dependencyManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</project>
And the module might look like this:
<project>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>1.0.00-SNAPSHOT</version>
</parent>
<groupId>com.example</groupId>
<artifactId>module</artifactId>
<version>1.0.00-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
</dependencies>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</project>
The module would:
have dependencies on org.slf4j:slf4j-api:1.7.7:compile, junit:junit:4.11:test and commons-lang:commons-lang:2.6:compile.
has the plugin org.apache.maven.plugins:maven-assembly-plugin:2.4
I would avoid dependencies in the parent pom. It's awkward if one of your (independent) microservices would want some other things. It's weird to have the parent know of each microservice.
You can stick with dependencyManagement though to suggest default versions/scopes if you want. A parent pom is, non the less, very convenient to define plugins, repositories and the like.
Instead, I would group a common set of dependencies into a specific artifact(s), that may be only a single pom with dependencies. Then you can depend on, say "com.example/common-db-dependencies/1.2" to include a standard set of database dependencies, like hibernate, apache derby and JPA specs. (or whatever you're using). A service does not use JPA/SQL could avoid that dependency all together.
Don't entangle yourself though. It's easy to overwork dependency structures if you're trying to cover each case. So, only try to standardize things that really get used by a majority of the services.
I would definitely use a parent project.
I've been working for years with both the structures...Microservices and not, modular or not, Ant, Maven and Gradle..
We need to understand that using a parent pom does not mean talk about microservices not coupled and independent:
they can be still independent and not coupled using parent pom,
they can be still built release and updated in isolation even if you are using a parent pom.
I heard saying "a microservice may need to use different versions for a dependency", well you can, just override the dependency in the specific microservice pom.
We need to focus on "What are here the benefit and what are the cons":
Control and standardization: I can manage the common dependencies (with the dependencies management) in a single point, it makes easier to roll out dependencies changes across all the modules, yes we may need different third parties version, but same time we need to avoid losing control over all the dependencies, so exceptions may be allowed but they needs to be balanced with the "standardization"
Group management: I can still release just a single module, but I can also manage multi modules releases in a easier way, without having to release module by module, but simply the modules that are under development, in this case I still have a single entry point and all the common dependencies can be overviews withing the parent
And much more:
common third parties and platform dependencies management
common third parties and platform standardization
Full control of the dependencies ecosystem withing the whole application (structured in micro services)
common plugins management and standardization
reduce duplication and redundant logic.
Configurations management and standardization
Easier maintenance, change in one place instead of potentially 30 places!!
easier to test and roll out common change.
What about the cons?
I don't see any for the moment, as exceptions can be managed through overriding common behaviour in the specific microservices pom, I can still manage anything in isolation (build in isolation, release in isolation, deploy in isolation..)
There is nothing coupled
Not sure yet what we mean with "it locks the modules in the same release cycle" It does not, unless you are using external SNAPSHOT, I can release a microservice in isolation re-using the same parent version.
for example I can have module 1 declaring Parent 1.0 and be released in isolation without having to run the release process from the parent, I can run it directly on the submodule, but I need to not declare any external SNAPSHOT within the submodule project (you would have same issues with or without parent)
Here there is one issue with dependency and dependency management. Say one of your micro service wants to upgrade to newer version of common for some reason...you cant do that as you have parent. I do understand temptation of reducing duplication of redundant things like plugin configuration. In micro service we need to think more about independence of each service.
Some config like say your repository or release configuration etc can be common.
Most books on microservice architecture recommend autonomy as a principle. Using a parent pom violates that principle.
First of all with a parent pom you can no
longer adopt a polyglot approach and write your microservices in different languages.
You'll also be forced to use the dependencies prescribed by the parent, especially if the enforcer plugin is employed.
The microservices will no longer be independently deployable.
There is also the risk that your work on any one microservice may break others if that work involves altering the parent.
A major drawback of using a parent pom approach with microservices is it will make the release management for microservices a slightly tricky affair. Few related pointers -
The parent pom should not be frequently changed, should be managed as a separate project in a separate repo.
Every change to the parent pom should increment the parent pom version. Once the changes are finalized, the parent pom repo should also be tagged. (treating is as a separate library with independent releases)
Moreover the child pom of all the microservices being touched should ideally be updated to point to the latest parent pom version (affecting the autonomy of microservices to some extent). This may also lead to forceful ask of upgrading the microservice to use newer versions of the libraries, which may not always be a feasible option.
Even if the only change in a microservice is to point to the new parent pom version, it would call for a new (mostly minor) version release of the service.
Suggestions -
You can use the maven enforcer plugin to check for duplicate dependency versions specified between parent and child poms.
The parent pom will not be a good option for extensive dependencies and dependency-management, but can certainly be used for things like repositories, distribution management, and plugin management which shall generally not have clashes between microservices.
Related
I'm working on a large Java codebase that's split into multiple modules, each with a separate pom.xml, all parented by a top-level pom.xml.
I'm currently in the process of bringing in a couple of library dependencies. The transitive set of dependencies is large, and as luck would have it, there are conflicting dependency versions for different modules.
Here's a simplification of my situation:
project/pom.xml
/module-a/pom.xml # references library-a, depends on library-c:v1
/module-b/pom.xml # references library-b, depends on library-c:v2
/module-c/pom.xml # references module-a and module-b
Now the unit tests for module-a will exercise library-a in the presence of library-c:v1, while module-b will exercise library-b in the presence of library-c:v2.
The trouble is that module-a and module-b need to live together on the same classpath when module-c is deployed, but whatever version of library-c is chosen when module-c is packaged, at least one combination of libraries hasn't been unit tested!
I'd like to pin the version of library-c at the parent pom level somehow, rather than repeating myself in every module that transitively depends on library-c; ideally it would be added in such a way indicating that it's a transitive dependency that is allowed to go away should library-a and library-b no longer rely on it.
I'd like a guarantee that there is exactly one version selected for
every transitive dependency across the entire project rooted from this parent pom, and I'd like the build to blow up if this isn't true. I wrote a tool to parse the output of mvn dependency:tree (turning the leaves of the tree into a forest of paths from leaf to root, then finding all different versions of leaf with the dependency path) so I can see the problem, but without explicitly resolving the transitive dependencies for every conflict and bloating out poms with redundant declarations, this doesn't seem fruitful. It's what I'll do if I have no alternative, naturally.
What's the best way to handle this transitive dependency conflict problem with Maven?
How severe is this problem? Quite apart from getting unconvincing test coverage, in practice I see JVM-killing NoSuchMethodError at runtime from the wrong versions getting deployed. I'd prefer to see these at test time at the very least.
Looks like there are two aspects to this:
You need to insist on a single version of a dependency, whether it is declared explicitly or acquired transitively
You can use <dependencyManagement/> here. For example in the top-level pom.xml you can pin the version of library-c:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>your.group.id</groupId>
<artifactId>library-c</artifactId>
<version>2</version>
<dependency>
<dependencies>
<dependencyManagement>
And then in library-a, library-b you would declare the dependency on library-c as follows:
<dependencies>
<dependency>
<groupId>your.group.id</groupId>
<artifactId>library-c</artifactId>
<dependency>
<dependencies>
By declaring this dependency in the parent's dependencyManagement you are insisting on both of the child modules using the version declared in the parent.
You want to protect yourself from unhappy dependency additions occurring in future
You can use the Maven Enforcer plugin here, specifically the dependencyConvergence rule. For example:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M1</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<dependencyConvergence/>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
The enforcer can be configured to either fail or warn if it discovers a non convergent dependency.
Our company creates an ejb in two artifacts. The impl artifact contains the implementations and the client artifact contains all the interfaces. This means that the impl artifact has a compile dependency on the client artifact.
Now at runtime, the client artifact needs the impl artifact - otherwise the container cannot inject the required objects. This means that an ear needs to contain the impl artifacts for all client artifacts.
Does this mean that the client artifact should have a runtime dependency on the impl artifact? Or should these "circular" dependencies be avoided, even if one direction is compile and the other is runtime?
Does this mean that the client artifact should have a runtime dependency on the impl artifact?
No and there is no dependency (or better should not be). Take a look at the import statements in the client artifact's classes and interfaces and you will see that the client artifact does not depend on implementations.
If the client would depend on the implementation it would violate the dependency inversion principle which is part of the SOLID principles.
Or should these "circular" dependencies be avoided, even if one direction is compile and the other is runtime?
In fact at runtime an implementation is needed, but that is a question of component assembly. One might want to replace the implementation some day or for test reasons. So it wouldn't be a good idea to introduce a maven dependency in the client artifact to the implementation only to make component assembly a little bit easier.
Instead you should declare the implementation dependency in the EAR deployment unit, because the EAR is the assembly of the enterprise application.
EDIT
Our developers complain that making sure that every client has a corresponding impl in the ear is tedious manual work. One looks for all client artifacts in the dependency:list, adds all corresponding impl artifacts, calls dependency:list again, adds again all missing impl artifacts etc.
I think they take the JEE development roles description word by word.
A software developer performs the following tasks to deliver an EAR file containing the Java EE application:
Assembles EJB JAR and WAR files created in the previous phases into a Java EE application (EAR) file
Specifies the deployment descriptor for the Java EE application (optional)
Verifies that the contents of the EAR file are well formed and comply with the Java EE specification
Nevertheless the specification also says
The assembler or deployer can edit the deployment descriptor directly or can use tools that correctly add XML tags according to interactive selections.
I would say that the a ear pom is an example of an assembly description using a tool.
JF Meier also mentioned
Some developers write scripts for this process, but then again, after one changes versions of some ejbs, one needs to repeat the process because maybe somewhere deep down in the dependency tree, ejb-clients were erased or added, so additional impls might be necessary.
To me these scripts are the same as the ear pom. Maybe more flexible, but at the price of standards and conventions. The fact that they have to update the scripts with every release makes clear that it would be better if these versions are also updated by maven.
Furthermore... Since the ear pom is just a maven artifact, it can be deployed to a repository as well. This is better then private scripts that noone except the author has access to.
I hope these arguments will help you when discussing the deployment strategy with your colleagues.
You do not need to be concerned with the client's implicit dependency upon the implementation because the server will manage that.
The EJB container creates a proxy through which the implementation is invoked, so there is never a direct reference to it from the client.
If you have the pom for the EJB containing:
<groupId>com.stackoverflow</groupId>
<artifactId>Q43120825-server</artifactId>
<packaging>ejb</packaging>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
</dependency>
<dependency>
<groupId>com.stackoverflow</groupId>
<artifactId>Q43120825-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-ejb-plugin</artifactId>
<configuration>
<ejbVersion>3.2</ejbVersion>
</configuration>
</plugin>
</plugins>
</build>
and the EAR file pom containing:
<dependencies>
<dependency>
<groupId>com.stackoverflow</groupId>
<artifactId>Q43120825-server</artifactId>
<version>1.0-SNAPSHOT</version>
<type>ejb</type>
</dependency>
<dependency>
... other modules
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.10.1</version>
<configuration>
<version>7</version>
<defaultLibBundleDir>lib</defaultLibBundleDir>
<modules>
<ejbModule>
<groupId>com.stackoverflow</groupId>
<artifactId>Q43120825-server</artifactId>
<bundleFileName>Q43120825-server.jar</bundleFileName>
</ejbModule>
... other modules that might have the API jar as a dependency
</modules>
</configuration>
</plugin>
</plugins>
</build>
then this will build a correct EAR file with the API jar in it's lib directory.
I have a java backend project that includes services to import data from a database. While working on new features, I sometimes need to deploy and run the code on my local machine. Since I don't actually want to connect to the production db while running experimental code, I set up a mock datasource class using Mockito.
The Mock datasource works fine and does what I want when running locally. The problem I'm running into is that I don't want to include that class and its associated dependencies when doing a production deployment. I added an <excludes> section to the configuration section of maven-compiler-plugin. I added the Mock specific dependencies to a 'local' profile section. When I actually try to do a compile using maven however, I get compile errors on the mock datasource class that was supposed to be excluded. I'll post the relevant snippets from my .pom file below. I've tried putting the excludes statement in a specific profile and in the 'default' as shown below. Any help with the would be greatly appreciated.
<profiles>
<profile>
<id>local</id>
<properties>
<config>config.dev.properties</config>
</properties>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</profile>
...
</profiles>
<build>
<finalName>order</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<excludes>
<exclude>com/tura/order/guice/mock/**</exclude>
</excludes>
<compilerId>groovy-eclipse-compiler</compilerId>
</configuration>
</plugin>
</plugins>
</build>
As a simpler alternative, you could configure an alternative version of your app to be run from the src/main/test source directory instead of the normal directory.
You would also remove the profile and only declare the mockito dependency with the scope test (adding test).
This way, you could launch your app on your computer but this code and mockito would not appear in the final build.
I think it would be a lot simpler IF you can easily configure your app to be run from the test but I don't see why it would be otherwise. Usually, avoiding dealing with Maven profiles is considered good practise if there are alternative ways.
EDIT: following your question...
So first, make sure mockito is defined with the "test" scope in you pom. Like this:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
Then your code should not compile anymore as it is under src/main/java and needs Mockito.
Transfer your code into src/test/java/ where it will be able to benefit from the test dependencies (thus Mockito).
You have to know that test dependencies and testing code (in src/test/) will not be part of the final jar. So this is what you want.
And I forgot to say that the code in src/test/ may be whatever you like: unit test tests, applications with a main(..) methods.
The only tricky part may be to make your code work from the tests. But test code "sees" the main code (the opposite is not true) so you will have to call main code and pass it your mock, where your mock is instantiated in the test code.
Hope it helps
Altough I like Francois Marot's answer, this other choice is cleaner (tough more complicated): Split your current project into several ones:
One project containing the core code of the application: It must publish pure APIs with no dependencies.
Another project, which must have a dependency on the core, and include the mockito infraestructure as well as your "local" environment facade.
The last one, if necessary, must have a dependency on the core, and add the proper infraestructure and classes for the "production" environment (depending on the complexity, maybe you could decide to include this one into the core itself).
In this way, you will package your code into 100% reusable libraries, and make one distribution for each required target environment, so that no one of them will be polluted with code aimed for the other target environments.
And the POMs will become simplier and won't need profiles.
I have a project whose dependency tree is huge i.e. it packs in modules from several teams.
Now there are some commonly used dependencies which are common across several modules.
A simplified example can be:
TopModule.jar
ChildModule.jar
CommonModule-v1.jar
CommonModule-v2.jar
When I build my project, I specify the latest version of common dependencies, but its very hard to ask the same from every other team.
So, frequently, the TopModule is built using different versions of CommonModule (v1 and v2 in the above example).
My question is:
If the final jar file contains both CommonModule-v1.jar and CommonModule-v2.jar, how does it affect the runtime?
Can the runtime erroneously load versions v2 where v1 is required and vice versa?
Maven will only use one version of each artifact in the end -- it doesn't do any fancy classloader isolation tricks. You can see which version it'll use with mvn dependency:resolve.
If you need to use specific versions within dependencies, you can use the shade plugin. It'll do renaming trickery so that dependencies get their own versions of libraries.
To fight with this problem globally use this DependencyConvergence Rule
This rule requires that dependency version numbers converge. If a
project has two dependencies, A and B, both depending on the same
artifact, C, this rule will fail the build if A depends on a different
version of C then the version of C depended on by B.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<DependencyConvergence/>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
After this all teams work together with consistent versions of dependencies.
This depends on the way the modules are named in maven. Usually, maven tries to resolve the conflicting libs and takes the highest version into the tree. But if the libraries are different artifacts in terms of artifactId, then maven will not see that they are from the same breed and thus will not resolve the ambiguity.
Usually you resolve this by a common parent.pom, where you define the versions of commonly used libraries throughout the project. If you have no control over the other projects (not part of your build, only dependencies), you may be lucky to have your final project working fine. If the library breaks compatibility in the newer version, you will not be able to use it.
So, does your final project contain both versions of the library or not, did you check it? The dependency tree may show both versions, but if maven will use only the latest version of a dependency in the hierarchy.
Classloader will load the first JAR which appears on your classpath. In more details - it will search for the first class on your class path, so in each case all these searches would fall into i.e. CommonModule-v2.jar. So the answer is yes - it can erroneously load versions v2 where v1 if it appears earlier on your classpath.
If your pom.xml is only an aggregator of already packaged modules then this apply. If it is not the case and your project actually compile and packages all of those modules as a submodules then maven will choose one. If it compiles every project on its own then it will be packaged using that dependency. But if all of them end up in the same class loader then it won't work fine.
At runtime it can cause errors, think of method not found and the like. Your byte code classes were compiled and linked with the correct dependencies but since the class loader finds two candidates it just load one at runtime.
What you can do is set a parent pom defining a <dependencyManagement> and then ask all teams to use it as a parent and don't declare <version> but inherit it from the parent pom.xml.
References http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Management
On top of what #yshavit said, ideally you'd exclude the earlier version of the CommonModule so that only v2 is in the classpath. This is only possible if the CommonModule v2 api is backwards compatible with CommonModule v1.
Here's an example of how you exclude:
<dependency>
<groupId>ChildModuleGroupid</groupId>
<artifactId>ChildModuleArtifactid</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>CommonModuleGroupId</groupId>
<artifactId>CommonModuleArtifactId</artifactId>
</exclusion>
</exclusions>
</dependency>
You'd put that in the TopModule pom.xml.
We're using maven 2.1.0. I have multiple modules that are completely separate, but still have many common dependencies. Like log4J, but some modules don't need it. I am wondering if it is a good idea to declare all common dependencies in one parent file in the <dependencyManagement> section or is there a better way to deal with this?
A follow up question about <dependencyManagement>. If I declare Log4J in the <dependencyManagement> section of the parent and a sub project does not use it, will it be included anyway?
If you have a parent project, you can declare all dependencies and their versions in the dependencyManagement section of the parent pom. This doesn't mean that all projects will use all those dependencies, it means that if a project does declare the dependency, it will inherit the configuration, so it only need declare the groupId and artifactId of the dependency. You can even declare your child projects in the parent's dependencyManagement without introducing a cycle.
Note you can also do similar with plugins by declaring them in the pluginManagement section. This means any child declaring the plugin will inherit the configuration.
For example, if you have 4 projects, parent, core, ui and utils, you could declare all the external dependences and the internal project versions in the parent. The child projects then inherit that configuration for any dependencies they declare. If all modules are to have the same version, these can be even be declared as properties in the parent.
An example parent is as follows:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>name.seller.rich</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>name.seller.rich</groupId>
<artifactId>ui</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>name.seller.rich</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>name.seller.rich</groupId>
<artifactId>utils</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>utils</module>
<module>core</module>
<module>ui</module>
</modules>
</project>
And the utils, core, and ui projects inherit all the relevant versions.
utils:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>name.seller.rich</groupId>
<artifactId>utils</artifactId>
<!--note version not declared as it is inherited-->
<parent>
<artifactId>parent</artifactId>
<groupId>name.seller.rich</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
</project>
core:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>name.seller.rich</groupId>
<artifactId>core</artifactId>
<parent>
<artifactId>parent</artifactId>
<groupId>name.seller.rich</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>name.seller.rich</groupId>
<artifactId>utils</artifactId>
</dependency>
</dependencies>
ui:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>name.seller.rich</groupId>
<artifactId>ui</artifactId>
<parent>
<artifactId>parent</artifactId>
<groupId>name.seller.rich</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>name.seller.rich</groupId>
<artifactId>core</artifactId>
</dependency>
</dependencies>
</project>
I wrote up a list of best practices. Here are the most important ones.
Always use the maven-enforcer-plugin
Enforce dependency convergence
Otherwise it's possible that you depend on two different jars which both depend on log4j. Which one gets used at compile time depends on a set of rules that you shouldn't have to remember. They can both (!) get exported as transitive dependencies.
Require plugin versions (for all plugins, even the built in ones)
Define them in pluginManagement in the parent pom to define versions
Otherwise a new version of maven-surefire-plugin could break your build
Use dependencyManagement in the parent pom to use versions consistently across all modules
Periodically run mvn dependency:analyze
It's possible that you're getting a dependency transitively that you directly depend on at compile time. If so, it's important to add it to your pom with the version you require. This plays nicely with the enforcer plugin.
It's possible that you're declaring extra dependencies that you don't use. This doesn't work properly 100% of the time, especially with libraries that are designed to have optional pieces (i.e. slf4j-api gets detected properly, but slf4j-log4j12 fails).
Each module should have its own POM and where it declares its own dependencies. This not only tracks external dependencies, but also internal ones.
When you use Maven to build a project it will sort the whole lot out. So if many modules (perhaps all) depend on log4j, then it will only be included once. There are some problems if your modules depend on different versions of log4j but this approach usually works fine.
It is also useful (if there are more than 1-2 developers working together) to set up an internal repository (like Artifactory) and use that internally. It makes it much easier to deal with libraries that are not in the public repos (just add it to your internal repo!) and you can also use build tools to push builds of your own code there so other can use the modules without checking out the code (useful in larger projects)
A follow up question about . If I declare Log4J in the section of the parent and a sub project does not use it, will it be included anyway?
No. Dependency management only sets the default version and possibly scope (I've seen this both appear to be inherited and appear to not be inherited so you will need to look this one up on your own). To include the dependency in a child module, you need to declare it as a dependency of the module and omit the version element. You can override the default in a child module simply be including the version number in the dependency element of the child module's POM.
I have multiple modules that are completely separate, but still have many common dependancies.
In this case, yes and no.
For modules that are built, versioned, and deployed together as a unified project, for instance the modules that compose a single Web application, most definitely yes. You want to relieve yourself of the headache of changing the version in more than one POM when you decide to move to a new version of a dependency. It can also save you work when you need to exclude certain transitive dependencies. If you declare the dependency with its excludes in the section you don't have to maintain the exclusions in multiple POMs.
For modules that are not directly related but are built within a single team within the company you may want to consider declaring default versions for common libraries like testing utilities, logging utilities, etc. in order to keep the team working with the standard versions of the tools that you have defined as part of your best practices. Remember you can always increase the version of your super POM when you standardize on a new set of common libraries. Where you draw the line between standardized library and tools and project specific libraries and tools is up to you but it should be easy for your team to find.
We use a single common parent with a dependencyManagement block for all our projects. This is starting to break down as we move more projects into maven - if a project needs a different version then we have to either declare it as a dependency for all children or explicitly define the version for each pertinent child.
We're trying out a model where we split the dependencyManagement out from our common parent and then import our corporate dependencyManagement pom into the top level project pom. This allows us to selectively define project defaults that override the corporate defaults.
Here is the original scenario:
A defines version 1.0 of foo.jar as the corporate default
B child of A
C1, C2, C3 children of B
D1, D2, D3 children of C1, C2, C3 respectively
If D1 and D2 require version 1.1 of foo.jar, then our choice used to be:
Declare foo.jar version 1.1 as a dependency in B, making it appear that C1, C2, C3 and D3 also depended upon version 1.1
Declare foo.jar version 1.1 as a dependency in D1 and D2, moving the dependency declaration into multiple places deeper in our project hierarchy.
Here is what we're trying out:
A defines version 1.0 of foo.jar as the corporate default
B dependencyManagement: imports A, declares a default of foo.jar version 1.1
C1, C2, C3 children of B
D1, D2, D3 children of C1, C2, C3 respectively
Now D1 and D2 just declare a dependency upon foo.jar and pick up version 1.1 from the dependencyManagement block of B.
In a multi-module project I place any common dependencies in the element of the parent pom.xml. I'm not sure if this would be best practice if the modules were not related to the same project though.