Skip to content

Commit

Permalink
update model and split documentation into multi-step setup
Browse files Browse the repository at this point in the history
  • Loading branch information
slu-it committed Mar 5, 2024
1 parent fcd8c01 commit 8458c9d
Show file tree
Hide file tree
Showing 25 changed files with 675 additions and 150 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/master-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: "Build"
shell: bash
run: './gradlew --no-daemon build'
- name: "Upload Service Description"
- name: "Upload Application Description"
uses: actions/upload-artifact@v4
with:
name: application-description
Expand All @@ -43,7 +43,7 @@ jobs:
with:
repository: automatic-architecture-documentation/documentation
token: ${{ secrets.DOCUMENTATION_WRITE_TOKEN }}
- name: "Download Service Description"
- name: "Download Application Description"
uses: actions/download-artifact@v4
with:
name: application-description
Expand Down
64 changes: 46 additions & 18 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
import documentation.generateApplicationDescription
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES

plugins {
id("org.springframework.boot") version "3.2.3"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.22"
kotlin("plugin.spring") version "1.9.22"
id("org.springframework.boot") version "3.2.3"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.22"
kotlin("plugin.spring") version "1.9.22"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
mavenCentral()
mavenCentral()
}

dependencyManagement {
imports {
mavenBom("org.jetbrains.kotlin:kotlin-bom:1.9.22")
mavenBom(BOM_COORDINATES)
}
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
testImplementation("org.springframework.boot:spring-boot-starter-test")
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.wiremock:wiremock-standalone:3.4.1")
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
jvmTarget = "17"
}
kotlinOptions {
freeCompilerArgs += "-Xjsr305=strict"
jvmTarget = "17"
}
}

tasks.withType<Test> {
useJUnitPlatform()
useJUnitPlatform()
}

tasks.register("generateApplicationDescription") {
val sourceFolder = File(project.rootDir, "build/architecture-documentation")
val targetFolder = File(project.rootDir, "build/documentation/json")

inputs.dir(sourceFolder)
outputs.dir(targetFolder)

dependsOn("test")
doLast {
generateApplicationDescription(sourceFolder, targetFolder, "backend-service-1")
}
}

tasks.build {
dependsOn("generateApplicationDescription")
}
12 changes: 12 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
kotlin("jvm") version "1.9.22"
}

repositories {
mavenCentral()
}

dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1")
}
45 changes: 45 additions & 0 deletions buildSrc/src/main/kotlin/documentation/model/model.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package documentation.model

// (!) The content of this file is copied from repository to repository.
// It is the model used by the various generation Gradle tasks and is the same for all repositories.
// In a real project this would likely be packaged as library package and distributed using some kind of registry.

sealed interface Component {
val id: String
val groupId: String?
val systemId: String?
val type: ComponentType?
val distanceFromUs: Distance?
}

enum class ComponentType { BACKEND, FRONTEND, DATABASE }
enum class Distance { OWNED, CLOSE, DISTANT }

data class Application(
override val id: String,
override val groupId: String?,
override val systemId: String?,
override val type: ComponentType?,
override val distanceFromUs: Distance?,
val dependents: List<Dependent> = emptyList(),
val dependencies: List<Dependency> = emptyList(),
) : Component

data class Dependent(
override val id: String,
override val groupId: String?,
override val systemId: String?,
override val type: ComponentType?,
override val distanceFromUs: Distance?,
) : Component

data class Dependency(
override val id: String,
override val groupId: String?,
override val systemId: String?,
override val type: ComponentType?,
override val distanceFromUs: Distance?,
val httpEndpoints: List<HttpEndpoint> = emptyList(),
) : Component

data class HttpEndpoint(val method: String, val path: String)
78 changes: 78 additions & 0 deletions buildSrc/src/main/kotlin/documentation/tasks.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package documentation

import com.fasterxml.jackson.annotation.JsonInclude.Include
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import documentation.model.Application
import documentation.model.Dependency
import documentation.model.Dependent
import documentation.model.HttpEndpoint
import java.io.File
import kotlin.reflect.KClass

private val objectMapper = jacksonObjectMapper()
.setSerializationInclusion(Include.NON_EMPTY)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
.enable(SerializationFeature.INDENT_OUTPUT)

fun generateApplicationDescription(sourceFolder: File, targetFolder: File, applicationId: String) {
val baseApplicationDescription = loadBaseApplicationDescription(sourceFolder, applicationId)
val dependents = loadDependents(sourceFolder)
val dependencies = loadDependencies(sourceFolder)

val applicationDescription = baseApplicationDescription.copy(dependents = dependents, dependencies = dependencies)

val file = File(targetFolder, applicationDescription.id + ".json")
objectMapper.writeValue(file, applicationDescription)
}

private fun loadBaseApplicationDescription(sourceFolder: File, applicationId: String): Application {
val file = File(sourceFolder, "$applicationId.json")
check(file.isFile) { "File not found: $file" }
return objectMapper.readValue<Application>(file)
}

private fun loadDependents(sourceFolder: File): List<Dependent> =
listJsonFilesInFolder(File(sourceFolder, "dependents"))
.map { file -> loadDependent(file) }

private fun loadDependent(file: File): Dependent {
return objectMapper.readValue<Dependent>(file)
}

private fun loadDependencies(sourceFolder: File): List<Dependency> =
listJsonFilesInFolder(File(sourceFolder, "dependencies"))
.map { file -> loadDependency(file) }

private fun loadDependency(file: File): Dependency {
val dependency = objectMapper.readValue<Dependency>(file)

var httpEndpoints = emptyList<HttpEndpoint>()

val httpEndpointsFile = File(file.parentFile, "http-endpoints/${dependency.id}.jsonl")
if (httpEndpointsFile.isFile) {
httpEndpoints = loadFromJsonListFile(httpEndpointsFile, HttpEndpoint::class)
.sortedWith(compareBy(HttpEndpoint::path, HttpEndpoint::method))
.distinct()
}

return dependency.copy(httpEndpoints = httpEndpoints)
}

private fun listJsonFilesInFolder(folder: File): List<File> =
if (folder.isDirectory) {
folder.listFiles()!!
.filter { it.isFile }
.filter { it.extension == "json" }
} else {
emptyList()
}

private fun <T : Any> loadFromJsonListFile(file: File, clazz: KClass<T>): List<T> =
file.readLines()
.filter(String::isNotBlank)
.map(String::trim)
.map { objectMapper.readValue(it, clazz.java) }

This file was deleted.

This file was deleted.

3 changes: 0 additions & 3 deletions src/main/kotlin/application/documentation/ComponentType.kt

This file was deleted.

3 changes: 0 additions & 3 deletions src/main/kotlin/application/documentation/Distance.kt

This file was deleted.

This file was deleted.

14 changes: 14 additions & 0 deletions src/main/kotlin/application/documentation/model.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package application.documentation

data class ComponentDescription(
val id: String,
val groupId: String? = null,
val systemId: String? = null,
val type: ComponentType,
val distanceFromUs: Distance,
)

enum class ComponentType { BACKEND, FRONTEND, DATABASE }
enum class Distance { OWNED, CLOSE, EXTERNAL }

data class HttpEndpoint(val method: String, val path: String)
20 changes: 0 additions & 20 deletions src/main/kotlin/application/documentation/writer.kt

This file was deleted.

51 changes: 34 additions & 17 deletions src/main/kotlin/application/external/BackendService2Client.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
package application.external

import application.documentation.ComponentDescription
import application.documentation.ComponentType.BACKEND
import application.documentation.Distance.OWNED
import application.documentation.DocumentedDependency
import org.springframework.stereotype.Component
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestClient
import org.springframework.web.client.body
import java.net.URL

@Component
class BackendService2Client : DocumentedDependency {
class BackendService2Client(
private val client: RestClient
) {

override val description = ComponentDescription(
id = "backend-service-2",
contextId = "application",
systemId = "platform",
type = BACKEND,
distanceFromUs = OWNED
)
fun getSomething(id: String): String? =
client.get()
.uri { it.path("/bar/{id}").build(mapOf("id" to id)) }
.retrieve()
.body<String>()
}

@Configuration
@EnableConfigurationProperties(BackendService2Properties::class)
class BackendService2Configuration(
private val properties: BackendService2Properties
) {

fun getSomething(): String {
TODO("some kind of implementation")
}
@Bean
fun backendService2Client(): BackendService2Client =
BackendService2Client(
client = RestClient.builder()
.baseUrl(properties.baseUrl.toString())
.build()
)
}

@ConfigurationProperties("application.external.backends.backend-service-2")
data class BackendService2Properties(
val baseUrl: URL
)
Loading

0 comments on commit 8458c9d

Please sign in to comment.