refactoring: Docker-Dateien Api-Gateway

This commit is contained in:
2025-11-21 15:38:56 +01:00
parent 69032cb6e7
commit b1c95c1d34
7 changed files with 418 additions and 335 deletions
+6 -3
View File
@@ -34,14 +34,14 @@ PGADMIN_EMAIL=user@domain.com
PGADMIN_PASSWORD=strong-password PGADMIN_PASSWORD=strong-password
PGADMIN_PORT=8888:80 PGADMIN_PORT=8888:80
# --- PROMETHEUS (Metriken) ---
PROMETHEUS_PORT=9090:9090
# --- GRAFANA (Monitoring GUI) --- # --- GRAFANA (Monitoring GUI) ---
GF_ADMIN_USER=gf-admin GF_ADMIN_USER=gf-admin
GF_ADMIN_PASSWORD=gf-password GF_ADMIN_PASSWORD=gf-password
GF_PORT=3000:3000 GF_PORT=3000:3000
# --- PROMETHEUS (Metriken) ---
PROMETHEUS_PORT=9090:9090
# --- SERVICE DISCOVERY (Consul) --- # --- SERVICE DISCOVERY (Consul) ---
CONSUL_PORT=8500:8500 CONSUL_PORT=8500:8500
@@ -50,3 +50,6 @@ CONSUL_PORT=8500:8500
GATEWAY_PORT=8081 GATEWAY_PORT=8081
# Debug Port für IntelliJ (Remote JVM Debug) # Debug Port für IntelliJ (Remote JVM Debug)
GATEWAY_DEBUG_PORT=5005 GATEWAY_DEBUG_PORT=5005
# --- MICROSERVICES ---
PING_SERVICE_PORT=8082:8082
+42
View File
@@ -202,6 +202,48 @@ services:
networks: networks:
- meldestelle-network - 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: volumes:
postgres-data: postgres-data:
pgadmin-data: pgadmin-data:
+45 -42
View File
@@ -1,73 +1,76 @@
// Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt // Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt
// für alle externen Anfragen an das Meldestelle-System. // für alle externen Anfragen an das Meldestelle-System.
plugins { plugins {
alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring) alias(libs.plugins.kotlinSpring)
alias(libs.plugins.kotlinJpa) alias(libs.plugins.kotlinJpa)
alias(libs.plugins.spring.boot) alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Konfiguriert die Hauptklasse für das ausführbare JAR // Konfiguriert die Hauptklasse für das ausführbare JAR
springBoot { springBoot {
mainClass.set("at.mocode.infrastructure.gateway.GatewayApplicationKt") mainClass.set("at.mocode.infrastructure.gateway.GatewayApplicationKt")
} }
dependencies { dependencies {
implementation(platform(projects.platform.platformBom)) implementation(platform(projects.platform.platformBom))
// === Core Dependencies === // === Core Dependencies ===
implementation(projects.core.coreUtils) implementation(projects.core.coreUtils)
implementation(projects.platform.platformDependencies) implementation(projects.platform.platformDependencies)
implementation(projects.infrastructure.monitoring.monitoringClient) implementation(projects.infrastructure.monitoring.monitoringClient)
// === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN === // === GATEWAY-SPEZIFISCHE ABHÄNGIGKEITEN ===
implementation(libs.bundles.spring.cloud.gateway) implementation(libs.bundles.spring.cloud.gateway)
implementation(libs.bundles.spring.boot.security) implementation(libs.bundles.spring.boot.security)
implementation(libs.bundles.resilience) implementation(libs.bundles.resilience)
implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j") implementation("org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j")
implementation(libs.spring.boot.starter.actuator) // Wichtig für Health & Metrics implementation(libs.spring.boot.starter.actuator) // Wichtig für Health & Metrics
implementation(libs.bundles.logging) implementation(libs.bundles.logging)
implementation(libs.bundles.jackson.kotlin) implementation(libs.bundles.jackson.kotlin)
// === Test Dependencies === // WICHTIG: PostgreSQL Treiber hinzufügen!
testImplementation(projects.platform.platformTesting) implementation(libs.postgresql.driver)
testImplementation(libs.bundles.testing.jvm)
// === Test Dependencies ===
testImplementation(projects.platform.platformTesting)
testImplementation(libs.bundles.testing.jvm)
} }
tasks.test { tasks.test {
useJUnitPlatform() useJUnitPlatform()
} }
// Konfiguration für Integration Tests // Konfiguration für Integration Tests
sourceSets { sourceSets {
val integrationTest by creating { val integrationTest by creating {
compileClasspath += sourceSets.main.get().output compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output runtimeClasspath += sourceSets.main.get().output
} }
} }
val integrationTestImplementation by configurations.getting { val integrationTestImplementation by configurations.getting {
extendsFrom(configurations.testImplementation.get()) extendsFrom(configurations.testImplementation.get())
} }
tasks.register<Test>("integrationTest") { tasks.register<Test>("integrationTest") {
description = "Führt die Integration Tests aus" description = "Führt die Integration Tests aus"
group = "verification" group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath classpath = sourceSets["integrationTest"].runtimeClasspath
useJUnitPlatform() useJUnitPlatform()
shouldRunAfter("test") shouldRunAfter("test")
testLogging { testLogging {
events("passed", "skipped", "failed") events("passed", "skipped", "failed")
showStandardStreams = false showStandardStreams = false
showExceptions = true showExceptions = true
showCauses = true showCauses = true
showStackTraces = true showStackTraces = true
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
} }
} }
@@ -17,126 +17,126 @@ import java.time.Duration
*/ */
@Component @Component
class GatewayHealthIndicator( class GatewayHealthIndicator(
private val discoveryClient: DiscoveryClient, private val discoveryClient: DiscoveryClient,
private val webClient: WebClient.Builder, private val webClient: WebClient.Builder,
private val environment: Environment private val environment: Environment
) : HealthIndicator { ) : HealthIndicator {
companion object { companion object {
private val CRITICAL_SERVICES = setOf( private val CRITICAL_SERVICES = setOf(
"members-service", "ping-service"
"horses-service", )
"events-service",
"masterdata-service", private val OPTIONAL_SERVICES = setOf(
"auth-service" "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<String, Any>()
try {
// Prüfe alle registrierten Services in Consul
val allServices = discoveryClient.services
val discoveredServices = mutableMapOf<String, Any>()
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( details["discoveredServices"] = discoveredServices
"ping-service" details["totalServices"] = allServices.size
)
private val HEALTH_CHECK_TIMEOUT = Duration.ofSeconds(5) // Prüfe kritische Services
} val criticalServiceStatus = mutableMapOf<String, String>()
var hasCriticalFailure = false
override fun health(): Health { CRITICAL_SERVICES.forEach { serviceName ->
val builder = Health.up() val status = checkServiceHealth(serviceName)
val details = mutableMapOf<String, Any>() criticalServiceStatus[serviceName] = status
if (status != "UP") {
try { hasCriticalFailure = true
// Prüfe alle registrierten Services in Consul
val allServices = discoveryClient.services
val discoveredServices = mutableMapOf<String, Any>()
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<String, String>()
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<String, String>()
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}"
} }
}
return builder.withDetails(details).build() // Prüfe optionale Services
} val optionalServiceStatus = mutableMapOf<String, String>()
OPTIONAL_SERVICES.forEach { serviceName ->
optionalServiceStatus[serviceName] = checkServiceHealth(serviceName)
}
private fun checkServiceHealth(serviceName: String): String { details["criticalServices"] = criticalServiceStatus
return try { details["optionalServices"] = optionalServiceStatus
val instances = discoveryClient.getInstances(serviceName)
if (instances.isEmpty()) { // Gateway Status basierend auf kritischen Services
"NO_INSTANCES" val isTestEnvironment = environment.activeProfiles.contains("test")
} else { val isDevEnvironment = environment.activeProfiles.contains("dev")
// 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() if (hasCriticalFailure && !isTestEnvironment && !isDevEnvironment) {
val response = client.get() builder.down()
.uri(healthUrl) details["status"] = "DOWN"
.retrieve() details["reason"] = "Ein oder mehrere kritische Services sind nicht verfügbar"
.bodyToMono(Map::class.java) } else {
.timeout(HEALTH_CHECK_TIMEOUT) details["status"] = "UP"
.onErrorReturn(mapOf("status" to "DOWN")) details["reason"] = when {
.block() isTestEnvironment -> "Gesundheitsprüfung erfolgreich (Testumgebung)"
isDevEnvironment -> "Gesundheitsprüfung erfolgreich (Entwicklungsumgebung - nicht alle Services erforderlich)"
val status = response?.get("status")?.toString() ?: "UNKNOWN" else -> "Alle kritischen Services sind verfügbar"
if (status == "UP") "UP" else "DOWN"
}
} catch (exception: WebClientResponseException) {
when (exception.statusCode.value()) {
404 -> "NO_HEALTH_ENDPOINT"
503 -> "DOWN"
else -> "ERROR"
}
} catch (_: Exception) {
"ERROR"
} }
}
} 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"
}
}
} }
@@ -21,96 +21,96 @@ import java.time.Duration
@EnableWebFluxSecurity @EnableWebFluxSecurity
@EnableConfigurationProperties(GatewaySecurityProperties::class) @EnableConfigurationProperties(GatewaySecurityProperties::class)
class SecurityConfig( class SecurityConfig(
private val securityProperties: GatewaySecurityProperties private val securityProperties: GatewaySecurityProperties
) { ) {
/** /**
* Konfiguriert die zentrale Security-Filter-Kette für das Gateway. * Konfiguriert die zentrale Security-Filter-Kette für das Gateway.
* *
* Diese Konfiguration nutzt den Standard-OAuth2-Resource-Server von Spring Security, * Diese Konfiguration nutzt den Standard-OAuth2-Resource-Server von Spring Security,
* um JWTs (z.B. von Keycloak) automatisch zu validieren. * um JWTs (z.B. von Keycloak) automatisch zu validieren.
*/ */
@Bean @Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http { // Start der modernen Kotlin-DSL return http { // Start der modernen Kotlin-DSL
// 1. CORS-Konfiguration anwenden // 1. CORS-Konfiguration anwenden
cors { } cors { }
// 2. CSRF deaktivieren (für zustandslose APIs) // 2. CSRF deaktivieren (für zustandslose APIs)
csrf { disable() } csrf { disable() }
// 3. Routen-Berechtigungen definieren // 3. Routen-Berechtigungen definieren
authorizeExchange { authorizeExchange {
// Öffentlich zugängliche Pfade aus der .yml-Datei laden // Öffentlich zugängliche Pfade aus der .yml-Datei laden
authorize( authorize(
pathMatchers(*securityProperties.publicPaths.toTypedArray()), pathMatchers(*securityProperties.publicPaths.toTypedArray()),
permitAll permitAll
) )
// Alle anderen Pfade erfordern eine Authentifizierung // Alle anderen Pfade erfordern eine Authentifizierung
authorize(anyExchange, authenticated) authorize(anyExchange, authenticated)
} }
// 4. JWT-Validierung via Keycloak aktivieren // 4. JWT-Validierung via Keycloak aktivieren
oauth2ResourceServer { oauth2ResourceServer {
jwt { } 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
} }
/** return UrlBasedCorsConfigurationSource().apply {
* Erstellt einen ReactiveJwtDecoder für die JWT-Validierung. registerCorsConfiguration("/**", configuration)
*
* 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)
}
} }
}
} }
/** /**
@@ -118,25 +118,26 @@ class SecurityConfig(
*/ */
@ConfigurationProperties(prefix = "gateway.security") @ConfigurationProperties(prefix = "gateway.security")
data class GatewaySecurityProperties( data class GatewaySecurityProperties(
val cors: CorsProperties = CorsProperties(), val cors: CorsProperties = CorsProperties(),
val publicPaths: List<String> = listOf( val publicPaths: List<String> = listOf(
"/", "/",
"/fallback/**", "/fallback/**",
"/actuator/**", "/actuator/**",
"/webjars/**", "/webjars/**",
"/v3/api-docs/**", "/v3/api-docs/**",
"/api/auth/**" // Alle Auth-Endpunkte "/api/auth/**", // Alle Auth-Endpunkte
) "/api/ping/**"
)
) )
/** /**
* DTO für CORS-Properties mit sinnvollen Standardwerten. * DTO für CORS-Properties mit sinnvollen Standardwerten.
*/ */
data class CorsProperties( data class CorsProperties(
val allowedOriginPatterns: Set<String> = setOf("http://localhost:[*]", "https://*.meldestelle.at"), val allowedOriginPatterns: Set<String> = setOf("http://localhost:[*]", "https://*.meldestelle.at"),
val allowedMethods: Set<String> = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"), val allowedMethods: Set<String> = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"),
val allowedHeaders: Set<String> = setOf("*"), val allowedHeaders: Set<String> = setOf("*"),
val exposedHeaders: Set<String> = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"), val exposedHeaders: Set<String> = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"),
val allowCredentials: Boolean = true, val allowCredentials: Boolean = true,
val maxAge: Duration = Duration.ofHours(1) val maxAge: Duration = Duration.ofHours(1)
) )
@@ -73,6 +73,10 @@ spring:
name: Cache-Control name: Cache-Control
value: no-cache, no-store, must-revalidate value: no-cache, no-store, must-revalidate
routes: routes:
# ==============================================================
# --- Gateway-Info-Route (optional) ---
# ==============================================================
- id: gateway-info-route - id: gateway-info-route
uri: http://localhost:${server.port} uri: http://localhost:${server.port}
predicates: predicates:
@@ -81,56 +85,80 @@ spring:
filters: filters:
- SetStatus=200 - SetStatus=200
- SetResponseHeader=Content-Type,application/json - SetResponseHeader=Content-Type,application/json
- id: members-service-route
uri: lb://members-service # ==============================================================
predicates: # --- Members-Service-Integration (optional) ---
- Path=/api/members/** # ==============================================================
filters: # - id: members-service-route
- StripPrefix=1 # uri: lb://members-service
- name: CircuitBreaker # predicates:
args: # - Path=/api/members/**
name: membersCircuitBreaker # filters:
fallbackUri: forward:/fallback/members # - StripPrefix=1
- id: horses-service-route # - name: CircuitBreaker
uri: lb://horses-service # args:
predicates: # name: membersCircuitBreaker
- Path=/api/horses/** # fallbackUri: forward:/fallback/members
filters:
- StripPrefix=1 # ==============================================================
- name: CircuitBreaker # --- Horses-Service-Integration (optional) ---
args: # ==============================================================
name: horsesCircuitBreaker # - id: horses-service-route
fallbackUri: forward:/fallback/horses # uri: lb://horses-service
- id: events-service-route # predicates:
uri: lb://events-service # - Path=/api/horses/**
predicates: # filters:
- Path=/api/events/** # - StripPrefix=1
filters: # - name: CircuitBreaker
- StripPrefix=1 # args:
- name: CircuitBreaker # name: horsesCircuitBreaker
args: # fallbackUri: forward:/fallback/horses
name: eventsCircuitBreaker
fallbackUri: forward:/fallback/events # ==============================================================
- id: masterdata-service-route # --- Events-Service-Integration (optional) ---
uri: lb://masterdata-service # ==============================================================
predicates: # - id: events-service-route
- Path=/api/masterdata/** # uri: lb://events-service
filters: # predicates:
- StripPrefix=1 # - Path=/api/events/**
- name: CircuitBreaker # filters:
args: # - StripPrefix=1
name: masterdataCircuitBreaker # - name: CircuitBreaker
fallbackUri: forward:/fallback/masterdata # args:
- id: auth-service-route # name: eventsCircuitBreaker
uri: lb://auth-service # fallbackUri: forward:/fallback/events
predicates:
- Path=/api/auth/** # ==============================================================
filters: # --- Masterdata-Service-Integration (optional) ---
- StripPrefix=1 # ==============================================================
- name: CircuitBreaker # - id: masterdata-service-route
args: # uri: lb://masterdata-service
name: authCircuitBreaker # predicates:
fallbackUri: forward:/fallback/auth # - 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 - id: ping-service-route
uri: lb://ping-service uri: lb://ping-service
predicates: predicates:
+40 -34
View File
@@ -1,54 +1,60 @@
// Optimized Spring Boot ping service for testing microservice architecture // Optimized Spring Boot ping service for testing microservice architecture
// This service demonstrates circuit breaker patterns, service discovery, and monitoring // This service demonstrates circuit breaker patterns, service discovery, and monitoring
plugins { plugins {
alias(libs.plugins.kotlinJvm) alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring) alias(libs.plugins.kotlinSpring)
alias(libs.plugins.kotlinJpa) alias(libs.plugins.kotlinJpa)
alias(libs.plugins.spring.boot) alias(libs.plugins.spring.boot)
alias(libs.plugins.spring.dependencyManagement) alias(libs.plugins.spring.dependencyManagement)
} }
// Configure the main class for the executable JAR // Configure the main class for the executable JAR
springBoot { springBoot {
mainClass.set("at.mocode.ping.service.PingServiceApplicationKt") mainClass.set("at.mocode.ping.service.PingServiceApplicationKt")
} }
dependencies { dependencies {
// Platform BOM für zentrale Versionsverwaltung // Platform BOM für zentrale Versionsverwaltung
implementation(platform(projects.platform.platformBom)) implementation(platform(projects.platform.platformBom))
// Platform und Core Dependencies // Platform und Core Dependencies
implementation(projects.platform.platformDependencies) implementation(projects.platform.platformDependencies)
implementation(projects.services.ping.pingApi) implementation(projects.services.ping.pingApi)
implementation(projects.infrastructure.monitoring.monitoringClient) implementation(projects.infrastructure.monitoring.monitoringClient)
// Spring Boot Service Complete Bundle // Spring Boot Service Complete Bundle
// Provides: web, validation, actuator, security, oauth2-client, oauth2-resource-server, // Provides: web, validation, actuator, security, oauth2-client, oauth2-resource-server,
// data-jpa, data-redis, micrometer-prometheus, tracing, zipkin // data-jpa, data-redis, micrometer-prometheus, tracing, zipkin
implementation(libs.bundles.spring.boot.service.complete) implementation(libs.bundles.spring.boot.service.complete)
// Jackson Kotlin Support Bundle // Datenbank (PostgresQL) Driver
implementation(libs.bundles.jackson.kotlin) implementation(libs.postgresql.driver)
// Kotlin Reflection (now from version catalog) // Web-Server (Tomcat) explizit hinzufügen!
implementation(libs.kotlin.reflect) implementation(libs.spring.boot.starter.web)
// Service Discovery // Jackson Kotlin Support Bundle
implementation(libs.spring.cloud.starter.consul.discovery) implementation(libs.bundles.jackson.kotlin)
// Caching (Caffeine for Spring Cloud LoadBalancer) // Kotlin Reflection (now from version catalog)
implementation(libs.caffeine) implementation(libs.kotlin.reflect)
implementation(libs.spring.web) // Provides spring-context-support
// Resilience4j Bundle (Circuit Breaker, Reactor, AOP) // Service Discovery
implementation(libs.bundles.resilience) implementation(libs.spring.cloud.starter.consul.discovery)
// OpenAPI Documentation // Caching (Caffeine for Spring Cloud LoadBalancer)
implementation(libs.springdoc.openapi.starter.webmvc.ui) implementation(libs.caffeine)
implementation(libs.spring.web) // Provides spring-context-support
// Test Dependencies // Resilience4j Bundle (Circuit Breaker, Reactor, AOP)
testImplementation(projects.platform.platformTesting) implementation(libs.bundles.resilience)
testImplementation(libs.bundles.testing.jvm)
testImplementation(libs.spring.boot.starter.test) // OpenAPI Documentation
testImplementation(libs.spring.boot.starter.web) 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)
} }