diff --git a/core/core-domain/build.gradle.kts b/core/core-domain/build.gradle.kts index be0ac9e2..1ed50016 100644 --- a/core/core-domain/build.gradle.kts +++ b/core/core-domain/build.gradle.kts @@ -5,14 +5,18 @@ plugins { alias(libs.plugins.kotlin.serialization) } +kotlin { + compilerOptions { + freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime") + } +} + dependencies { // Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog // definierten Bibliotheken hat. api(projects.platform.platformDependencies) - // Kern-Abhängigkeiten für das Domänen-Modell. - // `api` wird verwendet, damit Services, die `core-domain` einbinden, - // diese Typen ebenfalls direkt nutzen können. + // Kern-Abhängigkeiten für das Domänen-Modul. api(libs.uuid) api(libs.kotlinx.serialization.json) api(libs.kotlinx.datetime) diff --git a/core/core-domain/src/main/kotlin/at/mocode/core/domain/event/DomainEvent.kt b/core/core-domain/src/main/kotlin/at/mocode/core/domain/event/DomainEvent.kt index 25e7c631..6cdc7f76 100644 --- a/core/core-domain/src/main/kotlin/at/mocode/core/domain/event/DomainEvent.kt +++ b/core/core-domain/src/main/kotlin/at/mocode/core/domain/event/DomainEvent.kt @@ -2,8 +2,9 @@ package at.mocode.core.domain.event import com.benasher44.uuid.Uuid import com.benasher44.uuid.uuid4 -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import java.util.UUID +import kotlin.time.Clock +import kotlin.time.Instant /** * Base interface for all domain events in the system. @@ -12,9 +13,9 @@ import kotlinx.datetime.Instant interface DomainEvent { val eventId: Uuid val aggregateId: Uuid - val eventType: String + val eventType: java.time.Instant val timestamp: Instant - val version: Long + val version: Int // OPTIMIZED: Added correlation and causation IDs for distributed tracing. /** @@ -33,8 +34,8 @@ interface DomainEvent { */ abstract class BaseDomainEvent( override val aggregateId: Uuid, - override val eventType: String, - override val version: Long, + override val eventType: java.time.Instant, + override val version: Int, override val eventId: Uuid = uuid4(), override val timestamp: Instant = Clock.System.now(), override val correlationId: Uuid? = null, diff --git a/core/core-domain/src/main/kotlin/at/mocode/core/domain/model/BaseDto.kt b/core/core-domain/src/main/kotlin/at/mocode/core/domain/model/BaseDto.kt index fa4fee3e..d66c3bdb 100644 --- a/core/core-domain/src/main/kotlin/at/mocode/core/domain/model/BaseDto.kt +++ b/core/core-domain/src/main/kotlin/at/mocode/core/domain/model/BaseDto.kt @@ -3,8 +3,8 @@ package at.mocode.core.domain.model import at.mocode.core.domain.serialization.KotlinInstantSerializer import at.mocode.core.domain.serialization.UuidSerializer import com.benasher44.uuid.Uuid -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant +import kotlin.time.Clock +import kotlin.time.Instant import kotlinx.serialization.Serializable /** @@ -49,7 +49,8 @@ data class ErrorDto( data class ApiResponse( val data: T?, val success: Boolean, - val errors: List = emptyList(), // OPTIMIZED: Using structured ErrorDto + val errors: List = emptyList(), + @Serializable(with = KotlinInstantSerializer::class) val timestamp: Instant = Clock.System.now() ) { companion object { diff --git a/core/core-domain/src/main/kotlin/at/mocode/core/domain/serialization/Serializers.kt b/core/core-domain/src/main/kotlin/at/mocode/core/domain/serialization/Serializers.kt index 28afc0d8..a964b236 100644 --- a/core/core-domain/src/main/kotlin/at/mocode/core/domain/serialization/Serializers.kt +++ b/core/core-domain/src/main/kotlin/at/mocode/core/domain/serialization/Serializers.kt @@ -2,7 +2,7 @@ package at.mocode.core.domain.serialization import com.benasher44.uuid.Uuid import com.benasher44.uuid.uuidFrom -import kotlinx.datetime.Instant +import kotlin.time.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime diff --git a/core/core-utils/build.gradle.kts b/core/core-utils/build.gradle.kts index a6bf8776..4e899026 100644 --- a/core/core-utils/build.gradle.kts +++ b/core/core-utils/build.gradle.kts @@ -4,6 +4,12 @@ plugins { alias(libs.plugins.kotlin.jvm) } +kotlin { + compilerOptions { + freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime") + } +} + dependencies { // Abhängigkeit zum platform-Modul für zentrale Versionsverwaltung api(projects.platform.platformDependencies) @@ -31,4 +37,5 @@ dependencies { // Testing testImplementation(projects.platform.platformTesting) testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.kotlin.test) } diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppConfig.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppConfig.kt index ee98ea73..be1dc6f3 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppConfig.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppConfig.kt @@ -1,29 +1,24 @@ package at.mocode.core.utils.config -import at.mocode.core.utils.database.DatabaseConfig import java.io.File +import java.net.InetAddress import java.util.Properties /** - * Zentrale Konfigurations-Klasse für die Anwendung. - * Hält alle Konfigurationswerte, die beim Start des Service explizit geladen werden. + * Zentrale, unveränderliche Konfigurations-Klasse für die Anwendung. + * Hält alle Konfigurationswerte, die beim Start eines Service geladen werden. */ class AppConfig( val environment: AppEnvironment, val appInfo: AppInfoConfig, val server: ServerConfig, + val database: DatabaseConfig, + val serviceDiscovery: ServiceDiscoveryConfig, val security: SecurityConfig, val logging: LoggingConfig, - val rateLimit: RateLimitConfig, - val serviceDiscovery: ServiceDiscoveryConfig, - val database: DatabaseConfig + val rateLimit: RateLimitConfig ) { companion object { - /** - * Factory-Methode, die eine AppConfig-Instanz durch das Laden von - * .properties-Dateien und Umgebungsvariablen erstellt. - * Dies ist der zentrale Einstiegspunkt, um die Konfiguration zu laden. - */ fun load(): AppConfig { val environment = AppEnvironment.current() val props = loadProperties(environment) @@ -32,191 +27,148 @@ class AppConfig( environment = environment, appInfo = AppInfoConfig.fromProperties(props), server = ServerConfig.fromProperties(props), + database = DatabaseConfig.fromProperties(props), + serviceDiscovery = ServiceDiscoveryConfig.fromProperties(props), security = SecurityConfig.fromProperties(props), logging = LoggingConfig.fromProperties(props, environment), - rateLimit = RateLimitConfig.fromProperties(props), - serviceDiscovery = ServiceDiscoveryConfig.fromProperties(props), - database = DatabaseConfig.fromProperties(props) + rateLimit = RateLimitConfig.fromProperties(props) ) } private fun loadProperties(environment: AppEnvironment): Properties { val props = Properties() - - // Lade Basis-Properties loadPropertiesFile("application.properties", props) - - // Lade umgebungsspezifische Properties val envFile = "application-${environment.name.lowercase()}.properties" loadPropertiesFile(envFile, props) - return props } private fun loadPropertiesFile(filename: String, props: Properties) { - val resourceStream = javaClass.classLoader.getResourceAsStream(filename) + val resourceStream = AppConfig::class.java.classLoader.getResourceAsStream(filename) if (resourceStream != null) { - props.load(resourceStream) - resourceStream.close() - } else { - val file = File("config/$filename") - if (file.exists()) { - file.inputStream().use { props.load(it) } - } + resourceStream.use { props.load(it) } + return + } + val file = File("config/$filename") + if (file.exists()) { + file.inputStream().use { props.load(it) } } } } } -/** - * Konfiguration für Anwendungsinformationen. - */ -data class AppInfoConfig( - val name: String, - val version: String, - val description: String -) { +data class AppInfoConfig(val name: String, val version: String, val description: String) { companion object { - fun fromProperties(props: Properties): AppInfoConfig { - return AppInfoConfig( - name = props.getProperty("app.name", "Meldestelle"), - version = props.getProperty("app.version", "1.0.0"), - description = props.getProperty("app.description", "Pferdesport Meldestelle System") - ) - } + fun fromProperties(props: Properties) = AppInfoConfig( + name = props.getProperty("app.name", "Meldestelle"), + version = props.getProperty("app.version", "1.0.0"), + description = props.getProperty("app.description", "Pferdesport Meldestelle System") + ) } } -/** - * Konfiguration für den Server. - */ data class ServerConfig( val port: Int, val host: String, + val advertisedHost: String, val workers: Int, val cors: CorsConfig ) { companion object { fun fromProperties(props: Properties): ServerConfig { - val corsConfig = CorsConfig( - enabled = props.getProperty("server.cors.enabled")?.toBoolean() ?: true, - allowedOrigins = props.getProperty("server.cors.allowedOrigins")?.split(",")?.map { it.trim() } - ?: listOf("*") - ) + val defaultHost = try { InetAddress.getLocalHost().hostAddress } catch (_: Exception) { "127.0.0.1" } return ServerConfig( - port = System.getenv("API_PORT")?.toIntOrNull() ?: props.getProperty("server.port", "8081").toInt(), - host = System.getenv("API_HOST") ?: props.getProperty("server.host", "0.0.0.0"), - workers = props.getProperty("server.workers")?.toIntOrNull() ?: Runtime.getRuntime() - .availableProcessors(), - cors = corsConfig + port = props.getIntProperty("server.port", "API_PORT", 8081), + host = props.getStringProperty("server.host", "API_HOST", "0.0.0.0"), + advertisedHost = props.getStringProperty("server.advertisedHost", "API_HOST_ADVERTISED", defaultHost), + workers = props.getIntProperty("server.workers", "API_WORKERS", Runtime.getRuntime().availableProcessors()), + cors = CorsConfig.fromProperties(props) + ) + } + } + data class CorsConfig(val enabled: Boolean, val allowedOrigins: List) { + companion object { + fun fromProperties(props: Properties) = CorsConfig( + enabled = props.getBooleanProperty("server.cors.enabled", "API_CORS_ENABLED", true), + allowedOrigins = props.getProperty("server.cors.allowedOrigins")?.split(",")?.map { it.trim() } ?: listOf("*") ) } } - - data class CorsConfig( - val enabled: Boolean, - val allowedOrigins: List - ) } -/** - * Konfiguration für die Sicherheit. - */ -data class SecurityConfig( - val jwt: JwtConfig, - val apiKey: String? +data class DatabaseConfig( + val jdbcUrl: String, + val username: String, + val password: String, + val driverClassName: String, + val maxPoolSize: Int, + val minPoolSize: Int, + val autoMigrate: Boolean ) { companion object { - fun fromProperties(props: Properties): SecurityConfig { - val jwtConfig = JwtConfig( - secret = System.getenv("JWT_SECRET") ?: props.getProperty( - "security.jwt.secret", - "default-jwt-secret-key-please-change-in-production" - ), - issuer = System.getenv("JWT_ISSUER") ?: props.getProperty("security.jwt.issuer", "meldestelle-api"), - audience = System.getenv("JWT_AUDIENCE") ?: props.getProperty( - "security.jwt.audience", - "meldestelle-clients" - ), - realm = System.getenv("JWT_REALM") ?: props.getProperty("security.jwt.realm", "meldestelle"), - expirationInMinutes = props.getProperty("security.jwt.expirationInMinutes")?.toLongOrNull() ?: (60 * 24) - ) - return SecurityConfig( - jwt = jwtConfig, - apiKey = System.getenv("API_KEY") ?: props.getProperty("security.apiKey") + fun fromProperties(props: Properties): DatabaseConfig { + val host = props.getStringProperty("database.host", "DB_HOST", "localhost") + val port = props.getIntProperty("database.port", "DB_PORT", 5432) + val name = props.getStringProperty("database.name", "DB_NAME", "meldestelle_db") + return DatabaseConfig( + jdbcUrl = "jdbc:postgresql://$host:$port/$name", + username = props.getStringProperty("database.username", "DB_USER", "meldestelle_user"), + password = props.getStringProperty("database.password", "DB_PASSWORD", "secure_password_change_me"), + driverClassName = "org.postgresql.Driver", + maxPoolSize = props.getIntProperty("database.maxPoolSize", "DB_MAX_POOL_SIZE", 10), + minPoolSize = props.getIntProperty("database.minPoolSize", "DB_MIN_POOL_SIZE", 5), + autoMigrate = props.getBooleanProperty("database.autoMigrate", "DB_AUTO_MIGRATE", true) ) } } - - data class JwtConfig( - val secret: String, - val issuer: String, - val audience: String, - val realm: String, - val expirationInMinutes: Long - ) } -/** - * Konfiguration für das Logging. - */ -data class LoggingConfig( - val level: String, - val logRequests: Boolean, - val logResponses: Boolean - // ... many more detailed properties from your original file -) { +data class ServiceDiscoveryConfig(val enabled: Boolean, val consulHost: String, val consulPort: Int) { companion object { - fun fromProperties(props: Properties, env: AppEnvironment): LoggingConfig { - return LoggingConfig( - level = props.getProperty("logging.level", if (env == AppEnvironment.PRODUCTION) "INFO" else "DEBUG"), - logRequests = props.getProperty("logging.requests")?.toBoolean() ?: true, - logResponses = props.getProperty("logging.responses")?.toBoolean() ?: (env != AppEnvironment.PRODUCTION) - // ... load other properties here - ) - } + fun fromProperties(props: Properties) = ServiceDiscoveryConfig( + enabled = props.getBooleanProperty("service-discovery.enabled", "CONSUL_ENABLED", true), + consulHost = props.getStringProperty("service-discovery.consul.host", "CONSUL_HOST", "consul"), + consulPort = props.getIntProperty("service-discovery.consul.port", "CONSUL_PORT", 8500) + ) } } -/** - * Konfiguration für Rate Limiting. - */ -data class RateLimitConfig( - val enabled: Boolean, - val globalLimit: Int, - val globalPeriodMinutes: Int -) { +data class SecurityConfig(val jwt: JwtConfig, val apiKey: String?) { companion object { - fun fromProperties(props: Properties): RateLimitConfig { - return RateLimitConfig( - enabled = props.getProperty("ratelimit.enabled")?.toBoolean() ?: true, - globalLimit = props.getProperty("ratelimit.global.limit")?.toIntOrNull() ?: 100, - globalPeriodMinutes = props.getProperty("ratelimit.global.periodMinutes")?.toIntOrNull() ?: 1 + fun fromProperties(props: Properties) = SecurityConfig( + jwt = JwtConfig.fromProperties(props), + apiKey = props.getStringProperty("security.apiKey", "API_KEY", "").ifEmpty { null } + ) + } + data class JwtConfig(val secret: String, val issuer: String, val audience: String, val realm: String, val expirationInMinutes: Long) { + companion object { + fun fromProperties(props: Properties) = JwtConfig( + secret = props.getStringProperty("security.jwt.secret", "JWT_SECRET", "default-secret-please-change-in-production"), + issuer = props.getStringProperty("security.jwt.issuer", "JWT_ISSUER", "meldestelle-api"), + audience = props.getStringProperty("security.jwt.audience", "JWT_AUDIENCE", "meldestelle-clients"), + realm = props.getStringProperty("security.jwt.realm", "JWT_REALM", "meldestelle"), + expirationInMinutes = props.getLongProperty("security.jwt.expirationInMinutes", "JWT_EXPIRATION_MINUTES", 60 * 24) ) } } } -/** - * Konfiguration für Service Discovery. - */ -data class ServiceDiscoveryConfig( - val enabled: Boolean, - val consulHost: String, - val consulPort: Int -) { +data class LoggingConfig(val level: String, val logRequests: Boolean, val logResponses: Boolean) { companion object { - fun fromProperties(props: Properties): ServiceDiscoveryConfig { - return ServiceDiscoveryConfig( - enabled = props.getProperty("service-discovery.enabled")?.toBoolean() ?: true, - consulHost = System.getenv("CONSUL_HOST") ?: props.getProperty( - "service-discovery.consul.host", - "consul" - ), - consulPort = System.getenv("CONSUL_PORT")?.toIntOrNull() - ?: props.getProperty("service-discovery.consul.port", "8500").toInt() - ) - } + fun fromProperties(props: Properties, env: AppEnvironment) = LoggingConfig( + level = props.getStringProperty("logging.level", "LOG_LEVEL", if (env.isProduction()) "INFO" else "DEBUG"), + logRequests = props.getBooleanProperty("logging.requests", "LOG_REQUESTS", true), + logResponses = props.getBooleanProperty("logging.responses", "LOG_RESPONSES", !env.isProduction()) + ) } } +data class RateLimitConfig(val enabled: Boolean, val globalLimit: Int, val globalPeriodMinutes: Int) { + companion object { + fun fromProperties(props: Properties) = RateLimitConfig( + enabled = props.getBooleanProperty("ratelimit.enabled", "RATE_LIMIT_ENABLED", true), + globalLimit = props.getIntProperty("ratelimit.global.limit", "RATE_LIMIT_GLOBAL_LIMIT", 100), + globalPeriodMinutes = props.getIntProperty("ratelimit.global.periodMinutes", "RATE_LIMIT_GLOBAL_PERIOD", 1) + ) + } +} diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppEnvironment.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppEnvironment.kt index 7cabc76b..f4261083 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppEnvironment.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/AppEnvironment.kt @@ -1,48 +1,26 @@ package at.mocode.core.utils.config -/** - * Aufzählung der verschiedenen Anwendungsumgebungen. - */ +import org.slf4j.LoggerFactory + enum class AppEnvironment { - DEVELOPMENT, // Lokale Entwicklungsumgebung - TEST, // Testumgebung (CI/CD, Integrationstests) - STAGING, // Vorabproduktionsumgebung - PRODUCTION; // Produktionsumgebung + DEVELOPMENT, + TEST, + STAGING, + PRODUCTION; + + fun isProduction() = this == PRODUCTION companion object { - /** - * Ermittelt die aktuelle Umgebung basierend auf der APP_ENV Umgebungsvariable. - * - * @return Die aktuelle Umgebung (Standardmäßig DEVELOPMENT wenn nicht definiert) - */ + private val logger = LoggerFactory.getLogger(AppEnvironment::class.java) + fun current(): AppEnvironment { val envName = System.getenv("APP_ENV")?.uppercase() ?: "DEVELOPMENT" return try { valueOf(envName) } catch (_: IllegalArgumentException) { - println("Warnung: Unbekannte Umgebung '$envName', verwende DEVELOPMENT") + logger.warn("Unknown environment '{}', falling back to DEVELOPMENT.", envName) DEVELOPMENT } } - - /** - * Prüft, ob die aktuelle Umgebung die Entwicklungsumgebung ist. - */ - fun isDevelopment() = current() == DEVELOPMENT - - /** - * Prüft, ob die aktuelle Umgebung die Testumgebung ist. - */ - fun isTest() = current() == TEST - - /** - * Prüft, ob die aktuelle Umgebung die Staging-Umgebung ist. - */ - fun isStaging() = current() == STAGING - - /** - * Prüft, ob die aktuelle Umgebung die Produktionsumgebung ist. - */ - fun isProduction() = current() == PRODUCTION } } diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/PropertiesExtensions.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/PropertiesExtensions.kt new file mode 100644 index 00000000..16eb9f67 --- /dev/null +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/config/PropertiesExtensions.kt @@ -0,0 +1,39 @@ +package at.mocode.core.utils.config + +import java.util.Properties + +/** + * Liest eine String-Property, wobei eine Umgebungsvariable Vorrang hat. + * + * @param key Der Schlüssel in der '.properties-Datei'. + * @param envVar Der Name der Umgebungsvariable. + * @param default Der Standardwert, falls weder Property noch Env-Var existieren. + * @return Der geladene Konfigurationswert. + */ +fun Properties.getStringProperty(key: String, envVar: String, default: String): String { + return System.getenv(envVar) ?: this.getProperty(key, default) +} + +/** + * Liest eine Integer-Property, wobei eine Umgebungsvariable Vorrang hat. + */ +fun Properties.getIntProperty(key: String, envVar: String, default: Int): Int { + val value = System.getenv(envVar) ?: this.getProperty(key) + return value?.toIntOrNull() ?: default +} + +/** + * Liest eine Boolean-Property, wobei eine Umgebungsvariable Vorrang hat. + */ +fun Properties.getBooleanProperty(key: String, envVar: String, default: Boolean): Boolean { + val value = System.getenv(envVar) ?: this.getProperty(key) + return value?.toBoolean() ?: default +} + +/** + * Liest eine Long-Property, wobei eine Umgebungsvariable Vorrang hat. + */ +fun Properties.getLongProperty(key: String, envVar: String, default: Long): Long { + val value = System.getenv(envVar) ?: this.getProperty(key) + return value?.toLongOrNull() ?: default +} diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseConfig.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseConfig.kt deleted file mode 100644 index 0a519823..00000000 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseConfig.kt +++ /dev/null @@ -1,55 +0,0 @@ -package at.mocode.core.utils.database - -import java.util.Properties - -/** - * Konfiguration für die Datenbankverbindung. - * Diese Klasse ist ein reiner Datenhalter (Value Object). Die Logik zum Laden - * der Werte ist in der companion object Factory-Methode gekapselt. - */ -data class DatabaseConfig( - val jdbcUrl: String, - val username: String, - val password: String, - val driverClassName: String = "org.postgresql.Driver", - val maxPoolSize: Int = 10, - val minPoolSize: Int = 5, - val autoMigrate: Boolean = true // Flag to enable/disable Flyway migrations -) { - companion object { - /** - * Erstellt eine Datenbank-Konfiguration aus Umgebungsvariablen und Properties. - * Die Priorität ist: Umgebungsvariablen > Properties > Standardwerte. - */ - fun fromProperties(props: Properties): DatabaseConfig { - val host = System.getenv("DB_HOST") ?: props.getProperty("database.host", "localhost") - val port = System.getenv("DB_PORT") ?: props.getProperty("database.port", "5432") - val database = System.getenv("DB_NAME") ?: props.getProperty("database.name", "meldestelle_db") - val username = System.getenv("DB_USER") ?: props.getProperty("database.username", "meldestelle_user") - val password = - System.getenv("DB_PASSWORD") ?: props.getProperty("database.password", "secure_password_change_me") - - val maxPoolSize = System.getenv("DB_MAX_POOL_SIZE")?.toIntOrNull() - ?: props.getProperty("database.maxPoolSize")?.toIntOrNull() - ?: 10 - - val minPoolSize = System.getenv("DB_MIN_POOL_SIZE")?.toIntOrNull() - ?: props.getProperty("database.minPoolSize")?.toIntOrNull() - ?: 5 - - val autoMigrate = System.getenv("DB_AUTO_MIGRATE")?.toBoolean() - ?: props.getProperty("database.autoMigrate")?.toBoolean() - ?: true - - return DatabaseConfig( - jdbcUrl = "jdbc:postgresql://$host:$port/$database", - username = username, - password = password, - driverClassName = "org.postgresql.Driver", - maxPoolSize = maxPoolSize, - minPoolSize = minPoolSize, - autoMigrate = autoMigrate - ) - } - } -} diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseFactory.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseFactory.kt index a96a0ce7..e012a4d2 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseFactory.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/database/DatabaseFactory.kt @@ -1,33 +1,29 @@ package at.mocode.core.utils.database +import at.mocode.core.utils.config.DatabaseConfig import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import kotlinx.coroutines.Dispatchers import org.flywaydb.core.Flyway import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.slf4j.LoggerFactory -/** - * Factory-Klasse für die Datenbankverbindung. - * Erstellt und konfiguriert eine Datenbankverbindung inklusive Connection Pool - * und führt bei der Initialisierung die notwendigen Migrationen aus. - * - * @property config Die Datenbankkonfiguration, die für diese Instanz verwendet werden soll. - */ class DatabaseFactory(private val config: DatabaseConfig) { + private companion object { + private val logger = LoggerFactory.getLogger(DatabaseFactory::class.java) + } + private var dataSource: HikariDataSource? = null private var database: Database? = null - /** - * Initialisiert die Datenbankverbindung. Muss vor der ersten Verwendung aufgerufen werden. - * Konfiguriert den Connection Pool und führt Flyway-Migrationen aus. - */ fun connect() { if (dataSource != null) { + logger.warn("Database already connected. Closing existing connection before creating a new one.") close() } - + logger.info("Initializing database connection to ${config.jdbcUrl}") val hikariConfig = createHikariConfig() val ds = HikariDataSource(hikariConfig) dataSource = ds @@ -38,28 +34,16 @@ class DatabaseFactory(private val config: DatabaseConfig) { } } - /** - * Schließt die Datenbankverbindung und den Connection Pool. - */ fun close() { dataSource?.close() dataSource = null database = null + logger.info("Database connection closed.") } - /** - * Führt eine Datenbankoperation in einer neuen, suspendierenden Transaktion aus. - * Dies ist die primäre Methode, um mit der Datenbank zu interagieren. - * - * @param block Der Code, der in der Transaktion ausgeführt werden soll. - * @return Das Ergebnis der Transaktion. - */ suspend fun dbQuery(block: suspend () -> T): T { - // Wir stellen sicher, dass die dbQuery-Funktion nur auf einer verbundenen Datenbank läuft. - if (database == null) { - throw IllegalStateException("Database has not been connected. Call connect() first.") - } - return newSuspendedTransaction(Dispatchers.IO, db = database) { + val db = database ?: throw IllegalStateException("Database has not been connected. Call connect() first.") + return newSuspendedTransaction(Dispatchers.IO, db = db) { block() } } @@ -74,29 +58,27 @@ class DatabaseFactory(private val config: DatabaseConfig) { minimumIdle = config.minPoolSize isAutoCommit = false transactionIsolation = "TRANSACTION_READ_COMMITTED" - connectionTestQuery = "SELECT 1" - validationTimeout = 5000 // 5 seconds - connectionTimeout = 30000 // 30 seconds - idleTimeout = 600000 // 10 minutes - maxLifetime = 1800000 // 30 minutes - leakDetectionThreshold = 60000 // 1 minute - poolName = "MeldestelleDbPool-${config.jdbcUrl.substringAfterLast('/')}" // Eindeutiger Pool-Name + validationTimeout = 5000 + connectionTimeout = 30000 + idleTimeout = 600000 + maxLifetime = 1800000 + leakDetectionThreshold = 60000 + poolName = "MeldestelleDbPool" } } private fun runFlyway(dataSource: HikariDataSource) { - println("Starte Flyway-Migrationen für Schema: ${dataSource.jdbcUrl}") + logger.info("Starting Flyway migrations...") try { - Flyway.configure() + val count = Flyway.configure() .dataSource(dataSource) .locations("classpath:db/migration") .load() .migrate() - println("Flyway-Migrationen erfolgreich abgeschlossen.") + .migrationsExecuted + logger.info("Flyway migrations completed successfully. Applied $count migrations.") } catch (e: Exception) { - println("FEHLER: Flyway-Migration fehlgeschlagen! Details: ${e.message}") - // Wir werfen den Fehler weiter, damit die Anwendung beim Start fehlschlägt. - // Das ist wichtig, um Inkonsistenzen zu vermeiden. + logger.error("Flyway migration failed!", e) throw IllegalStateException("Flyway migration failed", e) } } diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/discovery/ServiceRegistration.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/discovery/ServiceRegistration.kt index 432aabde..4a8bda6e 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/discovery/ServiceRegistration.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/discovery/ServiceRegistration.kt @@ -3,31 +3,32 @@ package at.mocode.core.utils.discovery import at.mocode.core.utils.config.AppConfig import com.orbitz.consul.Consul import com.orbitz.consul.model.agent.ImmutableRegistration -// KORREKTUR: Expliziter Import für die `Registration`-Klasse, die den `RegCheck` enthält. import com.orbitz.consul.model.agent.Registration -import java.net.InetAddress +import org.slf4j.LoggerFactory import java.util.* -/** - * Repräsentiert die Registrierung eines einzelnen Service-Exemplars bei Consul. - * Diese Klasse kümmert sich um den Lebenszyklus (Registrierung, Deregistrierung). - */ class ServiceRegistration internal constructor( private val consul: Consul, private val registration: ImmutableRegistration ) { + private companion object { + private val logger = LoggerFactory.getLogger(ServiceRegistration::class.java) + } + private var isRegistered = false fun register() { if (isRegistered) return try { - // Der `register`-Aufruf ist korrekt, da das `registration`-Objekt - // bereits außerhalb vollständig und korrekt gebaut wurde. consul.agentClient().register(registration) isRegistered = true - println("Service '${registration.name()}' mit ID '${registration.id()}' erfolgreich bei Consul registriert.") + logger.info( + "Service '{}' with ID '{}' successfully registered with Consul.", + registration.name(), + registration.id() + ) } catch (e: Exception) { - println("FEHLER: Service-Registrierung bei Consul fehlgeschlagen: ${e.message}") + logger.error("Failed to register service '{}' with Consul.", registration.name(), e) throw IllegalStateException("Could not register service with Consul", e) } } @@ -35,63 +36,65 @@ class ServiceRegistration internal constructor( fun deregister() { if (!isRegistered) return try { - // Der `deregister`-Aufruf ist korrekt. Er erwartet die Service-ID als einfachen String. consul.agentClient().deregister(registration.id()) isRegistered = false - println("Service '${registration.name()}' mit ID '${registration.id()}' erfolgreich bei Consul deregistriert.") + logger.info( + "Service '{}' with ID '{}' successfully deregistered from Consul.", + registration.name(), + registration.id() + ) } catch (e: Exception) { - println("FEHLER: Service-Deregistrierung bei Consul fehlgeschlagen: ${e.message}") + logger.error("Failed to deregister service '{}' from Consul.", registration.id(), e) } } } -/** - * Zentraler Registrar, der beim Anwendungsstart Services registriert. - * Diese Klasse wird einmalig mit der Gesamt-AppConfig initialisiert. - */ class ServiceRegistrar(private val appConfig: AppConfig) { + private companion object { + private val logger = LoggerFactory.getLogger(ServiceRegistrar::class.java) + } private val consul: Consul by lazy { val consulConfig = appConfig.serviceDiscovery + logger.info("Connecting to Consul at {}:{}", consulConfig.consulHost, consulConfig.consulPort) Consul.builder() .withUrl("http://${consulConfig.consulHost}:${consulConfig.consulPort}") .build() } - /** - * Erstellt und registriert einen Service basierend auf der App-Konfiguration. - * @return Eine ServiceRegistration-Instanz zur Verwaltung des Lebenszyklus. - */ fun registerCurrentService(): ServiceRegistration { val serviceName = appConfig.appInfo.name val servicePort = appConfig.server.port val serviceId = "$serviceName-${UUID.randomUUID()}" - val hostAddress = InetAddress.getLocalHost().hostAddress + val hostAddress = appConfig.server.advertisedHost - // KORREKTUR: Der Health Check MUSS über die statische Factory-Methode `http` - // der `Registration.RegCheck`-Klasse erstellt werden. Dies war die Hauptfehlerquelle. val healthCheck = Registration.RegCheck.http( - "http://$hostAddress:$servicePort/health", // Standard-Health-Check-Pfad - 10L, // Intervall in Sekunden - 5L // Timeout in Sekunden + "http://$hostAddress:$servicePort/health", + 10L, + 5L ) + // ========= FINALE KORREKTUR ========= + // Wir erstellen die Liste und die Map VORHER mit expliziten Typen, + // um dem Compiler bei der Typinferenz zu helfen. + val serviceTags: List = listOf("env:${appConfig.environment.name.lowercase()}") + val serviceMeta: Map = mapOf("version" to appConfig.appInfo.version) + val registration = ImmutableRegistration.builder() .id(serviceId) .name(serviceName) .address(hostAddress) .port(servicePort) .check(healthCheck) - .tags(listOf("env:${appConfig.environment.name.lowercase()}")) - .meta(mapOf("version" to appConfig.appInfo.version)) + .tags(serviceTags) // Verwenden der explizit typisierten Variablen + .meta(serviceMeta) // Verwenden der explizit typisierten Variablen .build() val serviceRegistration = ServiceRegistration(consul, registration) serviceRegistration.register() - // Fügt einen Shutdown-Hook hinzu, um den Service beim Beenden sauber zu deregistrieren Runtime.getRuntime().addShutdownHook(Thread { - println("Shutdown-Hook: Deregistriere Service ${serviceId}...") + logger.info("Shutdown hook triggered: Deregistering service '{}'...", serviceId) serviceRegistration.deregister() }) diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/serialization/Serialization.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/serialization/Serialization.kt index a4830d05..ee36df3f 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/serialization/Serialization.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/serialization/Serialization.kt @@ -4,7 +4,7 @@ import com.benasher44.uuid.Uuid import com.benasher44.uuid.uuidFrom // KORREKTUR: Der Import wurde von java.math.BigDecimal auf die korrekte Bibliothek geändert. import com.ionspin.kotlin.bignum.decimal.BigDecimal -import kotlinx.datetime.Instant +import kotlin.time.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime @@ -14,6 +14,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.time.ExperimentalTime object BigDecimalSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING) @@ -27,6 +28,7 @@ object UuidSerializer : KSerializer { override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString()) } +@OptIn(ExperimentalTime::class) object KotlinInstantSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString()) diff --git a/core/core-utils/src/main/kotlin/at/mocode/core/utils/validation/ValidationUtils.kt b/core/core-utils/src/main/kotlin/at/mocode/core/utils/validation/ValidationUtils.kt index 66f3f472..39d89aec 100644 --- a/core/core-utils/src/main/kotlin/at/mocode/core/utils/validation/ValidationUtils.kt +++ b/core/core-utils/src/main/kotlin/at/mocode/core/utils/validation/ValidationUtils.kt @@ -1,9 +1,10 @@ package at.mocode.core.utils.validation import kotlinx.datetime.LocalDate -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.todayIn +import kotlin.time.ExperimentalTime /** * Common validation utilities @@ -92,6 +93,7 @@ object ValidationUtils { /** * Validates birth date */ + @OptIn(ExperimentalTime::class) fun validateBirthDate(birthDate: LocalDate?, fieldName: String = "geburtsdatum"): ValidationError? { if (birthDate == null) return null @@ -116,6 +118,7 @@ object ValidationUtils { /** * Validates year value */ + @OptIn(ExperimentalTime::class) fun validateYear(year: Int?, fieldName: String, minYear: Int = 1900): ValidationError? { if (year == null) return null diff --git a/core/core-utils/src/test/kotlin/at/mocode/core/utils/database/SimpleDatabaseTest.kt b/core/core-utils/src/test/kotlin/at/mocode/core/utils/database/SimpleDatabaseTest.kt index 729fd96d..3253e286 100644 --- a/core/core-utils/src/test/kotlin/at/mocode/core/utils/database/SimpleDatabaseTest.kt +++ b/core/core-utils/src/test/kotlin/at/mocode/core/utils/database/SimpleDatabaseTest.kt @@ -2,9 +2,8 @@ package at.mocode.core.utils.database import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals +import org.junit.* +import org.junit.jupiter.api.Assertions.assertEquals /** * Comprehensive database connectivity and operations test. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60cb3d6f..139f7e41 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,9 +33,9 @@ lettuce = "6.3.1.RELEASE" # --- Service Discovery & Monitoring --- consulClient = "1.5.3" micrometer = "1.12.2" -micrometerTracing = "1.2.5" # NEU -zipkin = "2.24.4" # NEU (Verwendet eine neuere, kompatible Version) -zipkinReporter = "2.16.4" # NEU +micrometerTracing = "1.2.5" +zipkin = "3.0.5" +zipkinReporter = "2.16.4" # --- Authentication --- auth0Jwt = "4.4.0" @@ -134,10 +134,10 @@ lettuce-core = { module = "io.lettuce:lettuce-core", version.ref = "lettuce" } consul-client = { module = "com.orbitz.consul:consul-client", version.ref = "consulClient" } micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "micrometer" } micrometer-tracing-bridge-brave = { module = "io.micrometer:micrometer-tracing-bridge-brave", version.ref = "micrometerTracing" } # NEU -zipkin-reporter-brave = { module = "io.zipkin.reporter2:zipkin-reporter-brave", version.ref = "zipkinReporter" } # NEU -zipkin-sender-okhttp3 = { module = "io.zipkin.reporter2:zipkin-sender-okhttp3", version.ref = "zipkinReporter" } # NEU -zipkin-server = { module = "io.zipkin:zipkin-server", version.ref = "zipkin" } # NEU -zipkin-autoconfigure-ui = { module = "io.zipkin:zipkin-autoconfigure-ui", version.ref = "zipkin" } # NEU +zipkin-reporter-brave = { module = "io.zipkin.reporter2:zipkin-reporter-brave", version.ref = "zipkinReporter" } +zipkin-sender-okhttp3 = { module = "io.zipkin.reporter2:zipkin-sender-okhttp3", version.ref = "zipkinReporter" } +zipkin-server = { module = "io.zipkin:zipkin-server", version.ref = "zipkin" } +zipkin-autoconfigure-ui = { module = "io.zipkin:zipkin-autoconfigure-ui", version.ref = "zipkin" } # --- Authentication --- auth0-java-jwt = { module = "com.auth0:java-jwt", version.ref = "auth0Jwt" } diff --git a/infrastructure/cache/redis-cache/build.gradle.kts b/infrastructure/cache/redis-cache/build.gradle.kts index 022e80fb..e6fdba9a 100644 --- a/infrastructure/cache/redis-cache/build.gradle.kts +++ b/infrastructure/cache/redis-cache/build.gradle.kts @@ -7,6 +7,11 @@ plugins { alias(libs.plugins.spring.dependencyManagement) } +// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul. +tasks.getByName("bootJar") { + enabled = false +} + dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. api(platform(projects.platform.platformBom)) @@ -20,4 +25,6 @@ dependencies { // Stellt alle Test-Abhängigkeiten gebündelt bereit. testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.kotlin.test) } diff --git a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt b/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt index 6e52ae7c..ab1051a8 100644 --- a/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt +++ b/infrastructure/event-store/redis-event-store/src/main/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventConsumer.kt @@ -2,6 +2,8 @@ package at.mocode.infrastructure.eventstore.redis import at.mocode.core.domain.event.DomainEvent import at.mocode.infrastructure.eventstore.api.EventSerializer +import jakarta.annotation.PostConstruct +import jakarta.annotation.PreDestroy import org.slf4j.LoggerFactory import org.springframework.data.domain.Range import org.springframework.data.redis.connection.stream.* @@ -9,8 +11,6 @@ import org.springframework.data.redis.core.StringRedisTemplate import org.springframework.scheduling.annotation.Scheduled import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList -import javax.annotation.PostConstruct -import javax.annotation.PreDestroy /** * Consumer for Redis Streams that processes events using consumer groups. diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt index 5808e8be..004437ba 100644 --- a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt +++ b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreIntegrationTest.kt @@ -5,6 +5,8 @@ import at.mocode.core.domain.event.DomainEvent import at.mocode.infrastructure.eventstore.api.EventSerializer import at.mocode.infrastructure.eventstore.api.EventStore import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.data.redis.connection.RedisStandaloneConfiguration @@ -18,8 +20,6 @@ import java.time.Instant import java.util.* import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -import kotlin.test.assertEquals -import kotlin.test.assertTrue /** * Integration tests for Redis Event Store. @@ -285,7 +285,7 @@ class RedisEventStoreIntegrationTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: Int, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) @@ -293,7 +293,7 @@ class RedisEventStoreIntegrationTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: Int, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) } diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt index 78c8b806..c1b60176 100644 --- a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt +++ b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisEventStoreTest.kt @@ -519,7 +519,7 @@ class RedisEventStoreTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: UUID, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) @@ -527,7 +527,7 @@ class RedisEventStoreTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: UUID, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) } diff --git a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt index 06474233..b333e4e0 100644 --- a/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt +++ b/infrastructure/event-store/redis-event-store/src/test/kotlin/at/mocode/infrastructure/eventstore/redis/RedisIntegrationTest.kt @@ -231,7 +231,7 @@ class RedisIntegrationTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: UUID, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) @@ -239,7 +239,7 @@ class RedisIntegrationTest { override val eventId: UUID = UUID.randomUUID(), override val timestamp: Instant = Instant.now(), override val aggregateId: UUID, - override val version: Long, + override val version: UUID, val name: String ) : BaseDomainEvent(eventId, timestamp, aggregateId, version) } diff --git a/infrastructure/messaging/messaging-client/build.gradle.kts b/infrastructure/messaging/messaging-client/build.gradle.kts index ca359c25..0003c144 100644 --- a/infrastructure/messaging/messaging-client/build.gradle.kts +++ b/infrastructure/messaging/messaging-client/build.gradle.kts @@ -7,6 +7,11 @@ plugins { alias(libs.plugins.spring.dependencyManagement) } +// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul. +tasks.getByName("bootJar") { + enabled = false +} + dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. implementation(platform(projects.platform.platformBom)) diff --git a/infrastructure/messaging/messaging-config/build.gradle.kts b/infrastructure/messaging/messaging-config/build.gradle.kts index 70ed670a..61bb3198 100644 --- a/infrastructure/messaging/messaging-config/build.gradle.kts +++ b/infrastructure/messaging/messaging-config/build.gradle.kts @@ -7,6 +7,11 @@ plugins { alias(libs.plugins.spring.dependencyManagement) } +// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul. +tasks.getByName("bootJar") { + enabled = false +} + dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. api(platform(projects.platform.platformBom)) diff --git a/infrastructure/monitoring/monitoring-client/build.gradle.kts b/infrastructure/monitoring/monitoring-client/build.gradle.kts index dd9a1038..2364d4d2 100644 --- a/infrastructure/monitoring/monitoring-client/build.gradle.kts +++ b/infrastructure/monitoring/monitoring-client/build.gradle.kts @@ -7,6 +7,12 @@ plugins { alias(libs.plugins.spring.dependencyManagement) } +// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul. +tasks.getByName("bootJar") { + enabled = false +} + + dependencies { // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. implementation(platform(projects.platform.platformBom)) diff --git a/infrastructure/monitoring/monitoring-server/build.gradle.kts b/infrastructure/monitoring/monitoring-server/build.gradle.kts index 1f4f24c1..b5d58bef 100644 --- a/infrastructure/monitoring/monitoring-server/build.gradle.kts +++ b/infrastructure/monitoring/monitoring-server/build.gradle.kts @@ -23,9 +23,7 @@ dependencies { implementation(libs.spring.boot.starter.actuator) // Abhängigkeiten für den Zipkin-Server und seine UI. - // OPTIMIERUNG: Versionen werden jetzt zentral über libs.versions.toml verwaltet. implementation(libs.zipkin.server) - implementation(libs.zipkin.autoconfigure.ui) // Stellt alle Test-Abhängigkeiten gebündelt bereit. testImplementation(projects.platform.platformTesting) diff --git a/platform/platform-testing/build.gradle.kts b/platform/platform-testing/build.gradle.kts index 3dcd8e15..3c571be5 100644 --- a/platform/platform-testing/build.gradle.kts +++ b/platform/platform-testing/build.gradle.kts @@ -1,4 +1,5 @@ -/*plugins { +/* +plugins { alias(libs.plugins.kotlin.multiplatform) } @@ -34,7 +35,9 @@ kotlin { } } } -}*/ +} + + */ // Dieses Modul bündelt alle für JVM-Tests notwendigen Abhängigkeiten. // Jedes Modul, das Tests enthält, sollte dieses Modul mit `testImplementation` einbinden. @@ -46,12 +49,11 @@ dependencies { // Importiert die zentrale BOM für konsistente Versionen. api(platform(projects.platform.platformBom)) - // OPTIMIERUNG: Verwendung von Bundles, um die Konfiguration zu vereinfachen. // Diese Bundles sind in `libs.versions.toml` definiert. api(libs.bundles.testing.jvm) api(libs.bundles.testcontainers) - // Einzelne Test-Abhängigkeiten, die nicht in den Haupt-Bundles enthalten sind. + // Stellt Spring Boot Test-Abhängigkeiten und die H2-Datenbank für Tests bereit. api(libs.spring.boot.starter.test) - api(libs.h2.driver) // H2 wird oft für In-Memory-Tests benötigt. + api(libs.h2.driver) }