fixing gradle build

This commit is contained in:
2025-08-01 00:04:50 +02:00
parent df5919fac8
commit 4ea084bd1d
25 changed files with 280 additions and 341 deletions
+7 -3
View File
@@ -5,14 +5,18 @@ plugins {
alias(libs.plugins.kotlin.serialization) alias(libs.plugins.kotlin.serialization)
} }
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime")
}
}
dependencies { dependencies {
// Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog // Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog
// definierten Bibliotheken hat. // definierten Bibliotheken hat.
api(projects.platform.platformDependencies) api(projects.platform.platformDependencies)
// Kern-Abhängigkeiten für das Domänen-Modell. // Kern-Abhängigkeiten für das Domänen-Modul.
// `api` wird verwendet, damit Services, die `core-domain` einbinden,
// diese Typen ebenfalls direkt nutzen können.
api(libs.uuid) api(libs.uuid)
api(libs.kotlinx.serialization.json) api(libs.kotlinx.serialization.json)
api(libs.kotlinx.datetime) api(libs.kotlinx.datetime)
@@ -2,8 +2,9 @@ package at.mocode.core.domain.event
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4 import com.benasher44.uuid.uuid4
import kotlinx.datetime.Clock import java.util.UUID
import kotlinx.datetime.Instant import kotlin.time.Clock
import kotlin.time.Instant
/** /**
* Base interface for all domain events in the system. * Base interface for all domain events in the system.
@@ -12,9 +13,9 @@ import kotlinx.datetime.Instant
interface DomainEvent { interface DomainEvent {
val eventId: Uuid val eventId: Uuid
val aggregateId: Uuid val aggregateId: Uuid
val eventType: String val eventType: java.time.Instant
val timestamp: Instant val timestamp: Instant
val version: Long val version: Int
// OPTIMIZED: Added correlation and causation IDs for distributed tracing. // OPTIMIZED: Added correlation and causation IDs for distributed tracing.
/** /**
@@ -33,8 +34,8 @@ interface DomainEvent {
*/ */
abstract class BaseDomainEvent( abstract class BaseDomainEvent(
override val aggregateId: Uuid, override val aggregateId: Uuid,
override val eventType: String, override val eventType: java.time.Instant,
override val version: Long, override val version: Int,
override val eventId: Uuid = uuid4(), override val eventId: Uuid = uuid4(),
override val timestamp: Instant = Clock.System.now(), override val timestamp: Instant = Clock.System.now(),
override val correlationId: Uuid? = null, override val correlationId: Uuid? = null,
@@ -3,8 +3,8 @@ package at.mocode.core.domain.model
import at.mocode.core.domain.serialization.KotlinInstantSerializer import at.mocode.core.domain.serialization.KotlinInstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer import at.mocode.core.domain.serialization.UuidSerializer
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock import kotlin.time.Clock
import kotlinx.datetime.Instant import kotlin.time.Instant
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@@ -49,7 +49,8 @@ data class ErrorDto(
data class ApiResponse<T>( data class ApiResponse<T>(
val data: T?, val data: T?,
val success: Boolean, val success: Boolean,
val errors: List<ErrorDto> = emptyList(), // OPTIMIZED: Using structured ErrorDto val errors: List<ErrorDto> = emptyList(),
@Serializable(with = KotlinInstantSerializer::class)
val timestamp: Instant = Clock.System.now() val timestamp: Instant = Clock.System.now()
) { ) {
companion object { companion object {
@@ -2,7 +2,7 @@ package at.mocode.core.domain.serialization
import com.benasher44.uuid.Uuid import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuidFrom import com.benasher44.uuid.uuidFrom
import kotlinx.datetime.Instant import kotlin.time.Instant
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime import kotlinx.datetime.LocalTime
+7
View File
@@ -4,6 +4,12 @@ plugins {
alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.jvm)
} }
kotlin {
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.time.ExperimentalTime")
}
}
dependencies { dependencies {
// Abhängigkeit zum platform-Modul für zentrale Versionsverwaltung // Abhängigkeit zum platform-Modul für zentrale Versionsverwaltung
api(projects.platform.platformDependencies) api(projects.platform.platformDependencies)
@@ -31,4 +37,5 @@ dependencies {
// Testing // Testing
testImplementation(projects.platform.platformTesting) testImplementation(projects.platform.platformTesting)
testImplementation(libs.bundles.testing.jvm) testImplementation(libs.bundles.testing.jvm)
testImplementation(libs.kotlin.test)
} }
@@ -1,29 +1,24 @@
package at.mocode.core.utils.config package at.mocode.core.utils.config
import at.mocode.core.utils.database.DatabaseConfig
import java.io.File import java.io.File
import java.net.InetAddress
import java.util.Properties import java.util.Properties
/** /**
* Zentrale Konfigurations-Klasse für die Anwendung. * Zentrale, unveränderliche Konfigurations-Klasse für die Anwendung.
* Hält alle Konfigurationswerte, die beim Start des Service explizit geladen werden. * Hält alle Konfigurationswerte, die beim Start eines Service geladen werden.
*/ */
class AppConfig( class AppConfig(
val environment: AppEnvironment, val environment: AppEnvironment,
val appInfo: AppInfoConfig, val appInfo: AppInfoConfig,
val server: ServerConfig, val server: ServerConfig,
val database: DatabaseConfig,
val serviceDiscovery: ServiceDiscoveryConfig,
val security: SecurityConfig, val security: SecurityConfig,
val logging: LoggingConfig, val logging: LoggingConfig,
val rateLimit: RateLimitConfig, val rateLimit: RateLimitConfig
val serviceDiscovery: ServiceDiscoveryConfig,
val database: DatabaseConfig
) { ) {
companion object { 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 { fun load(): AppConfig {
val environment = AppEnvironment.current() val environment = AppEnvironment.current()
val props = loadProperties(environment) val props = loadProperties(environment)
@@ -32,191 +27,148 @@ class AppConfig(
environment = environment, environment = environment,
appInfo = AppInfoConfig.fromProperties(props), appInfo = AppInfoConfig.fromProperties(props),
server = ServerConfig.fromProperties(props), server = ServerConfig.fromProperties(props),
database = DatabaseConfig.fromProperties(props),
serviceDiscovery = ServiceDiscoveryConfig.fromProperties(props),
security = SecurityConfig.fromProperties(props), security = SecurityConfig.fromProperties(props),
logging = LoggingConfig.fromProperties(props, environment), logging = LoggingConfig.fromProperties(props, environment),
rateLimit = RateLimitConfig.fromProperties(props), rateLimit = RateLimitConfig.fromProperties(props)
serviceDiscovery = ServiceDiscoveryConfig.fromProperties(props),
database = DatabaseConfig.fromProperties(props)
) )
} }
private fun loadProperties(environment: AppEnvironment): Properties { private fun loadProperties(environment: AppEnvironment): Properties {
val props = Properties() val props = Properties()
// Lade Basis-Properties
loadPropertiesFile("application.properties", props) loadPropertiesFile("application.properties", props)
// Lade umgebungsspezifische Properties
val envFile = "application-${environment.name.lowercase()}.properties" val envFile = "application-${environment.name.lowercase()}.properties"
loadPropertiesFile(envFile, props) loadPropertiesFile(envFile, props)
return props return props
} }
private fun loadPropertiesFile(filename: String, props: Properties) { private fun loadPropertiesFile(filename: String, props: Properties) {
val resourceStream = javaClass.classLoader.getResourceAsStream(filename) val resourceStream = AppConfig::class.java.classLoader.getResourceAsStream(filename)
if (resourceStream != null) { if (resourceStream != null) {
props.load(resourceStream) resourceStream.use { props.load(it) }
resourceStream.close() return
} else { }
val file = File("config/$filename") val file = File("config/$filename")
if (file.exists()) { if (file.exists()) {
file.inputStream().use { props.load(it) } file.inputStream().use { props.load(it) }
}
} }
} }
} }
} }
/** data class AppInfoConfig(val name: String, val version: String, val description: String) {
* Konfiguration für Anwendungsinformationen.
*/
data class AppInfoConfig(
val name: String,
val version: String,
val description: String
) {
companion object { companion object {
fun fromProperties(props: Properties): AppInfoConfig { fun fromProperties(props: Properties) = AppInfoConfig(
return AppInfoConfig( name = props.getProperty("app.name", "Meldestelle"),
name = props.getProperty("app.name", "Meldestelle"), version = props.getProperty("app.version", "1.0.0"),
version = props.getProperty("app.version", "1.0.0"), description = props.getProperty("app.description", "Pferdesport Meldestelle System")
description = props.getProperty("app.description", "Pferdesport Meldestelle System") )
)
}
} }
} }
/**
* Konfiguration für den Server.
*/
data class ServerConfig( data class ServerConfig(
val port: Int, val port: Int,
val host: String, val host: String,
val advertisedHost: String,
val workers: Int, val workers: Int,
val cors: CorsConfig val cors: CorsConfig
) { ) {
companion object { companion object {
fun fromProperties(props: Properties): ServerConfig { fun fromProperties(props: Properties): ServerConfig {
val corsConfig = CorsConfig( val defaultHost = try { InetAddress.getLocalHost().hostAddress } catch (_: Exception) { "127.0.0.1" }
enabled = props.getProperty("server.cors.enabled")?.toBoolean() ?: true,
allowedOrigins = props.getProperty("server.cors.allowedOrigins")?.split(",")?.map { it.trim() }
?: listOf("*")
)
return ServerConfig( return ServerConfig(
port = System.getenv("API_PORT")?.toIntOrNull() ?: props.getProperty("server.port", "8081").toInt(), port = props.getIntProperty("server.port", "API_PORT", 8081),
host = System.getenv("API_HOST") ?: props.getProperty("server.host", "0.0.0.0"), host = props.getStringProperty("server.host", "API_HOST", "0.0.0.0"),
workers = props.getProperty("server.workers")?.toIntOrNull() ?: Runtime.getRuntime() advertisedHost = props.getStringProperty("server.advertisedHost", "API_HOST_ADVERTISED", defaultHost),
.availableProcessors(), workers = props.getIntProperty("server.workers", "API_WORKERS", Runtime.getRuntime().availableProcessors()),
cors = corsConfig cors = CorsConfig.fromProperties(props)
)
}
}
data class CorsConfig(val enabled: Boolean, val allowedOrigins: List<String>) {
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<String>
)
} }
/** data class DatabaseConfig(
* Konfiguration für die Sicherheit. val jdbcUrl: String,
*/ val username: String,
data class SecurityConfig( val password: String,
val jwt: JwtConfig, val driverClassName: String,
val apiKey: String? val maxPoolSize: Int,
val minPoolSize: Int,
val autoMigrate: Boolean
) { ) {
companion object { companion object {
fun fromProperties(props: Properties): SecurityConfig { fun fromProperties(props: Properties): DatabaseConfig {
val jwtConfig = JwtConfig( val host = props.getStringProperty("database.host", "DB_HOST", "localhost")
secret = System.getenv("JWT_SECRET") ?: props.getProperty( val port = props.getIntProperty("database.port", "DB_PORT", 5432)
"security.jwt.secret", val name = props.getStringProperty("database.name", "DB_NAME", "meldestelle_db")
"default-jwt-secret-key-please-change-in-production" return DatabaseConfig(
), jdbcUrl = "jdbc:postgresql://$host:$port/$name",
issuer = System.getenv("JWT_ISSUER") ?: props.getProperty("security.jwt.issuer", "meldestelle-api"), username = props.getStringProperty("database.username", "DB_USER", "meldestelle_user"),
audience = System.getenv("JWT_AUDIENCE") ?: props.getProperty( password = props.getStringProperty("database.password", "DB_PASSWORD", "secure_password_change_me"),
"security.jwt.audience", driverClassName = "org.postgresql.Driver",
"meldestelle-clients" maxPoolSize = props.getIntProperty("database.maxPoolSize", "DB_MAX_POOL_SIZE", 10),
), minPoolSize = props.getIntProperty("database.minPoolSize", "DB_MIN_POOL_SIZE", 5),
realm = System.getenv("JWT_REALM") ?: props.getProperty("security.jwt.realm", "meldestelle"), autoMigrate = props.getBooleanProperty("database.autoMigrate", "DB_AUTO_MIGRATE", true)
expirationInMinutes = props.getProperty("security.jwt.expirationInMinutes")?.toLongOrNull() ?: (60 * 24)
)
return SecurityConfig(
jwt = jwtConfig,
apiKey = System.getenv("API_KEY") ?: props.getProperty("security.apiKey")
) )
} }
} }
data class JwtConfig(
val secret: String,
val issuer: String,
val audience: String,
val realm: String,
val expirationInMinutes: Long
)
} }
/** data class ServiceDiscoveryConfig(val enabled: Boolean, val consulHost: String, val consulPort: Int) {
* 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
) {
companion object { companion object {
fun fromProperties(props: Properties, env: AppEnvironment): LoggingConfig { fun fromProperties(props: Properties) = ServiceDiscoveryConfig(
return LoggingConfig( enabled = props.getBooleanProperty("service-discovery.enabled", "CONSUL_ENABLED", true),
level = props.getProperty("logging.level", if (env == AppEnvironment.PRODUCTION) "INFO" else "DEBUG"), consulHost = props.getStringProperty("service-discovery.consul.host", "CONSUL_HOST", "consul"),
logRequests = props.getProperty("logging.requests")?.toBoolean() ?: true, consulPort = props.getIntProperty("service-discovery.consul.port", "CONSUL_PORT", 8500)
logResponses = props.getProperty("logging.responses")?.toBoolean() ?: (env != AppEnvironment.PRODUCTION) )
// ... load other properties here
)
}
} }
} }
/** data class SecurityConfig(val jwt: JwtConfig, val apiKey: String?) {
* Konfiguration für Rate Limiting.
*/
data class RateLimitConfig(
val enabled: Boolean,
val globalLimit: Int,
val globalPeriodMinutes: Int
) {
companion object { companion object {
fun fromProperties(props: Properties): RateLimitConfig { fun fromProperties(props: Properties) = SecurityConfig(
return RateLimitConfig( jwt = JwtConfig.fromProperties(props),
enabled = props.getProperty("ratelimit.enabled")?.toBoolean() ?: true, apiKey = props.getStringProperty("security.apiKey", "API_KEY", "").ifEmpty { null }
globalLimit = props.getProperty("ratelimit.global.limit")?.toIntOrNull() ?: 100, )
globalPeriodMinutes = props.getProperty("ratelimit.global.periodMinutes")?.toIntOrNull() ?: 1 }
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)
) )
} }
} }
} }
/** data class LoggingConfig(val level: String, val logRequests: Boolean, val logResponses: Boolean) {
* Konfiguration für Service Discovery.
*/
data class ServiceDiscoveryConfig(
val enabled: Boolean,
val consulHost: String,
val consulPort: Int
) {
companion object { companion object {
fun fromProperties(props: Properties): ServiceDiscoveryConfig { fun fromProperties(props: Properties, env: AppEnvironment) = LoggingConfig(
return ServiceDiscoveryConfig( level = props.getStringProperty("logging.level", "LOG_LEVEL", if (env.isProduction()) "INFO" else "DEBUG"),
enabled = props.getProperty("service-discovery.enabled")?.toBoolean() ?: true, logRequests = props.getBooleanProperty("logging.requests", "LOG_REQUESTS", true),
consulHost = System.getenv("CONSUL_HOST") ?: props.getProperty( logResponses = props.getBooleanProperty("logging.responses", "LOG_RESPONSES", !env.isProduction())
"service-discovery.consul.host", )
"consul"
),
consulPort = System.getenv("CONSUL_PORT")?.toIntOrNull()
?: props.getProperty("service-discovery.consul.port", "8500").toInt()
)
}
} }
} }
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)
)
}
}
@@ -1,48 +1,26 @@
package at.mocode.core.utils.config package at.mocode.core.utils.config
/** import org.slf4j.LoggerFactory
* Aufzählung der verschiedenen Anwendungsumgebungen.
*/
enum class AppEnvironment { enum class AppEnvironment {
DEVELOPMENT, // Lokale Entwicklungsumgebung DEVELOPMENT,
TEST, // Testumgebung (CI/CD, Integrationstests) TEST,
STAGING, // Vorabproduktionsumgebung STAGING,
PRODUCTION; // Produktionsumgebung PRODUCTION;
fun isProduction() = this == PRODUCTION
companion object { companion object {
/** private val logger = LoggerFactory.getLogger(AppEnvironment::class.java)
* Ermittelt die aktuelle Umgebung basierend auf der APP_ENV Umgebungsvariable.
*
* @return Die aktuelle Umgebung (Standardmäßig DEVELOPMENT wenn nicht definiert)
*/
fun current(): AppEnvironment { fun current(): AppEnvironment {
val envName = System.getenv("APP_ENV")?.uppercase() ?: "DEVELOPMENT" val envName = System.getenv("APP_ENV")?.uppercase() ?: "DEVELOPMENT"
return try { return try {
valueOf(envName) valueOf(envName)
} catch (_: IllegalArgumentException) { } catch (_: IllegalArgumentException) {
println("Warnung: Unbekannte Umgebung '$envName', verwende DEVELOPMENT") logger.warn("Unknown environment '{}', falling back to DEVELOPMENT.", envName)
DEVELOPMENT 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
} }
} }
@@ -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
}
@@ -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
)
}
}
}
@@ -1,33 +1,29 @@
package at.mocode.core.utils.database package at.mocode.core.utils.database
import at.mocode.core.utils.config.DatabaseConfig
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.flywaydb.core.Flyway import org.flywaydb.core.Flyway
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction 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) { class DatabaseFactory(private val config: DatabaseConfig) {
private companion object {
private val logger = LoggerFactory.getLogger(DatabaseFactory::class.java)
}
private var dataSource: HikariDataSource? = null private var dataSource: HikariDataSource? = null
private var database: Database? = 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() { fun connect() {
if (dataSource != null) { if (dataSource != null) {
logger.warn("Database already connected. Closing existing connection before creating a new one.")
close() close()
} }
logger.info("Initializing database connection to ${config.jdbcUrl}")
val hikariConfig = createHikariConfig() val hikariConfig = createHikariConfig()
val ds = HikariDataSource(hikariConfig) val ds = HikariDataSource(hikariConfig)
dataSource = ds dataSource = ds
@@ -38,28 +34,16 @@ class DatabaseFactory(private val config: DatabaseConfig) {
} }
} }
/**
* Schließt die Datenbankverbindung und den Connection Pool.
*/
fun close() { fun close() {
dataSource?.close() dataSource?.close()
dataSource = null dataSource = null
database = 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 <T> dbQuery(block: suspend () -> T): T { suspend fun <T> dbQuery(block: suspend () -> T): T {
// Wir stellen sicher, dass die dbQuery-Funktion nur auf einer verbundenen Datenbank läuft. val db = database ?: throw IllegalStateException("Database has not been connected. Call connect() first.")
if (database == null) { return newSuspendedTransaction(Dispatchers.IO, db = db) {
throw IllegalStateException("Database has not been connected. Call connect() first.")
}
return newSuspendedTransaction(Dispatchers.IO, db = database) {
block() block()
} }
} }
@@ -74,29 +58,27 @@ class DatabaseFactory(private val config: DatabaseConfig) {
minimumIdle = config.minPoolSize minimumIdle = config.minPoolSize
isAutoCommit = false isAutoCommit = false
transactionIsolation = "TRANSACTION_READ_COMMITTED" transactionIsolation = "TRANSACTION_READ_COMMITTED"
connectionTestQuery = "SELECT 1" validationTimeout = 5000
validationTimeout = 5000 // 5 seconds connectionTimeout = 30000
connectionTimeout = 30000 // 30 seconds idleTimeout = 600000
idleTimeout = 600000 // 10 minutes maxLifetime = 1800000
maxLifetime = 1800000 // 30 minutes leakDetectionThreshold = 60000
leakDetectionThreshold = 60000 // 1 minute poolName = "MeldestelleDbPool"
poolName = "MeldestelleDbPool-${config.jdbcUrl.substringAfterLast('/')}" // Eindeutiger Pool-Name
} }
} }
private fun runFlyway(dataSource: HikariDataSource) { private fun runFlyway(dataSource: HikariDataSource) {
println("Starte Flyway-Migrationen für Schema: ${dataSource.jdbcUrl}") logger.info("Starting Flyway migrations...")
try { try {
Flyway.configure() val count = Flyway.configure()
.dataSource(dataSource) .dataSource(dataSource)
.locations("classpath:db/migration") .locations("classpath:db/migration")
.load() .load()
.migrate() .migrate()
println("Flyway-Migrationen erfolgreich abgeschlossen.") .migrationsExecuted
logger.info("Flyway migrations completed successfully. Applied $count migrations.")
} catch (e: Exception) { } catch (e: Exception) {
println("FEHLER: Flyway-Migration fehlgeschlagen! Details: ${e.message}") logger.error("Flyway migration failed!", e)
// Wir werfen den Fehler weiter, damit die Anwendung beim Start fehlschlägt.
// Das ist wichtig, um Inkonsistenzen zu vermeiden.
throw IllegalStateException("Flyway migration failed", e) throw IllegalStateException("Flyway migration failed", e)
} }
} }
@@ -3,31 +3,32 @@ package at.mocode.core.utils.discovery
import at.mocode.core.utils.config.AppConfig import at.mocode.core.utils.config.AppConfig
import com.orbitz.consul.Consul import com.orbitz.consul.Consul
import com.orbitz.consul.model.agent.ImmutableRegistration 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 com.orbitz.consul.model.agent.Registration
import java.net.InetAddress import org.slf4j.LoggerFactory
import java.util.* 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( class ServiceRegistration internal constructor(
private val consul: Consul, private val consul: Consul,
private val registration: ImmutableRegistration private val registration: ImmutableRegistration
) { ) {
private companion object {
private val logger = LoggerFactory.getLogger(ServiceRegistration::class.java)
}
private var isRegistered = false private var isRegistered = false
fun register() { fun register() {
if (isRegistered) return if (isRegistered) return
try { try {
// Der `register`-Aufruf ist korrekt, da das `registration`-Objekt
// bereits außerhalb vollständig und korrekt gebaut wurde.
consul.agentClient().register(registration) consul.agentClient().register(registration)
isRegistered = true 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) { } 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) throw IllegalStateException("Could not register service with Consul", e)
} }
} }
@@ -35,63 +36,65 @@ class ServiceRegistration internal constructor(
fun deregister() { fun deregister() {
if (!isRegistered) return if (!isRegistered) return
try { try {
// Der `deregister`-Aufruf ist korrekt. Er erwartet die Service-ID als einfachen String.
consul.agentClient().deregister(registration.id()) consul.agentClient().deregister(registration.id())
isRegistered = false 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) { } 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) { class ServiceRegistrar(private val appConfig: AppConfig) {
private companion object {
private val logger = LoggerFactory.getLogger(ServiceRegistrar::class.java)
}
private val consul: Consul by lazy { private val consul: Consul by lazy {
val consulConfig = appConfig.serviceDiscovery val consulConfig = appConfig.serviceDiscovery
logger.info("Connecting to Consul at {}:{}", consulConfig.consulHost, consulConfig.consulPort)
Consul.builder() Consul.builder()
.withUrl("http://${consulConfig.consulHost}:${consulConfig.consulPort}") .withUrl("http://${consulConfig.consulHost}:${consulConfig.consulPort}")
.build() .build()
} }
/**
* Erstellt und registriert einen Service basierend auf der App-Konfiguration.
* @return Eine ServiceRegistration-Instanz zur Verwaltung des Lebenszyklus.
*/
fun registerCurrentService(): ServiceRegistration { fun registerCurrentService(): ServiceRegistration {
val serviceName = appConfig.appInfo.name val serviceName = appConfig.appInfo.name
val servicePort = appConfig.server.port val servicePort = appConfig.server.port
val serviceId = "$serviceName-${UUID.randomUUID()}" 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( val healthCheck = Registration.RegCheck.http(
"http://$hostAddress:$servicePort/health", // Standard-Health-Check-Pfad "http://$hostAddress:$servicePort/health",
10L, // Intervall in Sekunden 10L,
5L // Timeout in Sekunden 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<String> = listOf("env:${appConfig.environment.name.lowercase()}")
val serviceMeta: Map<String, String> = mapOf("version" to appConfig.appInfo.version)
val registration = ImmutableRegistration.builder() val registration = ImmutableRegistration.builder()
.id(serviceId) .id(serviceId)
.name(serviceName) .name(serviceName)
.address(hostAddress) .address(hostAddress)
.port(servicePort) .port(servicePort)
.check(healthCheck) .check(healthCheck)
.tags(listOf("env:${appConfig.environment.name.lowercase()}")) .tags(serviceTags) // Verwenden der explizit typisierten Variablen
.meta(mapOf("version" to appConfig.appInfo.version)) .meta(serviceMeta) // Verwenden der explizit typisierten Variablen
.build() .build()
val serviceRegistration = ServiceRegistration(consul, registration) val serviceRegistration = ServiceRegistration(consul, registration)
serviceRegistration.register() serviceRegistration.register()
// Fügt einen Shutdown-Hook hinzu, um den Service beim Beenden sauber zu deregistrieren
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(Thread {
println("Shutdown-Hook: Deregistriere Service ${serviceId}...") logger.info("Shutdown hook triggered: Deregistering service '{}'...", serviceId)
serviceRegistration.deregister() serviceRegistration.deregister()
}) })
@@ -4,7 +4,7 @@ import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuidFrom import com.benasher44.uuid.uuidFrom
// KORREKTUR: Der Import wurde von java.math.BigDecimal auf die korrekte Bibliothek geändert. // KORREKTUR: Der Import wurde von java.math.BigDecimal auf die korrekte Bibliothek geändert.
import com.ionspin.kotlin.bignum.decimal.BigDecimal import com.ionspin.kotlin.bignum.decimal.BigDecimal
import kotlinx.datetime.Instant import kotlin.time.Instant
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.LocalTime import kotlinx.datetime.LocalTime
@@ -14,6 +14,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlin.time.ExperimentalTime
object BigDecimalSerializer : KSerializer<BigDecimal> { object BigDecimalSerializer : KSerializer<BigDecimal> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("BigDecimal", PrimitiveKind.STRING)
@@ -27,6 +28,7 @@ object UuidSerializer : KSerializer<Uuid> {
override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString()) override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString())
} }
@OptIn(ExperimentalTime::class)
object KotlinInstantSerializer : KSerializer<Instant> { object KotlinInstantSerializer : KSerializer<Instant> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString()) override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString())
@@ -1,9 +1,10 @@
package at.mocode.core.utils.validation package at.mocode.core.utils.validation
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.Clock import kotlin.time.Clock
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.todayIn import kotlinx.datetime.todayIn
import kotlin.time.ExperimentalTime
/** /**
* Common validation utilities * Common validation utilities
@@ -92,6 +93,7 @@ object ValidationUtils {
/** /**
* Validates birth date * Validates birth date
*/ */
@OptIn(ExperimentalTime::class)
fun validateBirthDate(birthDate: LocalDate?, fieldName: String = "geburtsdatum"): ValidationError? { fun validateBirthDate(birthDate: LocalDate?, fieldName: String = "geburtsdatum"): ValidationError? {
if (birthDate == null) return null if (birthDate == null) return null
@@ -116,6 +118,7 @@ object ValidationUtils {
/** /**
* Validates year value * Validates year value
*/ */
@OptIn(ExperimentalTime::class)
fun validateYear(year: Int?, fieldName: String, minYear: Int = 1900): ValidationError? { fun validateYear(year: Int?, fieldName: String, minYear: Int = 1900): ValidationError? {
if (year == null) return null if (year == null) return null
@@ -2,9 +2,8 @@ package at.mocode.core.utils.database
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import kotlin.test.Ignore import org.junit.*
import kotlin.test.Test import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.test.assertEquals
/** /**
* Comprehensive database connectivity and operations test. * Comprehensive database connectivity and operations test.
+7 -7
View File
@@ -33,9 +33,9 @@ lettuce = "6.3.1.RELEASE"
# --- Service Discovery & Monitoring --- # --- Service Discovery & Monitoring ---
consulClient = "1.5.3" consulClient = "1.5.3"
micrometer = "1.12.2" micrometer = "1.12.2"
micrometerTracing = "1.2.5" # NEU micrometerTracing = "1.2.5"
zipkin = "2.24.4" # NEU (Verwendet eine neuere, kompatible Version) zipkin = "3.0.5"
zipkinReporter = "2.16.4" # NEU zipkinReporter = "2.16.4"
# --- Authentication --- # --- Authentication ---
auth0Jwt = "4.4.0" 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" } consul-client = { module = "com.orbitz.consul:consul-client", version.ref = "consulClient" }
micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "micrometer" } 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 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-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" } # NEU zipkin-sender-okhttp3 = { module = "io.zipkin.reporter2:zipkin-sender-okhttp3", version.ref = "zipkinReporter" }
zipkin-server = { module = "io.zipkin:zipkin-server", version.ref = "zipkin" } # NEU zipkin-server = { module = "io.zipkin:zipkin-server", version.ref = "zipkin" }
zipkin-autoconfigure-ui = { module = "io.zipkin:zipkin-autoconfigure-ui", version.ref = "zipkin" } # NEU zipkin-autoconfigure-ui = { module = "io.zipkin:zipkin-autoconfigure-ui", version.ref = "zipkin" }
# --- Authentication --- # --- Authentication ---
auth0-java-jwt = { module = "com.auth0:java-jwt", version.ref = "auth0Jwt" } auth0-java-jwt = { module = "com.auth0:java-jwt", version.ref = "auth0Jwt" }
+7
View File
@@ -7,6 +7,11 @@ plugins {
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
tasks.getByName("bootJar") {
enabled = false
}
dependencies { dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
api(platform(projects.platform.platformBom)) api(platform(projects.platform.platformBom))
@@ -20,4 +25,6 @@ dependencies {
// Stellt alle Test-Abhängigkeiten gebündelt bereit. // Stellt alle Test-Abhängigkeiten gebündelt bereit.
testImplementation(projects.platform.platformTesting) testImplementation(projects.platform.platformTesting)
testImplementation(libs.bundles.testing.jvm)
testImplementation(libs.kotlin.test)
} }
@@ -2,6 +2,8 @@ package at.mocode.infrastructure.eventstore.redis
import at.mocode.core.domain.event.DomainEvent import at.mocode.core.domain.event.DomainEvent
import at.mocode.infrastructure.eventstore.api.EventSerializer import at.mocode.infrastructure.eventstore.api.EventSerializer
import jakarta.annotation.PostConstruct
import jakarta.annotation.PreDestroy
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.data.domain.Range import org.springframework.data.domain.Range
import org.springframework.data.redis.connection.stream.* 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 org.springframework.scheduling.annotation.Scheduled
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
/** /**
* Consumer for Redis Streams that processes events using consumer groups. * Consumer for Redis Streams that processes events using consumer groups.
@@ -5,6 +5,8 @@ import at.mocode.core.domain.event.DomainEvent
import at.mocode.infrastructure.eventstore.api.EventSerializer import at.mocode.infrastructure.eventstore.api.EventSerializer
import at.mocode.infrastructure.eventstore.api.EventStore import at.mocode.infrastructure.eventstore.api.EventStore
import org.junit.jupiter.api.AfterEach 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.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.data.redis.connection.RedisStandaloneConfiguration import org.springframework.data.redis.connection.RedisStandaloneConfiguration
@@ -18,8 +20,6 @@ import java.time.Instant
import java.util.* import java.util.*
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/** /**
* Integration tests for Redis Event Store. * Integration tests for Redis Event Store.
@@ -285,7 +285,7 @@ class RedisEventStoreIntegrationTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: Int,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
@@ -293,7 +293,7 @@ class RedisEventStoreIntegrationTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: Int,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
} }
@@ -519,7 +519,7 @@ class RedisEventStoreTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: UUID,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
@@ -527,7 +527,7 @@ class RedisEventStoreTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: UUID,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
} }
@@ -231,7 +231,7 @@ class RedisIntegrationTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: UUID,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
@@ -239,7 +239,7 @@ class RedisIntegrationTest {
override val eventId: UUID = UUID.randomUUID(), override val eventId: UUID = UUID.randomUUID(),
override val timestamp: Instant = Instant.now(), override val timestamp: Instant = Instant.now(),
override val aggregateId: UUID, override val aggregateId: UUID,
override val version: Long, override val version: UUID,
val name: String val name: String
) : BaseDomainEvent(eventId, timestamp, aggregateId, version) ) : BaseDomainEvent(eventId, timestamp, aggregateId, version)
} }
@@ -7,6 +7,11 @@ plugins {
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
tasks.getByName("bootJar") {
enabled = false
}
dependencies { dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
implementation(platform(projects.platform.platformBom)) implementation(platform(projects.platform.platformBom))
@@ -7,6 +7,11 @@ plugins {
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
tasks.getByName("bootJar") {
enabled = false
}
dependencies { dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
api(platform(projects.platform.platformBom)) api(platform(projects.platform.platformBom))
@@ -7,6 +7,12 @@ plugins {
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
tasks.getByName("bootJar") {
enabled = false
}
dependencies { dependencies {
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen. // Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
implementation(platform(projects.platform.platformBom)) implementation(platform(projects.platform.platformBom))
@@ -23,9 +23,7 @@ dependencies {
implementation(libs.spring.boot.starter.actuator) implementation(libs.spring.boot.starter.actuator)
// Abhängigkeiten für den Zipkin-Server und seine UI. // 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.server)
implementation(libs.zipkin.autoconfigure.ui)
// Stellt alle Test-Abhängigkeiten gebündelt bereit. // Stellt alle Test-Abhängigkeiten gebündelt bereit.
testImplementation(projects.platform.platformTesting) testImplementation(projects.platform.platformTesting)
+7 -5
View File
@@ -1,4 +1,5 @@
/*plugins { /*
plugins {
alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.multiplatform)
} }
@@ -34,7 +35,9 @@ kotlin {
} }
} }
} }
}*/ }
*/
// Dieses Modul bündelt alle für JVM-Tests notwendigen Abhängigkeiten. // Dieses Modul bündelt alle für JVM-Tests notwendigen Abhängigkeiten.
// Jedes Modul, das Tests enthält, sollte dieses Modul mit `testImplementation` einbinden. // 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. // Importiert die zentrale BOM für konsistente Versionen.
api(platform(projects.platform.platformBom)) api(platform(projects.platform.platformBom))
// OPTIMIERUNG: Verwendung von Bundles, um die Konfiguration zu vereinfachen.
// Diese Bundles sind in `libs.versions.toml` definiert. // Diese Bundles sind in `libs.versions.toml` definiert.
api(libs.bundles.testing.jvm) api(libs.bundles.testing.jvm)
api(libs.bundles.testcontainers) 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.spring.boot.starter.test)
api(libs.h2.driver) // H2 wird oft für In-Memory-Tests benötigt. api(libs.h2.driver)
} }