0

Lets say I have a multi-module Gradle Kotlin project. The structure is as follows:

  • main - app runner + gluing everything together
  • modules
    • users
      • users-adapters
      • users-domain
    • orders
      • orders-adapters
      • orders-domain

Now - I wanted to be able to manage dependencies selectively. My intentions are:

  1. 3rd party libraries like Web library, SQL library, etc. are declared in their versions once, centrally (all modules use same versions of given library).
  2. All domain modules are completely 3rd party agnostic. No 3rd party library should be available on classpath here.
  3. All adapters modules should be able to receive all or choose selectively the 3rd party dependencies declared somewhere centrally.

I was thinking about subprojects {} declaration in root build.gradle.kts but this would impact all modules, while I want to leave domain ones untouched.

Is there a way so that I can have like:

  • one "global" dependencies list
  • one "for adapters" dependencies list
  • one "for domains" dependencies list

How to achieve this?

Maciej Tułaza
  • 520
  • 1
  • 5
  • 10
  • It's a good idea to avoid using `allprojects {}` and `subprojects {}`, they're [not recommended by Gradle](https://docs.gradle.org/current/userguide/sharing_build_logic_between_subprojects.html#sec:convention_plugins_vs_cross_configuration). Instead you can use buildSrc convention plugins, which are a much more practical solution https://stackoverflow.com/a/71892685/4161471 – aSemy Oct 03 '22 at 06:54
  • However buildSrc plugins are not great for version alignment of dependencies. Instead I prefer the [Java Version Platform plugin](https://docs.gradle.org/current/userguide/java_platform_plugin.html). Create a separate subproject, define _all_ versions that are necessary, and the other subprojects can 'import' the version platform subproject. – aSemy Oct 03 '22 at 07:00

1 Answers1

0

I have been experimenting with the hexagonal architecture with Gradle myself and I have found the following setup working quite well for me.

(Please excuse for using the Kotlin DSL as I only have one repo to demo while time of writing this answer).

To start off, here is the package structure I follow:

<project root>
|     build.gradle.kts
|     ...
└───  apps
|     └───  <putting everything together>
|           |     build.gradle.kts
|           |     ...
└───  core
|     └───  <business logic>
|           |     build.gradle.kts
|           |     ...
└───  libs
      └───  <some common lib>
            |     build.gradle.kts
            |     ...

Let's focus on build.gradle.kts in project root:

plugins {
    id("io.spring.dependency-management") version "1.0.12.RELEASE"
}

subprojects {
    apply(plugin = "io.spring.dependency-management")

    dependencyManagement {
        dependencies {
            dependency("ch.qos.logback:logback-classic:1.2.11")           
            dependency("org.javamoney:moneta:1.4.2")
            dependency("nl.hiddewieringa:money-kotlin:1.0.1")
            dependencySet("io.jsonwebtoken:0.11.5") {
                entry("jjwt-api")
                entry("jjwt-impl")
            }
        }
    }
}

configure(subprojects) {
    dependencies {
        implementation("ch.qos.logback:logback-classic")
        testImplementation(kotlin("test"))
    }

    tasks.test {
        useJUnitPlatform()
    }
}

Quick explanation,
I am using Spring IO's Gradle plugin which enables declaration of packages and their version across projects in one place. If you observe, I only apply this plugin across subprojects.
Now in the configuration, I keep common configs applicable to all the subprojects.


Let's jump to core -> build.gradle.kts:

dependencies {
    api("nl.hiddewieringa:money-kotlin")
}

Here you can see the core only uses money-kotlin (Kotlin extensions for javax.money) which is api only.
Note that since logger is defined in the configuration for the subproject, I can use that in core logic to define logs. Also, versions are acquired from the ones mentioned in the main Gradle file.

Something similar would go for libs -> build.gradle.kts.


Finally apps -> build.gradle.kts looks like:

dependencies {
    api("io.jsonwebtoken:jjwt-api")
    api("nl.hiddewieringa:money-kotlin")
    implementation("org.javamoney:moneta")
    runtimeOnly("io.jsonwebtoken:jjwt-impl")
}

Here, I am applying the actual implementation of java-money using moneta (separating logic and api from actual impl). Similarly, jjwt is only applied here.

This way, all the versions are controlled in a single place. Logic gets separated out of impl details.
As I see, this covers most things needed for hexagonal pattern.
Hope it helps.

advay rajhansa
  • 1,129
  • 9
  • 17