Module rules — protect your build time and architecture.
Have you ever realised that some module dependencies are incorrect within your project? Was the connection already too expensive to break? It is way cheaper to prevent dreaded dependencies than refactoring them. Let’s have a look at how we can approach prevention.

The problem
Modularisation is great and teams are highly encouraged to modularise their codebase. However with benefits of this comes a risk, that after the effort of modularising, new dependencies within modules slip in. Eventually we can realise that our modules graph is actually a list or we have a spaghetti modules graph, with more drawbacks than advantages.
Module rules
Many teams have agreed dependency rules and a recommended structure. At least orally, written somewhere in a better case. The problematic part is module dependency is so easy to add — single line in a Gradle file.
Murphy’s law of dependencies: Whatever they can access — they will access.
Once the dependency is in place, engineers often don’t even recognise that they start using classes from other modules. Android Studio is offering them, so why not? Any usage of class across modules tightens the screws. When the responsible engineer finally finds out, it is too late — one week of refactoring is a hard sell to your product manager.
In such situations, you regret not having the discussion at the moment the dependency was introduced. Perhaps just a small change or one interface would prevent the disaster.
Build time impact
Modularisation can improve your build time especially if we achieve a flat structure of independent modules to leverage your machine’s multithreading capabilities. Ideal modules graph could look like this:

The clean build of an application like this will take 20 seconds.
During some task, we suddenly realise we need config
within analytics
and also we need to use analytics
in our users
module.
A quick solution unfortunately takes only 2 lines of Gradle configuration:
api project('feature-config') // feature-analytics/build.gradle
api project('feature-analytics') // feature-users/build.gradle
and our optimised build time doubles to 40 seconds.

We have lost 20 seconds on each clean build, affecting incremental builds as well by a bigger amount of tasks executed in a serial order. We don’t see this immediately — at the moment we realise, it might be too late.

Architecture impact
The module graph above is most likely making you feel uncomfortable. We shouldn’t design applications like this.
We invest time into managing logical class layers and we also design our APIs to follow and reinforce these hierarchies. Similarly, we need to apply some sort of reinforcement to keep our module graph in a good shape. Otherwise, its bad shape can have the same consequences like spaghetti code.
Enforce the rules
Gradle provides an API to browse dependencies and we can access these through project.configurations
property. Through these properties we can inspect for example api
or implementation
dependencies. Example of getting all dependencies of single module:
One way of enforcement can be a Gradle plugin, browsing dependent modules and in case of any violation, notify the engineer or even more aggressively — fail the build.
Module Graph Assert plugin implements this technique and provides an assertion system for your project modules based on regex matching. You can define which dependencies are allowed, maximum height of the graph or your custom restrictions.
The plugin will add a new task assertModuleGraph
to the check task, quickly verifying these rules and failing in case any are violated. This should trigger the right conversations in the right moment when your desired module structure is in danger.
You can also generate your modules graph by Graphviz to get more insights where you can improve your modules structure.
Enjoy the modular world
Modularisation is powerful and if done right, it will bring you many benefits. Unfortunately, if done wrong it can bring you harm, frustration and rework.
Agreeing with your team on how your modules graph should look like is a first step. However only agreed rules might not be enough: reinforce your decisions by enforcement.
How big is your module graph? Any insights to share? You are welcome to post your modules graph or thoughts in comments.
Happy coding!