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.
Related
I have the following scenario, simplified:
projectX ---> projectA ---> projectB
Where ---> means "depends on".
ProjectB is really simple. It doesn't declare any dependenci. In fact, the only relevant part is this:
<packaging>jar</packaging>
In pom.xml of projectA I have declared the dependency to projectB:
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.mycompany</groupId>
<artifactId>projectB</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
And in pom.xml of projectX I have:
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.mycompany</groupId>
<artifactId>projectA</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
The problem is that projectX needs to use components (classes and such) that are defined in projectB. If I change the scope in projectA to use compile for projectB, everything will work, but then projectB will be included when generating the war in projectX, and I need to have this library out of the generated war because I'm providing projectB in other part of the project.
In the real scenario, I have several dependencies like projectB that affect projectA, so to reduce the size of the generated war, I would like to set them as provided, but then projectX cannot use the components defined in any of those libraries. Example of components: Spring, Hibernate, etc.
Question: Is there a way to achieve this in a clean way without re-declaring dependencies in lots of places?
The problem is that projectX needs to use components (classes and such) that are defined in projectB.
Then ProjetB should be a dependency of ProjectX indeed, in provided scope, again. Re-declaring the dependency would change how Maven would handle it as transitive dependency (that is, you can override its behavior by saying: I want this dependency in my project with this scope).
but then projectB will be included when generating the war in projectX, and I need to have this library out of the generated war
This will not happen by re-declaring it in provided scope. Alternatively, you could change the scope of ProjectB in ProjectA to compile and then configure the maven-war-plugin to exclude them, using inclusions/exclusions.
Before doing so though, you should double-check why semantically (or requirements-wise) ProjectB was set as provided in ProjectA and what would be its impact on other consumer projects.
Update
Both approaches above may suit your needs. Alternatively, as suggested in comments further options could be more sustainable and clear in the long run towards a better governance and maintenance (centralization):
Use a common parent pom where you could declare the list of shared dependencies, that is, dependencies that will be used by all children projects, defined (and maintained) once.
Use the dependencyManagement section in ProjectX to change how Maven dependencies mediation would handle transitive dependencies on ProjectA, without changing ProjectA or ProjectB (similar to option 1 at the top of this answer, with the difference that it will only be applied whenever the dependency comes into scope and ignored otherwise). To centralize this management, again, would be better to do so in a common parent (option above).
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.
We are trying to centralize the versions of all the artifacts that we are using in our code base to remove duplication and ease the task of bumping versions.
We have created a BOM pom with the versions of all of our artifacts and third party artifacts and imported it (scope import) in the dependencyManagement section of the poms of each of our artifacts.
To avoid having to update each artifact each time the bom version changes we have tried to use a version range when importing the bom.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.acme</groupId>
<artifactId>bom</artifactId>
<version>[1.0,)</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
However maven does not seem to recognize version ranges in the dependencyManagement section of a pom.
I'm aware that if the relationship between our artifacts were hierarchical we could use modules and release from a parent POM. However unfortunately this is not the case.
This must be a common use case for maven. What are we doing wrong or what other solutions exist?
Taking into account the number of relevant issues in Maven issues tracker, it looks like it was a long-lasting issue in Maven.
Based on the most recent and relevant ticket, this will be resolved in the next major Maven release (4.0.0).
I have a multimodule maven project, where module share dependencies. By share I mean use the same dependencies. However each module declares dependencies itself. To keep sanity (yeah, maven, sanity, I know), and to have all modules using the same version of dependencies, parent pom declares properties with version numbers:
<properties>
<dependency1.version>1.0-SNAPSHOT</dependency1.version>
<dependency2.version>1.1-SNAPSHOT</dependency2.version>
</properties>
and all modules use that like:
<dependency>
<groupId>group</groupId>
<artifactId>dependency1</artifactId>
<version>${dependency1.version}</version>
</dependency>
I'm quite happy with this setup, as it allows me to change dependencies versions in 1 place.
Now I have a bunch of dependencies that I maintain myself. Release of those is automatic and very simple, basically:
mvn release:prepare release:perform -B
now I want to automate further and in the main project I run:
mvn versions:update-properties
(basically I also run: "mvn versions:use-releases" to change usual dependencies if needed, but it's out of the scope of this question).
After this update-properties run, properties in my main projects pom point to releases (which is good). However if my modules use properties to define versions of other dependencies and those projects have newer versions available, those properties are also changed.
Is there any way to limit damage from update-properties? versions:use-release takes includes property, so I can use it only on mine artefacts. Cannot find anything similar for update-properties.
I can revert all poms besides parent one and commit/push only that, but it doesn't seem elegant.
It sounds that you didn't understand the concept of maven.
In such circumstances you should use dependencyManagement in the parent pom like the following:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
...
</dependencies>
</dependencyManagement>
In you modules you just use a dependency like this:
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
...
</dependencies>
The important step is not to define the version. In this case the version will be used which is defined by the dependency management block. So you don't need to define properties etc. and furthermore you have a single point where you can define and change the dependencies in particular the versions.
Apart from that it's possible to limit the properties which will be changed defining it on the command line on the version:update-properties call.
I am new to Maven and am setting up my first maven project. I am also creating some maven assets in the form of some poms that can be inherited from or used as dependencies in any future projects as well. I want to group dependencies together and to be able to selectively add them to a project as needed.
I read this article on pom best practices. I like the idea of grouping related dependencies together into poms and then adding the pom as a dependency to a project as needed. This approach works great for compile scoped dependencies. However it fails for provided scoped ones since as transitive dependencies, they get omitted.
Here's an example of what I mean: Lets say I group together web dependencies for my projects into a web-deps pom.xml. These include compile scoped spring framework dependencies and also a provided scoped javaee one:
<modelVersion>4.0.0</modelVersion>
<groupId>com.xyz</groupId>
<artifactId>mvn-web-deps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>javaee</groupId>
<artifactId>javaee-api</artifactId>
<version>${javaee.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
I then add this pom as a dependency in another project:
<modelVersion>4.0.0</modelVersion>
<groupId>com.xyz</groupId>
<artifactId>project-a</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependency>
<groupId>com.xyz</groupId>
<artifactId>mvn-web-deps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>pom</type>
</dependency>
The dependencies in mvn-web-deps now become transitive. Since the dependency reference above is compile scoped, the provided transitive dependency gets omitted.
I want to avoid adding them to the dependency section of a parent since there can only be one parent and a project may need only some of these dependency groups, not all. I can perhaps add them to the dependencyManagement section, but then I will have to redeclare each dependency (sans the version) in each child project.
What is the correct/better way of grouping dependencies while avoiding the issues like above?
The short answer to your question is that you should only include 'provided' dependencies locally where the code requires it to compile, but not in parent pom.xml or other structures. Indicating that you have a 'provided' dependency in global pom.xml is non-sense for maven, because it does not need it to compile in such pom.xml.
Here is the long answer:
When I started using Maven, I had the same idea of trying to group artifacts and dependencies into pom.xml modules hoping they would be useful in the future. Now, that I have a bit more experience, I got to understand that it is a complete waste of time. For me, this was form of over-engineering.
I have learned to split my big projects into separate modules, each in their own subversion repository. I am including dependencies as necessary for each local module in their pom.xml. I release versioned tags of each module as I am coding and as necessary (i.e., when tested and stable).
I build my big projects by creating a separate maven project with its own pom.xml and import my modules as dependencies. From time to time, I update the module's version in the dependency when I have made a release. Then, I let maven do the job of pulling whatever it has to pull, transitively of not, when compiling/releasing the big project.
Maven allows all sorts of complex constructions and hierarchy between pom.xmls, but IMHO this feature creates unnecessary mess and complexities. So far it has not proved to be a real benefit for me. At the beginning, I was hoping that compiling one pom.xml would compile the rest properly in a cascading way. I did get some result, but what a mess to maintain in all the global pom.xml.
Releasing my module's artifacts separately and building my project on these releases has saved me so much time that I can only recommend it. In total, I have less pom.xml to maintain and they are also less complex. For the same final result...
So, if your only reason for building global/structural pom.xml is a hope to save time, I recommend abandoning this idea... Separate code in separate projects, release and THEN compile globally.
I concluded that Maven was not designed for this kind of use-case. I ended up having a parent pom.xml with all the libraries I use added to its <dependencyManagement> section. Any new projects/modules that I create have their pom.xml inherit from the parent pom.xml and add each dependency they need to their own <dependencies> section, minus the version. This scheme allows me to manage the versions for the libraries that I use and the respository declarations they need at a single place. Another advantage (over trying to create dependency bundles somehow) is that this gives more fine-grained control over the libraries added to child poms - only the dependencies that are actually needed are added.
Provided-scope dependencies are indeed inherited from parent POM, but NOT from POM defined as dependencies and I consider that a Maven weakness.
Given that Maven has also difficulties in adding modules as dependencies across module hierarchies, I can't say Maven is a sophisticated tool to manage multi-module projects. Maven expects a strict single-rooted hierarchy that is only suitable for the simplest projects.