Skip to content

Project Structure & Build System

The build system is a set of tools and practices used to automate the process of building the project.

The project uses Gradle as build system.

Project Structure

The team decided to use mono-repository, multi project structure. Subprojects are the following ones:

  • architecture-tests (jvm): contains test for static structural code analysis of projects.
  • bdd (jvm): contains the Behavior-driven development tests.
  • commons (multiplatform): contains common code for all the projects.
  • events (multiplatform): contains the events shared between the services.
  • friendships-service (jvm): contains the friendships microservice.
  • frontend-service (node): contains the frontend microservice.
  • notifications-service (node): contains the notifications microservice.
  • multimedia-service (jvm): contains the multimedia microservice.
  • servers-service (jvm): contains the servers microservice.
  • users-service (jvm): contains the users microservice.

Shared build logic

Shared build logic is achieved using shared gradle conventions plugins. This allows to share common build logic between the projects, applying the defined plugins. Microservices project based on micronaut share many dependencies and configurations, so this approach helps to keep the build logic DRY, defining it in a single place.

It is possible to apply the shared build logic by adding the following line to the settings.gradle.kts file of the project:

kotlin
pluginManagement { includeBuild("build-logic") }

As follow a conceptual example of dependencies in every micronaut project:

kotlin
// build-logic/src/main/kotlin/micronaut.gradle.kts
plugins {
    id("org.jetbrains.kotlin.jvm")
    id("io.micronaut.application")
    ...
}

dependencies { ... }

micronaut { ... }

// Shared tasks

This configuration is defined in the build-logic project, and applied everywhere it is needed.

The result build.gradle.kts file will look like this:

kotlin
// servers-service/build.gradle.kts
plugins {
    id("micronaut")
}

// users-service/build.gradle.kts
plugins {
    id("micronaut")
}

Actual configuration

The shared build logic is defined in the build-logic project, and applied in the settings.gradle.kts file of the project.

Here the conventions plugins with hierarchy:

  • kotlin-conventions: define a common conventions for Kotlin projects configuration such as formatting.
  • kotlin-jvm: define a basic Kotlin JVM project configuration, extending with testing framework.
  • micronaut-base: define a basic Micronaut project configuration, with Micronaut plugin, and some common dependencies.
  • micronaut-full: add some additional dependencies and configurations to micronaut-base, commonly used in project's modules.
  • kotlin-multiplatform: define a basic Kotlin Multiplatform project configuration, with formatting, linting, and testing framework.
  • non-micronaut-project: here are defined tasks, used during the build process, for projects that are not based on Micronaut.

Dependencies declaration

In order to collect all the dependencies in a single place, the project uses the Version Catalog. You can find the file in gradle/libs.versions.toml.

This method allows to declare dependencies in build.gradle.kts files using the following syntax:

kotlin
dependencies {
    implementation(libs.kotlin)
    implementation(libs.micronaut)
    ...
}

Note: Many library version are not specified in the file (e.g. Micronaut sub dependencies), because they are managed by the framework itself, using the bom (Bill-Of-Materials) feature.

Unfortunately, the Version Catalog is not injected into the build-logic and plugins must be in class path if they are applied from a convention plugin.

Here the proposed workarounds:

  • Add the Version Catalog to the build-logic project:
kotlin
// build-logic/settings.gradle.kts
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}
  • Adding plugins as dependencies in the build-logic project:
kotlin
// build-logic/build.gradle.kts
fun Provider<PluginDependency>.asDependency(): Provider<String> =
    this.map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }

dependencies {
    implementation(libs.plugins.detekt.asDependency())
    implementation(libs.plugins.kotlin.asDependency())
    ...
}
  • Adding plugins in convention plugin with name, without version (it takes the version from project dependencies):
kotlin
// build-logic/src/main/kotlin/kotlin-base.gradle.kts
plugins {
    kotlin("jvm")
    id("io.gitlab.arturbosch.detekt")
}
  • Create the Version Catalog in the convention plugin project:
kotlin
val catalog: VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")

fun VersionCatalog.getLibrary(name: String): Provider<MinimalExternalModuleDependency> =
    findLibrary(name).get()

dependencies {
    testImplementation(catalog.getLibrary("konsist"))
    testImplementation(catalog.getLibrary("kotest"))
}

Typescript Gradle Plugin

Some sub projects are written in Typescript, so, for a didactic purpose, it's been create a Gradle plugin to manage the Typescript build. It's not intended to be a fully wrapper for tsc, node, npm and so on, but only a simple way to integrate Typescript projects into piper-kt project build flow.

You can find the code in a dedicated GitHub repository about Typescript Gradle Plugin with the documentation available in the README.md file.

It's been published on:

To apply the plugin in a project, you can add the following line in the build.gradle.kts file:

kotlin
plugins {
    id("io.github.zucchero-sintattico.typescript-gradle-plugin") version "<version>"
}

This plugin add gradle lifecycle tasks like build and check to the Typescript project, in order to easy integrate typescript projects (notifications, frontend) into build flow.

Additional tasks

The Micronaut Gradle Plugin used in micronaut subprojects, adds support for docker image context creation, docker file, and other related things.

In order to Build the Docker image are created similar tasks for non micronaut projects, each one with specific steps given the different build flow.

In particular the tasks are buildLayers and dockerfile.

Here a example:

kotlin
tasks.named("buildLayers") {
    doLast {
        copy {
            from("build/dist")
            into("build/docker/main/dist")
        }
        copy {
            from("node_modules")
            into("build/docker/main/node_modules")
        }
    }
}

tasks.register("dockerfile") {
    doLast {
        copy {
            from("Dockerfile")
            into("build/docker/main")
        }
    }
}