A Gradle plugin to constrain a multi-module build dependency graph.
Meant to enforce an API/Implementation
modularisation pattern in a multi-module monolith project:
Note
Credits of the picture to this good reference
article Slaying the monolith: API/Implementation modularisation pattern in Android development
by Sean Coyle
Example of the failure when, for instance, implementation
tries to depend on another implementation
module:
Apply the plugin at the root project:
plugins {
java
id("io.github.gmazzo.modulekind") version "<latest>"
}
Then each subproject must be configured with as api
or implementation
:
moduleKind = "api" // or "implementation" or "monolith"
The plugin introduces a new io.github.gmazzo.modulekind
attribute that:
- Is requested on key resolvable
Configuration
s for known plugins (java
andandroid
) - Decorates module's
Outgoing Variant
s. - Is used by an
AttributeCompatibilityRule
to determine if is a legal dependency for its consumer
It's recommended to read the Understanding Variant Selection chapter in Gradle's manual for a better understanding.
By default, the plugin will provide the following compatibility table (which can be printed by running
moduleKindConstraints
task):
moduleKind |
api | implementation | monolith |
---|---|---|---|
api | ❌ | ❌ | ❌ |
implementation | ✅ | ❌ | ❌ |
monolith | ✅ | ✅ | ✅ |
These restrictions are fully configurable through the moduleKindConstraints
DSL.
The following is the equivalent configuration of the table above:
moduleKindConstraints {
"implementation" compatibleWith "api"
"monolith" compatibleWith "implementation"
"monolith" compatibleWith "api" // redundant, since compatibilities are transitive
}
Or in groovy
:
moduleKindConstraints {
compatibility("implementation", "api")
compatibility("monolith", "implementation")
compatibility("monolith", "api") // redundant, since compatibilities are transitive
}
Note
Compatibles are transitive. So, if monolith
can depend on implementation
and implementation
on api
,
then monolith
can also depend on api
.
Like the demo
project, imagine you want to have an extra deep in the 4th levels dependency graph where:
app
is the monolithimplementation
andapi
are the usual setupapi
can also ondomain
modules, meant to share domain objects betweenapi
s
moduleKindConstraints {
"api" compatibleWith "domain"
"implementation" compatibleWith "api"
"app" compatibleWith "implementation"
}