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_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
+42
View File
@@ -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:
+45 -42
View File
@@ -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<Test>("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
}
}
@@ -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<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(
"ping-service"
)
details["discoveredServices"] = discoveredServices
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 {
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}" }
)
}
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}"
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<String, String>()
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"
}
}
}
@@ -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<String> = listOf(
"/",
"/fallback/**",
"/actuator/**",
"/webjars/**",
"/v3/api-docs/**",
"/api/auth/**" // Alle Auth-Endpunkte
)
val cors: CorsProperties = CorsProperties(),
val publicPaths: List<String> = 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<String> = setOf("http://localhost:[*]", "https://*.meldestelle.at"),
val allowedMethods: Set<String> = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"),
val allowedHeaders: Set<String> = setOf("*"),
val exposedHeaders: Set<String> = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"),
val allowCredentials: Boolean = true,
val maxAge: Duration = Duration.ofHours(1)
val allowedOriginPatterns: Set<String> = setOf("http://localhost:[*]", "https://*.meldestelle.at"),
val allowedMethods: Set<String> = setOf("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"),
val allowedHeaders: Set<String> = setOf("*"),
val exposedHeaders: Set<String> = setOf("X-Correlation-ID", "X-RateLimit-Limit", "X-RateLimit-Remaining"),
val allowCredentials: Boolean = true,
val maxAge: Duration = Duration.ofHours(1)
)
@@ -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:
+40 -34
View File
@@ -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)
}