Umbau zu SCS

This commit is contained in:
stefan
2025-07-17 15:17:31 +02:00
parent 67c52f7381
commit 029b0c86bc
255 changed files with 6458 additions and 26663 deletions
+49
View File
@@ -0,0 +1,49 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
}
kotlin {
jvm {
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
mainRun {
mainClass.set("at.mocode.gateway.ApplicationKt")
}
}
sourceSets {
commonMain.dependencies {
implementation(project(":shared-kernel"))
implementation(project(":master-data"))
implementation(project(":member-management"))
implementation(project(":horse-registry"))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime)
implementation(libs.uuid)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutines.test)
}
jvmMain.dependencies {
implementation(libs.ktor.server.core)
implementation(libs.ktor.server.netty)
implementation(libs.ktor.server.contentNegotiation)
implementation(libs.ktor.server.cors)
implementation(libs.ktor.server.auth)
implementation(libs.ktor.server.authJwt)
implementation(libs.ktor.server.callLogging)
implementation(libs.ktor.server.statusPages)
implementation(libs.ktor.server.serializationKotlinxJson)
implementation(libs.logback)
}
jvmTest.dependencies {
implementation(libs.ktor.server.tests)
}
}
}
@@ -0,0 +1,42 @@
package at.mocode.gateway
import at.mocode.gateway.config.configureDatabase
import at.mocode.gateway.config.configureSerialization
import at.mocode.gateway.config.configureMonitoring
import at.mocode.gateway.config.configureSecurity
import at.mocode.gateway.routing.configureRouting
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
/**
* Main application entry point for the Self-Contained Systems API Gateway.
*
* This gateway aggregates all bounded context APIs into a unified interface
* while maintaining the independence of each context.
*/
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
/**
* Main application module configuration.
*
* Configures all necessary components for the API Gateway including:
* - Database connections for all contexts
* - Serialization and content negotiation
* - Security and authentication
* - Monitoring and logging
* - Route aggregation from all bounded contexts
*/
fun Application.module() {
// Configure core components
configureDatabase()
configureSerialization()
configureMonitoring()
configureSecurity()
// Configure routing - aggregates all bounded context routes
configureRouting()
}
@@ -0,0 +1,55 @@
package at.mocode.gateway.config
import io.ktor.server.application.*
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
/**
* Database configuration for the API Gateway.
*
* Sets up database connections and schema initialization for all bounded contexts.
*/
fun Application.configureDatabase() {
val databaseUrl = environment.config.propertyOrNull("database.url")?.getString()
?: "jdbc:postgresql://localhost:5432/meldestelle"
val databaseUser = environment.config.propertyOrNull("database.user")?.getString()
?: "meldestelle_user"
val databasePassword = environment.config.propertyOrNull("database.password")?.getString()
?: "meldestelle_password"
// Initialize database connection
Database.connect(
url = databaseUrl,
driver = "org.postgresql.Driver",
user = databaseUser,
password = databasePassword
)
// Initialize database schemas for all contexts
transaction {
// Import table definitions from all contexts
try {
// Master Data Context tables
SchemaUtils.createMissingTablesAndColumns(
at.mocode.masterdata.infrastructure.repository.LandTable
)
// Member Management Context tables
SchemaUtils.createMissingTablesAndColumns(
at.mocode.members.infrastructure.repository.PersonTable,
at.mocode.members.infrastructure.repository.VereinTable
)
// Horse Registry Context tables
SchemaUtils.createMissingTablesAndColumns(
at.mocode.horses.infrastructure.repository.HorseTable
)
log.info("Database schemas initialized successfully")
} catch (e: Exception) {
log.error("Failed to initialize database schemas: ${e.message}")
// In production, you might want to fail fast here
}
}
}
@@ -0,0 +1,59 @@
package at.mocode.gateway.config
import io.ktor.server.application.*
import io.ktor.server.plugins.calllogging.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.request.*
import io.ktor.http.*
import io.ktor.server.response.*
import at.mocode.dto.base.BaseDto
import org.slf4j.event.Level
/**
* Monitoring and logging configuration for the API Gateway.
*
* Configures request logging, error handling, and status pages.
*/
fun Application.configureMonitoring() {
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/api") }
format { call ->
val status = call.response.status()
val httpMethod = call.request.httpMethod.value
val userAgent = call.request.headers["User-Agent"]
"$status: $httpMethod ${call.request.path()} - $userAgent"
}
}
install(StatusPages) {
exception<Throwable> { call, cause ->
call.application.log.error("Unhandled exception", cause)
call.respond(
HttpStatusCode.InternalServerError,
BaseDto.error<Any>("Internal server error: ${cause.message}")
)
}
status(HttpStatusCode.NotFound) { call, status ->
call.respond(
status,
BaseDto.error<Any>("Endpoint not found: ${call.request.path()}")
)
}
status(HttpStatusCode.Unauthorized) { call, status ->
call.respond(
status,
BaseDto.error<Any>("Authentication required")
)
}
status(HttpStatusCode.Forbidden) { call, status ->
call.respond(
status,
BaseDto.error<Any>("Access forbidden")
)
}
}
}
@@ -0,0 +1,38 @@
package at.mocode.gateway.config
import io.ktor.server.application.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.http.*
/**
* Security configuration for the API Gateway.
*
* Configures CORS, authentication, and other security-related settings.
*/
fun Application.configureSecurity() {
install(CORS) {
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
allowMethod(HttpMethod.Patch)
allowHeader(HttpHeaders.Authorization)
allowHeader(HttpHeaders.ContentType)
allowHeader("X-Requested-With")
// Allow requests from common development origins
allowHost("localhost:3000")
allowHost("localhost:8080")
allowHost("127.0.0.1:3000")
allowHost("127.0.0.1:8080")
// In production, configure specific allowed origins
anyHost() // This should be restricted in production
}
// TODO: Add JWT authentication configuration
// install(Authentication) {
// jwt("auth-jwt") {
// // JWT configuration
// }
// }
}
@@ -0,0 +1,23 @@
package at.mocode.gateway.config
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
/**
* Serialization configuration for the API Gateway.
*
* Configures JSON serialization settings that are consistent across all bounded contexts.
*/
fun Application.configureSerialization() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
encodeDefaults = true
explicitNulls = false
})
}
}
@@ -0,0 +1,151 @@
package at.mocode.gateway.routing
import at.mocode.dto.base.BaseDto
import at.mocode.horses.infrastructure.api.HorseController
import at.mocode.horses.infrastructure.repository.HorseRepositoryImpl
import at.mocode.masterdata.application.usecase.CreateCountryUseCase
import at.mocode.masterdata.application.usecase.GetCountryUseCase
import at.mocode.masterdata.infrastructure.api.CountryController
import at.mocode.masterdata.infrastructure.repository.LandRepositoryImpl
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
/**
* Main routing configuration for the API Gateway.
*
* This aggregates routes from all bounded contexts into a unified API
* while maintaining the independence and self-contained nature of each context.
*/
fun Application.configureRouting() {
// Initialize repository implementations for each context
val landRepository = LandRepositoryImpl()
val horseRepository = HorseRepositoryImpl()
// Initialize use cases
val getCountryUseCase = GetCountryUseCase(landRepository)
val createCountryUseCase = CreateCountryUseCase(landRepository)
// Initialize controllers for each bounded context
val countryController = CountryController(getCountryUseCase, createCountryUseCase)
val horseController = HorseController(horseRepository)
routing {
// Root endpoint - API Gateway health check and info
get("/") {
call.respond(HttpStatusCode.OK, BaseDto.success(
ApiGatewayInfo(
name = "Meldestelle API Gateway",
version = "1.0.0",
description = "Self-Contained Systems API Gateway for Austrian Equestrian Federation",
availableContexts = listOf(
"master-data",
"horse-registry"
),
endpoints = mapOf(
"master-data" to "/api/masterdata/*",
"horse-registry" to "/api/horses/*"
)
)
))
}
// Health check endpoint
get("/health") {
call.respond(HttpStatusCode.OK, BaseDto.success(
HealthStatus(
status = "UP",
contexts = mapOf(
"master-data" to "UP",
"horse-registry" to "UP"
)
)
))
}
// API documentation endpoint
get("/api") {
call.respond(HttpStatusCode.OK, BaseDto.success(
ApiDocumentation(
title = "Meldestelle Self-Contained Systems API",
description = "Unified API Gateway for all bounded contexts",
contexts = listOf(
ContextInfo(
name = "Master Data Context",
path = "/api/masterdata",
description = "Reference data management (countries, states, age classes, venues)"
),
ContextInfo(
name = "Horse Registry Context",
path = "/api/horses",
description = "Horse registration, ownership, and pedigree management"
)
)
)
))
}
// Configure routes for each bounded context
// Master Data Context Routes
countryController.configureRoutes(this)
// Horse Registry Context Routes
horseController.configureRoutes(this)
// Catch-all for undefined routes
route("{...}") {
handle {
call.respond(
HttpStatusCode.NotFound,
BaseDto.error<Any>("Endpoint not found. Check /api for available endpoints.")
)
}
}
}
}
/**
* API Gateway information DTO.
*/
@Serializable
data class ApiGatewayInfo(
val name: String,
val version: String,
val description: String,
val availableContexts: List<String>,
val endpoints: Map<String, String>
)
/**
* Health status DTO.
*/
@Serializable
data class HealthStatus(
val status: String,
val contexts: Map<String, String>
)
/**
* API documentation DTO.
*/
@Serializable
data class ApiDocumentation(
val title: String,
val description: String,
val contexts: List<ContextInfo>
)
/**
* Context information DTO.
*/
@Serializable
data class ContextInfo(
val name: String,
val path: String,
val description: String
)