diff --git a/.env b/.env index 22a23974..5c097ebb 100644 --- a/.env +++ b/.env @@ -34,14 +34,14 @@ PGADMIN_EMAIL=user@domain.com PGADMIN_PASSWORD=strong-password PGADMIN_PORT=8888:80 +# --- PROMETHEUS (Metriken) --- +PROMETHEUS_PORT=9090:9090 + # --- GRAFANA (Monitoring GUI) --- GF_ADMIN_USER=gf-admin GF_ADMIN_PASSWORD=gf-password GF_PORT=3000:3000 -# --- PROMETHEUS (Metriken) --- -PROMETHEUS_PORT=9090:9090 - # --- SERVICE DISCOVERY (Consul) --- CONSUL_PORT=8500:8500 @@ -50,3 +50,6 @@ CONSUL_PORT=8500:8500 GATEWAY_PORT=8081 # Debug Port für IntelliJ (Remote JVM Debug) GATEWAY_DEBUG_PORT=5005 + +# --- MICROSERVICES --- +PING_SERVICE_PORT=8082:8082 diff --git a/compose.yaml b/compose.yaml index ce75184d..f07807a4 100644 --- a/compose.yaml +++ b/compose.yaml @@ -202,6 +202,48 @@ services: networks: - meldestelle-network + # ========================================== + # MICROSERVICES + # ========================================== + ping-service: + build: + context: . + dockerfile: dockerfiles/services/ping-service/Dockerfile + args: + GRADLE_VERSION: 9.1.0 + JAVA_VERSION: 21 + VERSION: 1.0.0 + BUILD_DATE: "2025-11-20" + container_name: ${COMPOSE_PROJECT_NAME}-ping-service + restart: no # "${RESTART_POLICY:-unless-stopped}" + ports: + - "${PING_SERVICE_PORT}" + - "5006:5005" # Debug Port + environment: + SPRING_PROFILES_ACTIVE: docker + DEBUG: "true" + SERVER_PORT: 8082 + + # --- CONSUL --- + SPRING_CLOUD_CONSUL_HOST: consul + SPRING_CLOUD_CONSUL_PORT: 8500 + SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: ping-service + + # --- DATENBANK VERBINDUNG (Das hat gefehlt!) --- + # Wir nutzen die Container-Namen aus deiner .env Variable + SPRING_DATASOURCE_URL: jdbc:postgresql://${COMPOSE_PROJECT_NAME}-postgres:5432/${POSTGRES_DB} + SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER} + SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD} + # WICHTIG: Wir wollen nur validieren, nichts erstellen. + SPRING_JPA_HIBERNATE_DDL_AUTO: validate + + # --- REDIS (DAS HAT GEFEHLT!) --- + # Wir nutzen den Service-Namen, genau wie bei Postgres + SPRING_DATA_REDIS_HOST: ${COMPOSE_PROJECT_NAME}-redis + SPRING_DATA_REDIS_PORT: 6379 + networks: + - meldestelle-network + volumes: postgres-data: pgadmin-data: diff --git a/infrastructure/gateway/build.gradle.kts b/infrastructure/gateway/build.gradle.kts index 1761af9d..1ff18a52 100644 --- a/infrastructure/gateway/build.gradle.kts +++ b/infrastructure/gateway/build.gradle.kts @@ -1,73 +1,76 @@ // Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt // für alle externen Anfragen an das Meldestelle-System. plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSpring) - alias(libs.plugins.kotlinJpa) - alias(libs.plugins.spring.boot) - alias(libs.plugins.spring.dependencyManagement) + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSpring) + alias(libs.plugins.kotlinJpa) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencyManagement) } // Konfiguriert die Hauptklasse für das ausführbare JAR springBoot { - mainClass.set("at.mocode.infrastructure.gateway.GatewayApplicationKt") + mainClass.set("at.mocode.infrastructure.gateway.GatewayApplicationKt") } dependencies { - implementation(platform(projects.platform.platformBom)) + implementation(platform(projects.platform.platformBom)) - // === Core Dependencies === - implementation(projects.core.coreUtils) - implementation(projects.platform.platformDependencies) - implementation(projects.infrastructure.monitoring.monitoringClient) + // === Core Dependencies === + implementation(projects.core.coreUtils) + implementation(projects.platform.platformDependencies) + implementation(projects.infrastructure.monitoring.monitoringClient) - // === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN === - implementation(libs.bundles.spring.cloud.gateway) - implementation(libs.bundles.spring.boot.security) - implementation(libs.bundles.resilience) - implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j") - implementation(libs.spring.boot.starter.actuator) // Wichtig für Health & Metrics - implementation(libs.bundles.logging) - implementation(libs.bundles.jackson.kotlin) + // === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN === + implementation(libs.bundles.spring.cloud.gateway) + implementation(libs.bundles.spring.boot.security) + implementation(libs.bundles.resilience) + implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j") + implementation(libs.spring.boot.starter.actuator) // Wichtig für Health & Metrics + implementation(libs.bundles.logging) + implementation(libs.bundles.jackson.kotlin) - // === Test Dependencies === - testImplementation(projects.platform.platformTesting) - testImplementation(libs.bundles.testing.jvm) + // WICHTIG: PostgreSQL Treiber hinzufügen! + implementation(libs.postgresql.driver) + + // === Test Dependencies === + testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) } tasks.test { - useJUnitPlatform() + useJUnitPlatform() } // Konfiguration für Integration Tests sourceSets { - val integrationTest by creating { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - } + val integrationTest by creating { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + } } val integrationTestImplementation by configurations.getting { - extendsFrom(configurations.testImplementation.get()) + extendsFrom(configurations.testImplementation.get()) } tasks.register("integrationTest") { - description = "Führt die Integration Tests aus" - group = "verification" + description = "Führt die Integration Tests aus" + group = "verification" - testClassesDirs = sourceSets["integrationTest"].output.classesDirs - classpath = sourceSets["integrationTest"].runtimeClasspath + testClassesDirs = sourceSets["integrationTest"].output.classesDirs + classpath = sourceSets["integrationTest"].runtimeClasspath - useJUnitPlatform() + useJUnitPlatform() - shouldRunAfter("test") + shouldRunAfter("test") - testLogging { - events("passed", "skipped", "failed") - showStandardStreams = false - showExceptions = true - showCauses = true - showStackTraces = true - exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL - } + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = false + showExceptions = true + showCauses = true + showStackTraces = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } } diff --git a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt index 3a7be7ff..76b6b485 100644 --- a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt +++ b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/health/GatewayHealthIndicator.kt @@ -17,126 +17,126 @@ import java.time.Duration */ @Component class GatewayHealthIndicator( - private val discoveryClient: DiscoveryClient, - private val webClient: WebClient.Builder, - private val environment: Environment + private val discoveryClient: DiscoveryClient, + private val webClient: WebClient.Builder, + private val environment: Environment ) : HealthIndicator { - companion object { - private val CRITICAL_SERVICES = setOf( - "members-service", - "horses-service", - "events-service", - "masterdata-service", - "auth-service" + companion object { + private val CRITICAL_SERVICES = setOf( + "ping-service" + ) + + private val OPTIONAL_SERVICES = setOf( + "members-service", + "horses-service", + "events-service", + "masterdata-service", + "auth-service" + ) + + private val HEALTH_CHECK_TIMEOUT = Duration.ofSeconds(5) + } + + override fun health(): Health { + val builder = Health.up() + val details = mutableMapOf() + + try { + // Prüfe alle registrierten Services in Consul + val allServices = discoveryClient.services + val discoveredServices = mutableMapOf() + + allServices.forEach { serviceName -> + val instances = discoveryClient.getInstances(serviceName) + discoveredServices[serviceName] = mapOf( + "instanceCount" to instances.size, + "instances" to instances.map { "${it.host}:${it.port}" } ) + } - private val OPTIONAL_SERVICES = setOf( - "ping-service" - ) + details["discoveredServices"] = discoveredServices + details["totalServices"] = allServices.size - private val HEALTH_CHECK_TIMEOUT = Duration.ofSeconds(5) - } + // Prüfe kritische Services + val criticalServiceStatus = mutableMapOf() + var hasCriticalFailure = false - override fun health(): Health { - val builder = Health.up() - val details = mutableMapOf() - - try { - // Prüfe alle registrierten Services in Consul - val allServices = discoveryClient.services - val discoveredServices = mutableMapOf() - - allServices.forEach { serviceName -> - val instances = discoveryClient.getInstances(serviceName) - discoveredServices[serviceName] = mapOf( - "instanceCount" to instances.size, - "instances" to instances.map { "${it.host}:${it.port}" } - ) - } - - details["discoveredServices"] = discoveredServices - details["totalServices"] = allServices.size - - // Prüfe kritische Services - val criticalServiceStatus = mutableMapOf() - var hasCriticalFailure = false - - CRITICAL_SERVICES.forEach { serviceName -> - val status = checkServiceHealth(serviceName) - criticalServiceStatus[serviceName] = status - if (status != "UP") { - hasCriticalFailure = true - } - } - - // Prüfe optionale Services - val optionalServiceStatus = mutableMapOf() - OPTIONAL_SERVICES.forEach { serviceName -> - optionalServiceStatus[serviceName] = checkServiceHealth(serviceName) - } - - details["criticalServices"] = criticalServiceStatus - details["optionalServices"] = optionalServiceStatus - - // Gateway Status basierend auf kritischen Services - val isTestEnvironment = environment.activeProfiles.contains("test") - val isDevEnvironment = environment.activeProfiles.contains("dev") - - if (hasCriticalFailure && !isTestEnvironment && !isDevEnvironment) { - builder.down() - details["status"] = "DOWN" - details["reason"] = "Ein oder mehrere kritische Services sind nicht verfügbar" - } else { - details["status"] = "UP" - details["reason"] = when { - isTestEnvironment -> "Gesundheitsprüfung erfolgreich (Testumgebung)" - isDevEnvironment -> "Gesundheitsprüfung erfolgreich (Entwicklungsumgebung - nicht alle Services erforderlich)" - else -> "Alle kritischen Services sind verfügbar" - } - } - - } catch (exception: Exception) { - builder.down() - .withException(exception) - details["status"] = "DOWN" - details["reason"] = "Fehler beim Prüfen der nachgelagerten Services: ${exception.message}" + CRITICAL_SERVICES.forEach { serviceName -> + val status = checkServiceHealth(serviceName) + criticalServiceStatus[serviceName] = status + if (status != "UP") { + hasCriticalFailure = true } + } - return builder.withDetails(details).build() - } + // Prüfe optionale Services + val optionalServiceStatus = mutableMapOf() + OPTIONAL_SERVICES.forEach { serviceName -> + optionalServiceStatus[serviceName] = checkServiceHealth(serviceName) + } - private fun checkServiceHealth(serviceName: String): String { - return try { - val instances = discoveryClient.getInstances(serviceName) + details["criticalServices"] = criticalServiceStatus + details["optionalServices"] = optionalServiceStatus - if (instances.isEmpty()) { - "NO_INSTANCES" - } else { - // Versuche Health-Check für die erste verfügbare Instanz - val instance = instances.first() - val healthUrl = "http://${instance.host}:${instance.port}/actuator/health" + // Gateway Status basierend auf kritischen Services + val isTestEnvironment = environment.activeProfiles.contains("test") + val isDevEnvironment = environment.activeProfiles.contains("dev") - val client = webClient.build() - val response = client.get() - .uri(healthUrl) - .retrieve() - .bodyToMono(Map::class.java) - .timeout(HEALTH_CHECK_TIMEOUT) - .onErrorReturn(mapOf("status" to "DOWN")) - .block() - - val status = response?.get("status")?.toString() ?: "UNKNOWN" - if (status == "UP") "UP" else "DOWN" - } - } catch (exception: WebClientResponseException) { - when (exception.statusCode.value()) { - 404 -> "NO_HEALTH_ENDPOINT" - 503 -> "DOWN" - else -> "ERROR" - } - } catch (_: Exception) { - "ERROR" + if (hasCriticalFailure && !isTestEnvironment && !isDevEnvironment) { + builder.down() + details["status"] = "DOWN" + details["reason"] = "Ein oder mehrere kritische Services sind nicht verfügbar" + } else { + details["status"] = "UP" + details["reason"] = when { + isTestEnvironment -> "Gesundheitsprüfung erfolgreich (Testumgebung)" + isDevEnvironment -> "Gesundheitsprüfung erfolgreich (Entwicklungsumgebung - nicht alle Services erforderlich)" + else -> "Alle kritischen Services sind verfügbar" } + } + + } catch (exception: Exception) { + builder.down() + .withException(exception) + details["status"] = "DOWN" + details["reason"] = "Fehler beim Prüfen der nachgelagerten Services: ${exception.message}" } + + return builder.withDetails(details).build() + } + + private fun checkServiceHealth(serviceName: String): String { + return try { + val instances = discoveryClient.getInstances(serviceName) + + if (instances.isEmpty()) { + "NO_INSTANCES" + } else { + // Versuche Health-Check für die erste verfügbare Instanz + val instance = instances.first() + val healthUrl = "http://${instance.host}:${instance.port}/actuator/health" + + val client = webClient.build() + val response = client.get() + .uri(healthUrl) + .retrieve() + .bodyToMono(Map::class.java) + .timeout(HEALTH_CHECK_TIMEOUT) + .onErrorReturn(mapOf("status" to "DOWN")) + .block() + + val status = response?.get("status")?.toString() ?: "UNKNOWN" + if (status == "UP") "UP" else "DOWN" + } + } catch (exception: WebClientResponseException) { + when (exception.statusCode.value()) { + 404 -> "NO_HEALTH_ENDPOINT" + 503 -> "DOWN" + else -> "ERROR" + } + } catch (_: Exception) { + "ERROR" + } + } } diff --git a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt index 24ab7580..9e81568a 100644 --- a/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt +++ b/infrastructure/gateway/src/main/kotlin/at/mocode/infrastructure/gateway/security/SecurityConfig.kt @@ -21,96 +21,96 @@ import java.time.Duration @EnableWebFluxSecurity @EnableConfigurationProperties(GatewaySecurityProperties::class) class SecurityConfig( - private val securityProperties: GatewaySecurityProperties + private val securityProperties: GatewaySecurityProperties ) { - /** - * Konfiguriert die zentrale Security-Filter-Kette für das Gateway. - * - * Diese Konfiguration nutzt den Standard-OAuth2-Resource-Server von Spring Security, - * um JWTs (z.B. von Keycloak) automatisch zu validieren. - */ - @Bean - fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { - return http { // Start der modernen Kotlin-DSL - // 1. CORS-Konfiguration anwenden - cors { } + /** + * Konfiguriert die zentrale Security-Filter-Kette für das Gateway. + * + * Diese Konfiguration nutzt den Standard-OAuth2-Resource-Server von Spring Security, + * um JWTs (z.B. von Keycloak) automatisch zu validieren. + */ + @Bean + fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http { // Start der modernen Kotlin-DSL + // 1. CORS-Konfiguration anwenden + cors { } - // 2. CSRF deaktivieren (für zustandslose APIs) - csrf { disable() } + // 2. CSRF deaktivieren (für zustandslose APIs) + csrf { disable() } - // 3. Routen-Berechtigungen definieren - authorizeExchange { - // Öffentlich zugängliche Pfade aus der .yml-Datei laden - authorize( - pathMatchers(*securityProperties.publicPaths.toTypedArray()), - permitAll - ) - // Alle anderen Pfade erfordern eine Authentifizierung - authorize(anyExchange, authenticated) - } + // 3. Routen-Berechtigungen definieren + authorizeExchange { + // Öffentlich zugängliche Pfade aus der .yml-Datei laden + authorize( + pathMatchers(*securityProperties.publicPaths.toTypedArray()), + permitAll + ) + // Alle anderen Pfade erfordern eine Authentifizierung + authorize(anyExchange, authenticated) + } - // 4. JWT-Validierung via Keycloak aktivieren - oauth2ResourceServer { - jwt { } - } - } + // 4. JWT-Validierung via Keycloak aktivieren + oauth2ResourceServer { + jwt { } + } + } + } + + /** + * Erstellt einen ReactiveJwtDecoder für die JWT-Validierung. + * + * Verwendet die JWK Set URI aus der Konfiguration, um die öffentlichen Schlüssel + * von Keycloak zu laden. Falls die URI nicht konfiguriert ist oder Keycloak + * nicht erreichbar ist, wird trotzdem ein Bean erstellt, um Startfehler zu vermeiden. + */ + @Bean + fun reactiveJwtDecoder( + @Value($$"${spring.security.oauth2.resourceserver.jwt.jwk-set-uri:}") jwkSetUri: String + ): ReactiveJwtDecoder { + return if (jwkSetUri.isNotBlank()) { + try { + NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build() + } catch (e: Exception) { + // Log warning and return a no-op decoder to allow startup + println("WARN: Failed to configure JWT decoder with JWK Set URI: $jwkSetUri - ${e.message}") + println("WARN: JWT authentication will not work until Keycloak is available") + createNoOpJwtDecoder() + } + } else { + println("INFO: No JWK Set URI configured, using no-op JWT decoder") + createNoOpJwtDecoder() + } + } + + /** + * Erstellt einen No-Op JWT Decoder für Fälle, in denen Keycloak nicht verfügbar ist. + * Dieser Decoder lehnt alle Token ab, erlaubt aber den Anwendungsstart. + */ + private fun createNoOpJwtDecoder(): ReactiveJwtDecoder { + return ReactiveJwtDecoder { token -> + throw IllegalStateException("JWT validation is not available - Keycloak may not be running") + } + } + + /** + * Definiert die zentrale und einzige CORS-Konfiguration für das Gateway. + */ + @Bean + fun corsConfigurationSource(): CorsConfigurationSource { + val configuration = CorsConfiguration().apply { + allowedOriginPatterns = securityProperties.cors.allowedOriginPatterns.toList() + allowedMethods = securityProperties.cors.allowedMethods.toList() + allowedHeaders = securityProperties.cors.allowedHeaders.toList() + exposedHeaders = securityProperties.cors.exposedHeaders.toList() + allowCredentials = securityProperties.cors.allowCredentials + maxAge = securityProperties.cors.maxAge.seconds } - /** - * Erstellt einen ReactiveJwtDecoder für die JWT-Validierung. - * - * Verwendet die JWK Set URI aus der Konfiguration, um die öffentlichen Schlüssel - * von Keycloak zu laden. Falls die URI nicht konfiguriert ist oder Keycloak - * nicht erreichbar ist, wird trotzdem ein Bean erstellt, um Startfehler zu vermeiden. - */ - @Bean - fun reactiveJwtDecoder( - @Value($$"${spring.security.oauth2.resourceserver.jwt.jwk-set-uri:}") jwkSetUri: String - ): ReactiveJwtDecoder { - return if (jwkSetUri.isNotBlank()) { - try { - NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build() - } catch (e: Exception) { - // Log warning and return a no-op decoder to allow startup - println("WARN: Failed to configure JWT decoder with JWK Set URI: $jwkSetUri - ${e.message}") - println("WARN: JWT authentication will not work until Keycloak is available") - createNoOpJwtDecoder() - } - } else { - println("INFO: No JWK Set URI configured, using no-op JWT decoder") - createNoOpJwtDecoder() - } - } - - /** - * Erstellt einen No-Op JWT Decoder für Fälle, in denen Keycloak nicht verfügbar ist. - * Dieser Decoder lehnt alle Token ab, erlaubt aber den Anwendungsstart. - */ - private fun createNoOpJwtDecoder(): ReactiveJwtDecoder { - return ReactiveJwtDecoder { token -> - throw IllegalStateException("JWT validation is not available - Keycloak may not be running") - } - } - - /** - * Definiert die zentrale und einzige CORS-Konfiguration für das Gateway. - */ - @Bean - fun corsConfigurationSource(): CorsConfigurationSource { - val configuration = CorsConfiguration().apply { - allowedOriginPatterns = securityProperties.cors.allowedOriginPatterns.toList() - allowedMethods = securityProperties.cors.allowedMethods.toList() - allowedHeaders = securityProperties.cors.allowedHeaders.toList() - exposedHeaders = securityProperties.cors.exposedHeaders.toList() - allowCredentials = securityProperties.cors.allowCredentials - maxAge = securityProperties.cors.maxAge.seconds - } - - return UrlBasedCorsConfigurationSource().apply { - registerCorsConfiguration("/**", configuration) - } + return UrlBasedCorsConfigurationSource().apply { + registerCorsConfiguration("/**", configuration) } + } } /** @@ -118,25 +118,26 @@ class SecurityConfig( */ @ConfigurationProperties(prefix = "gateway.security") data class GatewaySecurityProperties( - val cors: CorsProperties = CorsProperties(), - val publicPaths: List = listOf( - "/", - "/fallback/**", - "/actuator/**", - "/webjars/**", - "/v3/api-docs/**", - "/api/auth/**" // Alle Auth-Endpunkte - ) + val cors: CorsProperties = CorsProperties(), + val publicPaths: List = listOf( + "/", + "/fallback/**", + "/actuator/**", + "/webjars/**", + "/v3/api-docs/**", + "/api/auth/**", // Alle Auth-Endpunkte + "/api/ping/**" + ) ) /** * DTO für CORS-Properties mit sinnvollen Standardwerten. */ data class CorsProperties( - val allowedOriginPatterns: Set = setOf("http://localhost:[*]", "https://*.meldestelle.at"), - val allowedMethods: Set = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"), - val allowedHeaders: Set = setOf("*"), - val exposedHeaders: Set = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"), - val allowCredentials: Boolean = true, - val maxAge: Duration = Duration.ofHours(1) + val allowedOriginPatterns: Set = setOf("http://localhost:[*]", "https://*.meldestelle.at"), + val allowedMethods: Set = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"), + val allowedHeaders: Set = setOf("*"), + val exposedHeaders: Set = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"), + val allowCredentials: Boolean = true, + val maxAge: Duration = Duration.ofHours(1) ) diff --git a/infrastructure/gateway/src/main/resources/application.yml b/infrastructure/gateway/src/main/resources/application.yml index 5ac0d250..38890930 100644 --- a/infrastructure/gateway/src/main/resources/application.yml +++ b/infrastructure/gateway/src/main/resources/application.yml @@ -73,6 +73,10 @@ spring: name: Cache-Control value: no-cache, no-store, must-revalidate routes: + +# ============================================================== +# --- Gateway-Info-Route (optional) --- +# ============================================================== - id: gateway-info-route uri: http://localhost:${server.port} predicates: @@ -81,56 +85,80 @@ spring: filters: - SetStatus=200 - SetResponseHeader=Content-Type,application/json - - id: members-service-route - uri: lb://members-service - predicates: - - Path=/api/members/** - filters: - - StripPrefix=1 - - name: CircuitBreaker - args: - name: membersCircuitBreaker - fallbackUri: forward:/fallback/members - - id: horses-service-route - uri: lb://horses-service - predicates: - - Path=/api/horses/** - filters: - - StripPrefix=1 - - name: CircuitBreaker - args: - name: horsesCircuitBreaker - fallbackUri: forward:/fallback/horses - - id: events-service-route - uri: lb://events-service - predicates: - - Path=/api/events/** - filters: - - StripPrefix=1 - - name: CircuitBreaker - args: - name: eventsCircuitBreaker - fallbackUri: forward:/fallback/events - - id: masterdata-service-route - uri: lb://masterdata-service - predicates: - - Path=/api/masterdata/** - filters: - - StripPrefix=1 - - name: CircuitBreaker - args: - name: masterdataCircuitBreaker - fallbackUri: forward:/fallback/masterdata - - id: auth-service-route - uri: lb://auth-service - predicates: - - Path=/api/auth/** - filters: - - StripPrefix=1 - - name: CircuitBreaker - args: - name: authCircuitBreaker - fallbackUri: forward:/fallback/auth + +# ============================================================== +# --- Members-Service-Integration (optional) --- +# ============================================================== +# - id: members-service-route +# uri: lb://members-service +# predicates: +# - Path=/api/members/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# name: membersCircuitBreaker +# fallbackUri: forward:/fallback/members + +# ============================================================== +# --- Horses-Service-Integration (optional) --- +# ============================================================== +# - id: horses-service-route +# uri: lb://horses-service +# predicates: +# - Path=/api/horses/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# name: horsesCircuitBreaker +# fallbackUri: forward:/fallback/horses + +# ============================================================== +# --- Events-Service-Integration (optional) --- +# ============================================================== +# - id: events-service-route +# uri: lb://events-service +# predicates: +# - Path=/api/events/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# name: eventsCircuitBreaker +# fallbackUri: forward:/fallback/events + +# ============================================================== +# --- Masterdata-Service-Integration (optional) --- +# ============================================================== +# - id: masterdata-service-route +# uri: lb://masterdata-service +# predicates: +# - Path=/api/masterdata/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# name: masterdataCircuitBreaker +# fallbackUri: forward:/fallback/masterdata + +# ============================================================== +# --- Auth-Service-Integration (optional) --- +# ============================================================== +# - id: auth-service-route +# uri: lb://auth-service +# predicates: +# - Path=/api/auth/** +# filters: +# - StripPrefix=1 +# - name: CircuitBreaker +# args: +# name: authCircuitBreaker +# fallbackUri: forward:/fallback/auth + +# ============================================================== +# --- Ping-Service-Integration (optional) --- +# ============================================================== - id: ping-service-route uri: lb://ping-service predicates: diff --git a/services/ping/ping-service/build.gradle.kts b/services/ping/ping-service/build.gradle.kts index 904e1700..8d1489e0 100644 --- a/services/ping/ping-service/build.gradle.kts +++ b/services/ping/ping-service/build.gradle.kts @@ -1,54 +1,60 @@ // Optimized Spring Boot ping service for testing microservice architecture // This service demonstrates circuit breaker patterns, service discovery, and monitoring plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.kotlinSpring) - alias(libs.plugins.kotlinJpa) - alias(libs.plugins.spring.boot) - alias(libs.plugins.spring.dependencyManagement) + alias(libs.plugins.kotlinJvm) + alias(libs.plugins.kotlinSpring) + alias(libs.plugins.kotlinJpa) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependencyManagement) } // Configure the main class for the executable JAR springBoot { - mainClass.set("at.mocode.ping.service.PingServiceApplicationKt") + mainClass.set("at.mocode.ping.service.PingServiceApplicationKt") } dependencies { - // Platform BOM für zentrale Versionsverwaltung - implementation(platform(projects.platform.platformBom)) + // Platform BOM für zentrale Versionsverwaltung + implementation(platform(projects.platform.platformBom)) - // Platform und Core Dependencies - implementation(projects.platform.platformDependencies) - implementation(projects.services.ping.pingApi) - implementation(projects.infrastructure.monitoring.monitoringClient) + // Platform und Core Dependencies + implementation(projects.platform.platformDependencies) + implementation(projects.services.ping.pingApi) + implementation(projects.infrastructure.monitoring.monitoringClient) - // Spring Boot Service Complete Bundle - // Provides: web, validation, actuator, security, oauth2-client, oauth2-resource-server, - // data-jpa, data-redis, micrometer-prometheus, tracing, zipkin - implementation(libs.bundles.spring.boot.service.complete) + // Spring Boot Service Complete Bundle + // Provides: web, validation, actuator, security, oauth2-client, oauth2-resource-server, + // data-jpa, data-redis, micrometer-prometheus, tracing, zipkin + implementation(libs.bundles.spring.boot.service.complete) - // Jackson Kotlin Support Bundle - implementation(libs.bundles.jackson.kotlin) + // Datenbank (PostgresQL) Driver + implementation(libs.postgresql.driver) - // Kotlin Reflection (now from version catalog) - implementation(libs.kotlin.reflect) + // Web-Server (Tomcat) explizit hinzufügen! + implementation(libs.spring.boot.starter.web) - // Service Discovery - implementation(libs.spring.cloud.starter.consul.discovery) + // Jackson Kotlin Support Bundle + implementation(libs.bundles.jackson.kotlin) - // Caching (Caffeine for Spring Cloud LoadBalancer) - implementation(libs.caffeine) - implementation(libs.spring.web) // Provides spring-context-support + // Kotlin Reflection (now from version catalog) + implementation(libs.kotlin.reflect) - // Resilience4j Bundle (Circuit Breaker, Reactor, AOP) - implementation(libs.bundles.resilience) + // Service Discovery + implementation(libs.spring.cloud.starter.consul.discovery) - // OpenAPI Documentation - implementation(libs.springdoc.openapi.starter.webmvc.ui) + // Caching (Caffeine for Spring Cloud LoadBalancer) + implementation(libs.caffeine) + implementation(libs.spring.web) // Provides spring-context-support - // Test Dependencies - testImplementation(projects.platform.platformTesting) - testImplementation(libs.bundles.testing.jvm) - testImplementation(libs.spring.boot.starter.test) - testImplementation(libs.spring.boot.starter.web) + // Resilience4j Bundle (Circuit Breaker, Reactor, AOP) + implementation(libs.bundles.resilience) + + // OpenAPI Documentation + implementation(libs.springdoc.openapi.starter.webmvc.ui) + + // Test Dependencies + testImplementation(projects.platform.platformTesting) + testImplementation(libs.bundles.testing.jvm) + testImplementation(libs.spring.boot.starter.test) + testImplementation(libs.spring.boot.starter.web) }