feat: Health-Check-Ports und Service-URLs konsolidiert, Consul-Best-Practices umgesetzt
Signed-off-by: StefanMoCoAt <stefan.mo.co@gmail.com>
This commit is contained in:
parent
dd76ad6d14
commit
26b3b193ca
|
|
@ -1,6 +1,5 @@
|
|||
package at.mocode.infrastructure.gateway.config
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.cloud.gateway.route.RouteLocator
|
||||
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
|
||||
import org.springframework.cloud.gateway.route.builder.filters
|
||||
|
|
@ -9,15 +8,7 @@ import org.springframework.context.annotation.Bean
|
|||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class GatewayConfig(
|
||||
@Value("\${ping.service.url:http://localhost:8082}") private val pingServiceUrl: String,
|
||||
@Value("\${masterdata.service.url:http://localhost:8086}") private val masterdataServiceUrl: String,
|
||||
@Value("\${events.service.url:http://localhost:8085}") private val eventsServiceUrl: String,
|
||||
@Value("\${zns.import.service.url:http://localhost:8095}") private val znsImportServiceUrl: String,
|
||||
@Value("\${results.service.url:http://localhost:8088}") private val resultsServiceUrl: String,
|
||||
@Value("\${series.service.url:http://localhost:8089}") private val seriesServiceUrl: String,
|
||||
@Value("\${billing.service.url:http://localhost:8087}") private val billingServiceUrl: String
|
||||
) {
|
||||
class GatewayConfig {
|
||||
|
||||
@Bean
|
||||
fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
|
||||
|
|
@ -31,31 +22,31 @@ class GatewayConfig(
|
|||
it.fallbackUri = java.net.URI.create("forward:/fallback/ping")
|
||||
}
|
||||
}
|
||||
uri(pingServiceUrl)
|
||||
uri("lb://ping-service")
|
||||
}
|
||||
route(id = "masterdata-service") {
|
||||
path("/api/v1/masterdata/**")
|
||||
uri(masterdataServiceUrl)
|
||||
uri("lb://masterdata-service")
|
||||
}
|
||||
route(id = "events-service") {
|
||||
path("/api/v1/events/**")
|
||||
uri(eventsServiceUrl)
|
||||
uri("lb://events-service")
|
||||
}
|
||||
route(id = "zns-import-service") {
|
||||
path("/api/v1/import/zns/**", "/api/v1/import/zns")
|
||||
uri(znsImportServiceUrl)
|
||||
uri("lb://zns-import-service")
|
||||
}
|
||||
route(id = "results-service") {
|
||||
path("/api/v1/results/**")
|
||||
uri(resultsServiceUrl)
|
||||
uri("lb://results-service")
|
||||
}
|
||||
route(id = "series-service") {
|
||||
path("/api/v1/series/**")
|
||||
uri(seriesServiceUrl)
|
||||
uri("lb://series-service")
|
||||
}
|
||||
route(id = "billing-service") {
|
||||
path("/api/v1/billing/**")
|
||||
uri(billingServiceUrl)
|
||||
uri("lb://billing-service")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8081
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
gateway:
|
||||
httpclient: { }
|
||||
# Routen sind in GatewayConfig.kt definiert
|
||||
# Routen sind in GatewayConfig.kt via Service-Discovery (lb://) definiert
|
||||
|
||||
# --- SECURITY (OAuth2 Resource Server) ---
|
||||
security:
|
||||
|
|
@ -66,9 +66,3 @@ management:
|
|||
# Lokal: Zipkin auf Port 9411. In Docker via ENV MANAGEMENT_ZIPKIN_TRACING_ENDPOINT überschrieben.
|
||||
endpoint: ${MANAGEMENT_ZIPKIN_TRACING_ENDPOINT:http://localhost:9411/api/v2/spans}
|
||||
|
||||
# --- Custom Service URLs ---
|
||||
# Default: Localhost (für Entwicklung ohne Docker)
|
||||
# Im Docker-Compose überschreiben wir das mit dem Service-Namen
|
||||
ping:
|
||||
service:
|
||||
url: ${PING_SERVICE_URL:http://localhost:8082}
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8089
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
server:
|
||||
port: ${SERVER_PORT:${BILLING_SERVICE_PORT:8087}}
|
||||
port: 8089
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8083
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8085
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
server:
|
||||
port: ${SERVER_PORT:${IDENTITY_SERVICE_PORT:8088}}
|
||||
port: 8087 # identity-service port
|
||||
|
||||
spring:
|
||||
application:
|
||||
|
|
@ -18,7 +18,7 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8087
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
security:
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8092
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
server:
|
||||
port: 8085
|
||||
port: 8092
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port} # Tomcat Port für Health Checks
|
||||
health-check-port: 8086 # Spring Boot Management Port (Actuator)
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
port: ${masterdata.http.port} # Ktor API Port registrieren
|
||||
port: ${masterdata.http.port:8091} # Ktor API Port registrieren (Gateway Ziel)
|
||||
|
||||
server:
|
||||
port: 8086 # Spring Boot Management Port (Actuator & Tomcat)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8082
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8088
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
server:
|
||||
port: ${SERVER_PORT:${RESULTS_SERVICE_PORT:8084}}
|
||||
port: 8088
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8094
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
server:
|
||||
port: 8089
|
||||
port: 8094
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8093
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
|
||||
server:
|
||||
port: 8090
|
||||
port: 8093
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ plugins {
|
|||
alias(libs.plugins.kotlinSpring)
|
||||
alias(libs.plugins.spring.boot)
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
springBoot {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import at.mocode.zns.import.service.job.ImportJob
|
|||
import at.mocode.zns.import.service.job.ImportJobRegistry
|
||||
import at.mocode.zns.import.service.job.ZnsImportOrchestrator
|
||||
import at.mocode.zns.importer.ZnsImportMode
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
|
||||
@Serializable
|
||||
data class ImportStartResponse(val jobId: String)
|
||||
|
||||
@RestController
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package at.mocode.zns.import.service.job
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.springframework.stereotype.Component
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@Serializable
|
||||
enum class ImportJobStatus { AUSSTEHEND, ENTPACKEN, VERARBEITUNG, ABGESCHLOSSEN, FEHLER }
|
||||
|
||||
@Serializable
|
||||
data class ImportJob(
|
||||
val jobId: String,
|
||||
var status: ImportJobStatus = ImportJobStatus.AUSSTEHEND,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ spring:
|
|||
prefer-ip-address: true
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 15s
|
||||
health-check-port: ${server.port}
|
||||
health-check-port: 8095
|
||||
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
|
||||
service-name: ${spring.application.name}
|
||||
management:
|
||||
|
|
|
|||
45
docs/99_Journal/2026-04-16_ZNS-Serialization-Fix.md
Normal file
45
docs/99_Journal/2026-04-16_ZNS-Serialization-Fix.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# 📓 Journal-Eintrag: 2026-04-16 - ZNS-Import Serialization Fix
|
||||
|
||||
## 🏗️ Status Quo
|
||||
|
||||
Nach dem Deployment der ZNS-First Strategie trat beim Testen der Desktop-App ein Serialisierungsfehler auf:
|
||||
`Serializer for class 'ImportStartResponse' is not found`.
|
||||
|
||||
## 🚀 Analyse & Behebung
|
||||
|
||||
### 1. Das Problem
|
||||
|
||||
Die Kommunikation zwischen der Desktop-App (KMP/Compose) und dem `zns-import-service` (Spring Boot) nutzt
|
||||
`kotlinx.serialization` auf der Client-Seite.
|
||||
|
||||
- Die DTO-Klassen im Backend (`ImportStartResponse`, `ImportJob`) waren nicht mit `@Serializable` annotiert.
|
||||
- Das Gradle-Plugin `kotlin.plugin.serialization` fehlte in den relevanten Modulen (`zns-import-service` und
|
||||
`zns-import-feature`).
|
||||
- Dies führte dazu, dass der Ktor-Client im Frontend die JSON-Antwort des Backends nicht dekodieren konnte.
|
||||
|
||||
### 2. Die Lösung
|
||||
|
||||
- **Backend**: `@Serializable` zu `ImportStartResponse`, `ImportJob` und `ImportJobStatus` hinzugefügt.
|
||||
- **Gradle**: Das `kotlinSerialization` Plugin in `backend/services/zns-import/zns-import-service/build.gradle.kts` und
|
||||
`frontend/features/zns-import-feature/build.gradle.kts` aktiviert.
|
||||
- **Frontend**: Konsistenzprüfung der `ImportStartResponse` und `JobStatusResponse` im `ZnsImportViewModel`.
|
||||
|
||||
## 🛠️ Technische Details
|
||||
|
||||
- **Module**:
|
||||
- `backend:services:zns-import:zns-import-service`
|
||||
- `frontend:features:zns-import-feature`
|
||||
- **Änderungen**:
|
||||
- `ImportJobRegistry.kt`: `ImportJob` & `ImportJobStatus` sind jetzt `@Serializable`.
|
||||
- `ZnsImportController.kt`: `ImportStartResponse` ist jetzt `@Serializable`.
|
||||
- `build.gradle.kts`: Plugins ergänzt.
|
||||
|
||||
## 🏁 Fazit
|
||||
|
||||
Der ZNS-Import-Workflow (Upload -> JobId -> Polling) ist nun technisch stabil und die Typen sind über die Systemgrenzen
|
||||
hinweg serialisierbar.
|
||||
|
||||
---
|
||||
**🧹 [Curator]**: Dokumentation des Serialization-Fixes abgeschlossen.
|
||||
**👷 [Backend Developer]**: DTOs für API-Kommunikation stabilisiert.
|
||||
**🏗️ [Lead Architect]**: Konsistente Nutzung von Kotlinx-Serialization in KMP-Context sichergestellt.
|
||||
|
|
@ -10,6 +10,7 @@ plugins {
|
|||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
group = "at.mocode.clients"
|
||||
version = "1.0.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
# Journal-Eintrag: Consul Discovery Best Practice Fix
|
||||
|
||||
**Datum:** 16. April 2026
|
||||
**Beteiligte:** 🏗️ [Lead Architect], 👷 [Backend Developer]
|
||||
|
||||
## 1. Problemstellung
|
||||
|
||||
Trotz vorheriger Versuche meldete Consul für den `masterdata-service` weiterhin den Status "critical" (404 auf
|
||||
`/actuator/health`).
|
||||
Die Ursache lag in der hybriden Architektur (Spring Boot + Ktor):
|
||||
|
||||
- Spring Boot (Management/Actuator) lief auf Port 8086.
|
||||
- Ktor (API) lief auf Port 8091.
|
||||
- Consul versuchte fälschlicherweise, den Healthcheck auf dem API-Port (oder einer fehlerhaften Kombination)
|
||||
auszuführen, da die Service-Registrierung nicht präzise genug war.
|
||||
|
||||
## 2. Best Practice Lösung
|
||||
|
||||
Um die Stabilität und Wartbarkeit zu erhöhen, wurden folgende Maßnahmen ergriffen:
|
||||
|
||||
### A. Präzise Service-Registrierung (Masterdata-Service)
|
||||
|
||||
In der `application.yml` wurde die Consul-Discovery-Konfiguration geschärft:
|
||||
|
||||
- `health-check-port: 8086`: Der Healthcheck wird explizit an den Spring Boot Management Port gesendet.
|
||||
- `port: ${masterdata.http.port:8091}`: Der Service wird explizit mit seinem API-Port (Ktor) im Service-Katalog
|
||||
registriert.
|
||||
- Damit weiß das Gateway: "Sende Traffic an 8091", und Consul weiß: "Prüfe Health auf 8086".
|
||||
|
||||
### B. Umstellung auf Load-Balancer Abstraktion (`lb://`)
|
||||
|
||||
Das API-Gateway nutzte bisher hartcodierte URLs oder Umgebungsvariablen für das Routing. Dies ist fehleranfällig und
|
||||
widerspricht dem Prinzip der Service Discovery.
|
||||
|
||||
- **Änderung:** Alle Routen in `GatewayConfig.kt` wurden von `http://service-name:port` auf `lb://service-name`
|
||||
umgestellt.
|
||||
- **Vorteil:** Spring Cloud Gateway nutzt nun den Consul-Katalog direkt. Wenn ein Service (wie `masterdata-service`)
|
||||
dort mit Port 8091 registriert ist, wird er automatisch korrekt angesteuert.
|
||||
- Dies entfernt die Notwendigkeit für `*_SERVICE_URL` Umgebungsvariablen im Gateway.
|
||||
|
||||
## 3. Ergebnis
|
||||
|
||||
- Der `masterdata-service` wird nun korrekt als `healthy` markiert, da der Healthcheck den richtigen Port (8086)
|
||||
erreicht.
|
||||
- Das Routing ist nun dynamisch und robust gegenüber Port-Änderungen in den einzelnen Services.
|
||||
- Alle Backend-Services folgen nun einem einheitlichen "Best Practice" Muster für die Service Discovery.
|
||||
|
||||
---
|
||||
**🏗️ [Lead Architect]**: Architektur auf Standard Spring Cloud Discovery (lb://) gehärtet.
|
||||
**👷 [Backend Developer]**: Hybride Port-Konfiguration im Masterdata-Service final korrigiert.
|
||||
Loading…
Reference in New Issue
Block a user