Skip to content

Commit 8458c9d

Browse files
committed
update model and split documentation into multi-step setup
1 parent fcd8c01 commit 8458c9d

25 files changed

+675
-150
lines changed

.github/workflows/master-build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: "Build"
3030
shell: bash
3131
run: './gradlew --no-daemon build'
32-
- name: "Upload Service Description"
32+
- name: "Upload Application Description"
3333
uses: actions/upload-artifact@v4
3434
with:
3535
name: application-description
@@ -43,7 +43,7 @@ jobs:
4343
with:
4444
repository: automatic-architecture-documentation/documentation
4545
token: ${{ secrets.DOCUMENTATION_WRITE_TOKEN }}
46-
- name: "Download Service Description"
46+
- name: "Download Application Description"
4747
uses: actions/download-artifact@v4
4848
with:
4949
name: application-description

build.gradle.kts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,68 @@
1+
import documentation.generateApplicationDescription
12
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
import org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
24

35
plugins {
4-
id("org.springframework.boot") version "3.2.3"
5-
id("io.spring.dependency-management") version "1.1.4"
6-
kotlin("jvm") version "1.9.22"
7-
kotlin("plugin.spring") version "1.9.22"
6+
id("org.springframework.boot") version "3.2.3"
7+
id("io.spring.dependency-management") version "1.1.4"
8+
kotlin("jvm") version "1.9.22"
9+
kotlin("plugin.spring") version "1.9.22"
810
}
911

1012
group = "com.example"
1113
version = "0.0.1-SNAPSHOT"
1214

1315
java {
14-
sourceCompatibility = JavaVersion.VERSION_17
16+
sourceCompatibility = JavaVersion.VERSION_17
1517
}
1618

1719
repositories {
18-
mavenCentral()
20+
mavenCentral()
21+
}
22+
23+
dependencyManagement {
24+
imports {
25+
mavenBom("org.jetbrains.kotlin:kotlin-bom:1.9.22")
26+
mavenBom(BOM_COORDINATES)
27+
}
1928
}
2029

2130
dependencies {
22-
implementation("org.springframework.boot:spring-boot-starter")
23-
implementation("org.jetbrains.kotlin:kotlin-reflect")
24-
implementation("com.fasterxml.jackson.core:jackson-annotations")
25-
implementation("com.fasterxml.jackson.core:jackson-core")
26-
implementation("com.fasterxml.jackson.core:jackson-databind")
27-
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
28-
testImplementation("org.springframework.boot:spring-boot-starter-test")
31+
implementation("org.springframework.boot:spring-boot-starter")
32+
implementation("org.springframework.boot:spring-boot-starter-web")
33+
implementation("org.jetbrains.kotlin:kotlin-reflect")
34+
implementation("com.fasterxml.jackson.core:jackson-annotations")
35+
implementation("com.fasterxml.jackson.core:jackson-core")
36+
implementation("com.fasterxml.jackson.core:jackson-databind")
37+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
38+
testImplementation("org.springframework.boot:spring-boot-starter-test")
39+
testImplementation("org.wiremock:wiremock-standalone:3.4.1")
2940
}
3041

3142
tasks.withType<KotlinCompile> {
32-
kotlinOptions {
33-
freeCompilerArgs += "-Xjsr305=strict"
34-
jvmTarget = "17"
35-
}
43+
kotlinOptions {
44+
freeCompilerArgs += "-Xjsr305=strict"
45+
jvmTarget = "17"
46+
}
3647
}
3748

3849
tasks.withType<Test> {
39-
useJUnitPlatform()
50+
useJUnitPlatform()
51+
}
52+
53+
tasks.register("generateApplicationDescription") {
54+
val sourceFolder = File(project.rootDir, "build/architecture-documentation")
55+
val targetFolder = File(project.rootDir, "build/documentation/json")
56+
57+
inputs.dir(sourceFolder)
58+
outputs.dir(targetFolder)
59+
60+
dependsOn("test")
61+
doLast {
62+
generateApplicationDescription(sourceFolder, targetFolder, "backend-service-1")
63+
}
64+
}
65+
66+
tasks.build {
67+
dependsOn("generateApplicationDescription")
4068
}

buildSrc/build.gradle.kts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
plugins {
2+
kotlin("jvm") version "1.9.22"
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
}
8+
9+
dependencies {
10+
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
11+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1")
12+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package documentation.model
2+
3+
// (!) The content of this file is copied from repository to repository.
4+
// It is the model used by the various generation Gradle tasks and is the same for all repositories.
5+
// In a real project this would likely be packaged as library package and distributed using some kind of registry.
6+
7+
sealed interface Component {
8+
val id: String
9+
val groupId: String?
10+
val systemId: String?
11+
val type: ComponentType?
12+
val distanceFromUs: Distance?
13+
}
14+
15+
enum class ComponentType { BACKEND, FRONTEND, DATABASE }
16+
enum class Distance { OWNED, CLOSE, DISTANT }
17+
18+
data class Application(
19+
override val id: String,
20+
override val groupId: String?,
21+
override val systemId: String?,
22+
override val type: ComponentType?,
23+
override val distanceFromUs: Distance?,
24+
val dependents: List<Dependent> = emptyList(),
25+
val dependencies: List<Dependency> = emptyList(),
26+
) : Component
27+
28+
data class Dependent(
29+
override val id: String,
30+
override val groupId: String?,
31+
override val systemId: String?,
32+
override val type: ComponentType?,
33+
override val distanceFromUs: Distance?,
34+
) : Component
35+
36+
data class Dependency(
37+
override val id: String,
38+
override val groupId: String?,
39+
override val systemId: String?,
40+
override val type: ComponentType?,
41+
override val distanceFromUs: Distance?,
42+
val httpEndpoints: List<HttpEndpoint> = emptyList(),
43+
) : Component
44+
45+
data class HttpEndpoint(val method: String, val path: String)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package documentation
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude.Include
4+
import com.fasterxml.jackson.databind.DeserializationFeature
5+
import com.fasterxml.jackson.databind.SerializationFeature
6+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
7+
import com.fasterxml.jackson.module.kotlin.readValue
8+
import documentation.model.Application
9+
import documentation.model.Dependency
10+
import documentation.model.Dependent
11+
import documentation.model.HttpEndpoint
12+
import java.io.File
13+
import kotlin.reflect.KClass
14+
15+
private val objectMapper = jacksonObjectMapper()
16+
.setSerializationInclusion(Include.NON_EMPTY)
17+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
18+
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
19+
.enable(SerializationFeature.INDENT_OUTPUT)
20+
21+
fun generateApplicationDescription(sourceFolder: File, targetFolder: File, applicationId: String) {
22+
val baseApplicationDescription = loadBaseApplicationDescription(sourceFolder, applicationId)
23+
val dependents = loadDependents(sourceFolder)
24+
val dependencies = loadDependencies(sourceFolder)
25+
26+
val applicationDescription = baseApplicationDescription.copy(dependents = dependents, dependencies = dependencies)
27+
28+
val file = File(targetFolder, applicationDescription.id + ".json")
29+
objectMapper.writeValue(file, applicationDescription)
30+
}
31+
32+
private fun loadBaseApplicationDescription(sourceFolder: File, applicationId: String): Application {
33+
val file = File(sourceFolder, "$applicationId.json")
34+
check(file.isFile) { "File not found: $file" }
35+
return objectMapper.readValue<Application>(file)
36+
}
37+
38+
private fun loadDependents(sourceFolder: File): List<Dependent> =
39+
listJsonFilesInFolder(File(sourceFolder, "dependents"))
40+
.map { file -> loadDependent(file) }
41+
42+
private fun loadDependent(file: File): Dependent {
43+
return objectMapper.readValue<Dependent>(file)
44+
}
45+
46+
private fun loadDependencies(sourceFolder: File): List<Dependency> =
47+
listJsonFilesInFolder(File(sourceFolder, "dependencies"))
48+
.map { file -> loadDependency(file) }
49+
50+
private fun loadDependency(file: File): Dependency {
51+
val dependency = objectMapper.readValue<Dependency>(file)
52+
53+
var httpEndpoints = emptyList<HttpEndpoint>()
54+
55+
val httpEndpointsFile = File(file.parentFile, "http-endpoints/${dependency.id}.jsonl")
56+
if (httpEndpointsFile.isFile) {
57+
httpEndpoints = loadFromJsonListFile(httpEndpointsFile, HttpEndpoint::class)
58+
.sortedWith(compareBy(HttpEndpoint::path, HttpEndpoint::method))
59+
.distinct()
60+
}
61+
62+
return dependency.copy(httpEndpoints = httpEndpoints)
63+
}
64+
65+
private fun listJsonFilesInFolder(folder: File): List<File> =
66+
if (folder.isDirectory) {
67+
folder.listFiles()!!
68+
.filter { it.isFile }
69+
.filter { it.extension == "json" }
70+
} else {
71+
emptyList()
72+
}
73+
74+
private fun <T : Any> loadFromJsonListFile(file: File, clazz: KClass<T>): List<T> =
75+
file.readLines()
76+
.filter(String::isNotBlank)
77+
.map(String::trim)
78+
.map { objectMapper.readValue(it, clazz.java) }

src/main/kotlin/application/documentation/ApplicationDescription.kt

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/main/kotlin/application/documentation/ComponentDescription.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/main/kotlin/application/documentation/ComponentType.kt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/kotlin/application/documentation/Distance.kt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/kotlin/application/documentation/DocumentedDependency.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package application.documentation
2+
3+
data class ComponentDescription(
4+
val id: String,
5+
val groupId: String? = null,
6+
val systemId: String? = null,
7+
val type: ComponentType,
8+
val distanceFromUs: Distance,
9+
)
10+
11+
enum class ComponentType { BACKEND, FRONTEND, DATABASE }
12+
enum class Distance { OWNED, CLOSE, EXTERNAL }
13+
14+
data class HttpEndpoint(val method: String, val path: String)

src/main/kotlin/application/documentation/writer.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
package application.external
22

3-
import application.documentation.ComponentDescription
4-
import application.documentation.ComponentType.BACKEND
5-
import application.documentation.Distance.OWNED
6-
import application.documentation.DocumentedDependency
7-
import org.springframework.stereotype.Component
3+
import org.springframework.boot.context.properties.ConfigurationProperties
4+
import org.springframework.boot.context.properties.EnableConfigurationProperties
5+
import org.springframework.context.annotation.Bean
6+
import org.springframework.context.annotation.Configuration
7+
import org.springframework.web.client.RestClient
8+
import org.springframework.web.client.body
9+
import java.net.URL
810

9-
@Component
10-
class BackendService2Client : DocumentedDependency {
11+
class BackendService2Client(
12+
private val client: RestClient
13+
) {
1114

12-
override val description = ComponentDescription(
13-
id = "backend-service-2",
14-
contextId = "application",
15-
systemId = "platform",
16-
type = BACKEND,
17-
distanceFromUs = OWNED
18-
)
15+
fun getSomething(id: String): String? =
16+
client.get()
17+
.uri { it.path("/bar/{id}").build(mapOf("id" to id)) }
18+
.retrieve()
19+
.body<String>()
20+
}
21+
22+
@Configuration
23+
@EnableConfigurationProperties(BackendService2Properties::class)
24+
class BackendService2Configuration(
25+
private val properties: BackendService2Properties
26+
) {
1927

20-
fun getSomething(): String {
21-
TODO("some kind of implementation")
22-
}
28+
@Bean
29+
fun backendService2Client(): BackendService2Client =
30+
BackendService2Client(
31+
client = RestClient.builder()
32+
.baseUrl(properties.baseUrl.toString())
33+
.build()
34+
)
2335
}
36+
37+
@ConfigurationProperties("application.external.backends.backend-service-2")
38+
data class BackendService2Properties(
39+
val baseUrl: URL
40+
)

0 commit comments

Comments
 (0)