diff --git a/build.gradle.kts b/build.gradle.kts index 54771e7..f2e1695 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,6 +89,8 @@ dependencies { implementation(libs.springBoot.starter.mustache) implementation(libs.springBoot.starter.web) + testImplementation(libs.archUnit.junit) + developmentOnly(libs.springBoot.devtools) kover(project(":edge-contracts")) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index afa635d..2ad22ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ spring-cloud = "4.1.5" testcontainers = "1.19.7" [libraries] +archUnit-junit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.1" } flyway-core = { module = "org.flywaydb:flyway-core", version = "9.22.3" } jackson-datatype-jsr = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } diff --git a/src/test/kotlin/com/github/dannecron/demo/ArchTest.kt b/src/test/kotlin/com/github/dannecron/demo/ArchTest.kt new file mode 100644 index 0000000..5b9343b --- /dev/null +++ b/src/test/kotlin/com/github/dannecron/demo/ArchTest.kt @@ -0,0 +1,61 @@ +package com.github.dannecron.demo + +import com.tngtech.archunit.core.importer.ClassFileImporter +import com.tngtech.archunit.core.importer.ImportOption +import com.tngtech.archunit.library.Architectures +import org.junit.jupiter.api.Test + +class ArchTest { + + companion object { + // high-level layers + private const val LAYER_EDGE_CONSUMING = "edge-consuming" + private const val LAYER_EDGE_REST = "edge-rest" + // core + private const val LAYER_CORE = "core" + // low-level layers + private const val LAYER_DB = "db" + private const val LAYER_EDGE_INTEGRATIONS = "edge-integrations" + private const val LAYER_EDGE_PRODUCING = "edge-producing" + // edge-contracts + private const val LAYER_EDGE_CONTRACTS = "edge-contracts" + } + + private val config: List>> = listOf( + Triple(LAYER_EDGE_CONSUMING, "edgeconsuming", emptyList()), + Triple(LAYER_EDGE_REST, "edgerest", emptyList()), + Triple(LAYER_CORE, "core", listOf(LAYER_EDGE_CONSUMING, LAYER_EDGE_REST)), + Triple(LAYER_DB, "db", listOf(LAYER_CORE)), + Triple(LAYER_EDGE_INTEGRATIONS, "edgeintegration", listOf(LAYER_CORE)), + Triple(LAYER_EDGE_PRODUCING, "edgeproducing", listOf(LAYER_CORE)), + Triple( + LAYER_EDGE_CONTRACTS, + "edgecontracts", + listOf(LAYER_EDGE_INTEGRATIONS, LAYER_EDGE_PRODUCING, LAYER_EDGE_CONSUMING, LAYER_EDGE_REST), + ), + + ) + + @Test + fun `check layers`() { + val mainSrcPackage = this::class.java.packageName + val allProjectClasses = ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages(mainSrcPackage) + + Architectures.layeredArchitecture() + .consideringAllDependencies().let { arch -> + config.fold(arch) { archLocal, conf -> + val (layerName, layerNamespace, acceptedByLayers) = conf + archLocal.layer(layerName).definedBy("$mainSrcPackage.$layerNamespace..").let { + if (acceptedByLayers.isNotEmpty()) + it.whereLayer(layerName).mayOnlyBeAccessedByLayers(*acceptedByLayers.toTypedArray()) + else + it + } + } + }.also { + it.check(allProjectClasses) + } + } +}