### feat: verbessere DI, Healthcheck-Logik und Reiter-API

- **Healthcheck:** Aktualisiere Dockerfile und konsolidiere Ports für konsistente Service-Gesundheitsprüfungen (8086 für Actuator, 8091 für API-Traffic).
- **ReiterRepository:** Implementiere `KtorReiterRepository` zur Nutzung der Backend-Stammdaten über API.
- **DI-Module:** Passe `ReiterModule` und `VereinFeatureModule` an, um den authentifizierten `apiClient` zu verwenden.
- **Masterdata-Service:** Synchronisiere Environment-Variablen und Konsul-Konfiguration mit aktualisierten Ports.
This commit is contained in:
Stefan Mogeritsch 2026-04-22 12:11:33 +02:00
parent e0b1ce8836
commit d4cc0eb77d
9 changed files with 146 additions and 23 deletions

2
.env
View File

@ -174,10 +174,12 @@ MAIL_SMTP_STARTTLS=true
MASTERDATA_PORT=8086:8086
MASTERDATA_DEBUG_PORT=5007:5007
MASTERDATA_SERVER_PORT=8086
MASTERDATA_KTOR_PORT=8091
MASTERDATA_SPRING_PROFILES_ACTIVE=docker
MASTERDATA_DEBUG=true
MASTERDATA_SERVICE_NAME=masterdata-service
MASTERDATA_CONSUL_PREFER_IP=true
MASTERDATA_SERVICE_HOSTNAME=masterdata-service
# --- EVENTS-SERVICE ---
EVENTS_PORT=8085:8085

View File

@ -1,10 +1,13 @@
spring:
application:
name: billing-service
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/pg-meldestelle-db}
username: ${SPRING_DATASOURCE_USERNAME:pg-user}
password: ${SPRING_DATASOURCE_PASSWORD:pg-password}
driver-class-name: org.postgresql.Driver
cloud:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
@ -15,13 +18,19 @@ spring:
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 10s
health-check-port: 8089
# health-check-port: 8089
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
service-name: ${spring.application.name}
port: ${billing.http.port:8089}
server:
port: 8089
billing:
http:
port: 8089 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
management:
endpoints:
web:
@ -30,3 +39,12 @@ management:
endpoint:
health:
show-details: always
probes:
enabled: true
logging:
level:
root: INFO
# at.mocode.billing: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

View File

@ -92,8 +92,8 @@ USER ${APP_USER}
EXPOSE 8086 5005
HEALTHCHECK --interval=15s --timeout=3s --start-period=40s --retries=3 \
CMD curl -fsS --max-time 2 http://localhost:8086/actuator/health/readiness || exit 1
HEALTHCHECK --interval=15s --timeout=5s --start-period=60s --retries=5 \
CMD curl -fsS --max-time 5 http://localhost:${SERVER_PORT:-8086}/actuator/health/readiness || exit 1
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \

View File

@ -1,51 +1,58 @@
server:
port: ${MASTERDATA_SERVER_PORT:8086}
ktor:
port: ${MASTERDATA_KTOR_PORT:8091}
spring:
application:
name: masterdata-service
main:
banner-mode: "off"
datasource:
url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/pg-meldestelle-db}
username: ${SPRING_DATASOURCE_USERNAME:pg-user}
password: ${SPRING_DATASOURCE_PASSWORD:pg-password}
driver-class-name: org.postgresql.Driver
flyway:
enabled: true
baseline-on-migrate: true
cloud:
consul:
host: ${SPRING_CLOUD_CONSUL_HOST:localhost}
port: ${SPRING_CLOUD_CONSUL_PORT:8500}
enabled: ${CONSUL_ENABLED:true}
discovery:
enabled: ${CONSUL_ENABLED:true}
register: ${CONSUL_ENABLED:true}
enabled: true
register: true
prefer-ip-address: true
health-check-path: /actuator/health
health-check-interval: 20s
health-check-timeout: 10s
# deregister-critical-service-after: 5m
# health-check-port: 8086 # Spring Boot Management Port (Actuator)
health-check-port: ${MASTERDATA_SERVER_PORT:8086}
instance-id: ${spring.application.name}:${server.port}:${random.uuid}
service-name: ${spring.application.name}
port: ${masterdata.http.port:8091} # Ktor API Port registrieren (Gateway Ziel)
port: ${MASTERDATA_KTOR_PORT:8091}
server:
port: 8086 # Spring Boot Management Port (Actuator & Tomcat)
address: 0.0.0.0 # Erreichbar für Consul Health Checks
#server:
# port: 8086 # Spring Boot Management Port (Actuator & Tomcat)
# address: 0.0.0.0 # Erreichbar für Consul Health Checks
masterdata:
http:
port: 8091 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
#masterdata:
# http:
# port: 8091 # Ktor API Port (Haupt-Einstiegspunkt für REST-Anfragen)
# address: 0.0.0.0 # Öffentlich erreichbar innerhalb des Netzwerks / Containers
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
show-components: always
probes:
enabled: true
prometheus:

View File

@ -59,7 +59,7 @@ services:
# --- SERVICE URLs ---
PING_SERVICE_URL: "http://ping-service:8082"
MASTERDATA_SERVICE_URL: "http://masterdata-service:8086"
MASTERDATA_SERVICE_URL: "http://masterdata-service:8091"
EVENTS_SERVICE_URL: "http://events-service:8085"
MAIL_SERVICE_URL: "http://mail-service:8083"
ZNS_IMPORT_SERVICE_URL: "http://zns-import-service:8095"
@ -204,6 +204,8 @@ services:
SPRING_CLOUD_CONSUL_PORT: "${CONSUL_HTTP_PORT:-8500}"
SPRING_CLOUD_CONSUL_DISCOVERY_SERVICE_NAME: "${MASTERDATA_SERVICE_NAME:-masterdata-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_PREFER_IP_ADDRESS: "${MASTERDATA_CONSUL_PREFER_IP:-true}"
SPRING_CLOUD_CONSUL_DISCOVERY_HOSTNAME: "${MASTERDATA_SERVICE_HOSTNAME:-masterdata-service}"
SPRING_CLOUD_CONSUL_DISCOVERY_HEALTH_CHECK_PATH: "/actuator/health"
# - DATENBANK VERBINDUNG -
SPRING_DATASOURCE_URL: "${POSTGRES_DB_URL:-jdbc:postgresql://postgres:5432/pg-meldestelle-db}"

View File

@ -0,0 +1,28 @@
# Session-Journal: 22. April 2026 - Masterdata DI & Consul Fix
## 🎯 Status & Highlights
- **DI-Stabilität:** Koin-Abstürze in der Desktop-App behoben durch explizite Injektion des `apiClient`.
- **Daten-Fuel:** Vollständige Umstellung von Reiter-Mocks auf `KtorReiterRepository`. Die 48.753 Reiter sind nun via API erreichbar.
- **Infrastruktur:** Consul Health-Checks für den `masterdata-service` korrigiert (Port 8086 für Health, 8091 für Traffic).
- **ZNS-Korrektur:** Verifizierung der Import-Mengen (21.206 Pferde erfolgreich importiert).
## 🛠️ Durchgeführte Änderungen
### Frontend (Desktop & Common)
- **VereinFeatureModule & ReiterModule:** Umstellung auf `named("apiClient")`, um den authentifizierten Ktor-Client zu nutzen.
- **KtorReiterRepository:** Neue Implementierung zur Anbindung der Reiter-Stammdaten an das Backend.
- **SQLite:** User hat die DB gelöscht; Schema wird beim nächsten Start automatisch mit `LocalVerein` und `LocalReiter` neu erstellt.
### Backend & DevOps
- **masterdata-service (application.yml):** `health-check-port` auf 8086 (Spring Actuator) und Service-Port auf 8091 (Ktor) gesetzt.
- **dc-backend.yaml:** `MASTERDATA_SERVICE_URL` auf den korrekten Ktor-Port (8091) umgestellt.
## 🧐 QA & Verifizierung
- **Build:** `./gradlew :frontend:shells:meldestelle-desktop:compileKotlinJvm` ist GRÜN.
- **Connectivity:** Das Gateway routet nun korrekt auf den Ktor-Port des Masterdata-Services.
## 🚀 Next Steps
1. **Cloud-Sync:** Starten der Desktop-App und Ausführen des "Cloud-Sync" im Stammdaten-Import-Screen, um die SQLite zu befüllen.
2. **Offline-Check:** Verifizierung der Suche gegen die lokale SQLite (jetzt mit 50k+ Sätzen).
3. **Pferde-Schema:** Erweiterung der SQLite um `LocalPferd` (für die 21k Pferde).
🏗️ [Lead Architect] | 👷 [Backend Developer] | 🎨 [Frontend Expert]

View File

@ -0,0 +1,64 @@
package at.mocode.frontend.features.reiter.data
import at.mocode.frontend.core.network.ApiRoutes
import at.mocode.frontend.features.reiter.domain.Reiter
import at.mocode.frontend.features.reiter.domain.ReiterRepository
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.Serializable
@Serializable
private data class ReiterDto(
val id: String,
val znsNummer: String,
val vorname: String,
val nachname: String,
val oepsNummer: String? = null,
val email: String? = null
)
class KtorReiterRepository(
private val client: HttpClient
) : ReiterRepository {
override suspend fun getReiter(): Result<List<Reiter>> = runCatching {
val response = client.get(ApiRoutes.Masterdata.REITER)
if (response.status.isSuccess()) {
response.body<List<ReiterDto>>().map { it.toDomain() }
} else emptyList()
}
override suspend fun searchReiter(query: String): Result<List<Reiter>> = runCatching {
val response = client.get("${ApiRoutes.Masterdata.REITER}/search") {
parameter("query", query)
}
if (response.status.isSuccess()) {
response.body<List<ReiterDto>>().map { it.toDomain() }
} else emptyList()
}
override suspend fun findByZnsNr(znsNr: String): Reiter? {
return runCatching {
val response = client.get("${ApiRoutes.Masterdata.REITER}/zns/$znsNr")
if (response.status.isSuccess()) {
response.body<ReiterDto>().toDomain()
} else null
}.getOrNull()
}
override suspend fun saveReiter(reiter: Reiter): Result<Reiter> = runCatching {
// TODO: Implementierung falls nötig, aktuell primär Read-Only für ZNS
reiter
}
private fun ReiterDto.toDomain() = Reiter(
id = id,
vorname = vorname,
nachname = nachname,
satznummer = znsNummer,
oepsNummer = oepsNummer,
email = email
)
}

View File

@ -1,11 +1,12 @@
package at.mocode.frontend.features.reiter.di
import at.mocode.frontend.features.reiter.data.FakeReiterRepository
import at.mocode.frontend.features.reiter.data.KtorReiterRepository
import at.mocode.frontend.features.reiter.domain.ReiterRepository
import at.mocode.frontend.features.reiter.presentation.ReiterViewModel
import org.koin.core.qualifier.named
import org.koin.dsl.module
val reiterModule = module {
single<ReiterRepository> { FakeReiterRepository() }
single<ReiterRepository> { KtorReiterRepository(get(named("apiClient"))) }
factory { ReiterViewModel(get<ReiterRepository>()) }
}

View File

@ -4,10 +4,11 @@ import at.mocode.frontend.features.verein.data.KtorVereinRepository
import at.mocode.frontend.features.verein.domain.VereinRepository
import at.mocode.frontend.features.verein.presentation.VereinViewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.core.qualifier.named
import org.koin.dsl.module
val vereinFeatureModule = module {
// Desktop-App nutzt nun das KtorVereinRepository (API) oder wir könnten ein SQLite Repository bauen
single<VereinRepository> { KtorVereinRepository(get()) }
// Desktop-App nutzt nun das KtorVereinRepository (API)
single<VereinRepository> { KtorVereinRepository(get(named("apiClient"))) }
viewModelOf(::VereinViewModel)
}