refactor(ping-service): remove CORS configuration from code, restructure modules, and update Gradle dependencies
Migrated CORS settings from code to `application.yaml` for better separation of concerns. Integrated `ping-api` into the new `contracts` module for improved modularity. Updated Gradle scripts and dependencies accordingly to reflect the new project structure.
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
group = "at.mocode"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
// Toolchain is now handled centrally in the root build.gradle.kts
|
||||
|
||||
// JVM target for backend usage
|
||||
jvm()
|
||||
|
||||
// JS target for frontend usage (Compose/Browser)
|
||||
js {
|
||||
browser()
|
||||
// no need for binaries.executable() in a library
|
||||
}
|
||||
|
||||
// Wasm enabled by default
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package at.mocode.ping.api
|
||||
|
||||
interface PingApi {
|
||||
suspend fun simplePing(): PingResponse
|
||||
suspend fun enhancedPing(simulate: Boolean = false): EnhancedPingResponse
|
||||
suspend fun healthCheck(): HealthResponse
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package at.mocode.ping.api
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(val status: String, val timestamp: String, val service: String)
|
||||
|
||||
@Serializable
|
||||
data class EnhancedPingResponse(
|
||||
val status: String,
|
||||
val timestamp: String,
|
||||
val service: String,
|
||||
val circuitBreakerState: String,
|
||||
val responseTime: Long
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HealthResponse(
|
||||
val status: String,
|
||||
val timestamp: String,
|
||||
val service: String,
|
||||
val healthy: Boolean
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ kotlin {
|
||||
|
||||
dependencies {
|
||||
// === Project Dependencies ===
|
||||
implementation(projects.backend.services.ping.pingApi)
|
||||
implementation(projects.contracts.pingApi)
|
||||
implementation(projects.platform.platformDependencies)
|
||||
// NEU: Zugriff auf die verschobenen DatabaseUtils
|
||||
implementation(projects.backend.infrastructure.persistence)
|
||||
|
||||
+1
-22
@@ -2,33 +2,12 @@ package at.mocode.ping
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||
|
||||
@SpringBootApplication
|
||||
// Scannt explizit alle Sub-Packages (infrastructure, application, domain)
|
||||
@EnableAspectJAutoProxy
|
||||
class PingServiceApplication {
|
||||
|
||||
@Bean
|
||||
fun corsConfigurer(): WebMvcConfigurer {
|
||||
return object : WebMvcConfigurer {
|
||||
override fun addCorsMappings(registry: org.springframework.web.servlet.config.annotation.CorsRegistry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("http://localhost:*")
|
||||
.allowedOrigins("http://localhost:8080",
|
||||
"http://localhost:8083",
|
||||
"http://localhost:4000"
|
||||
)
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class PingServiceApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<PingServiceApplication>(*args)
|
||||
|
||||
@@ -42,6 +42,17 @@ spring:
|
||||
health-check-interval: 10s
|
||||
instance-id: ${spring.application.name}-${server.port}-${random.uuid}
|
||||
|
||||
# CORS-Konfiguration ausgelagert aus dem Code
|
||||
web:
|
||||
cors:
|
||||
mappings:
|
||||
"/**":
|
||||
allowed-origin-patterns: "http://localhost:*,http://127.0.0.1:*"
|
||||
allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
|
||||
allowed-headers: "*"
|
||||
allow-credentials: true
|
||||
max-age: 3600
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
|
||||
+3
-11
@@ -10,7 +10,7 @@ import java.util.zip.GZIPOutputStream
|
||||
|
||||
plugins {
|
||||
// Version management plugin for dependency updates
|
||||
id("com.github.ben-manes.versions") version "0.51.0"
|
||||
alias(libs.plugins.benManesVersions)
|
||||
|
||||
// Kotlin plugins declared here with 'apply false' to centralize version management
|
||||
// This prevents "plugin loaded multiple times" errors in Gradle 9.2.1+
|
||||
@@ -29,8 +29,8 @@ plugins {
|
||||
alias(libs.plugins.dokka)
|
||||
|
||||
// Static analysis (enabled at root and inherited by subprojects)
|
||||
id("io.gitlab.arturbosch.detekt") version "1.23.6"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "12.1.1"
|
||||
alias(libs.plugins.detekt)
|
||||
alias(libs.plugins.ktlint)
|
||||
}
|
||||
|
||||
// ##################################################################
|
||||
@@ -80,14 +80,6 @@ subprojects {
|
||||
// The agent configuration was causing Task.project access at execution time
|
||||
}
|
||||
|
||||
// Erzwinge eine stabile Version von kotlinx-serialization-json für alle Konfigurationen,
|
||||
// um Auflösungsfehler (z.B. 1.10.2, nicht verfügbar auf Maven Central) zu vermeiden
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
force("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||
}
|
||||
}
|
||||
|
||||
// Dedicated performance test task per JVM subproject
|
||||
plugins.withId("java") {
|
||||
val javaExt = extensions.getByType<JavaPluginExtension>()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
group = "at.mocode"
|
||||
version = "1.0.0"
|
||||
|
||||
kotlin {
|
||||
// Toolchain is now handled centrally in the root build.gradle.kts
|
||||
|
||||
// JVM target for backend usage
|
||||
jvm()
|
||||
|
||||
// JS target for frontend usage (Compose/Browser)
|
||||
js {
|
||||
browser()
|
||||
// no need for binaries.executable() in a library
|
||||
}
|
||||
|
||||
// Wasm enabled by default
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
|
||||
wasmJs {
|
||||
browser()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package at.mocode.ping.api
|
||||
|
||||
interface PingApi {
|
||||
suspend fun simplePing(): PingResponse
|
||||
suspend fun enhancedPing(simulate: Boolean = false): EnhancedPingResponse
|
||||
suspend fun healthCheck(): HealthResponse
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package at.mocode.ping.api
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(val status: String, val timestamp: String, val service: String)
|
||||
|
||||
@Serializable
|
||||
data class EnhancedPingResponse(
|
||||
val status: String,
|
||||
val timestamp: String,
|
||||
val service: String,
|
||||
val circuitBreakerState: String,
|
||||
val responseTime: Long
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HealthResponse(
|
||||
val status: String,
|
||||
val timestamp: String,
|
||||
val service: String,
|
||||
val healthy: Boolean
|
||||
)
|
||||
@@ -0,0 +1,202 @@
|
||||
**An:** Senior Backend Developer
|
||||
**Von:** Lead Software Architect
|
||||
**Betreff:** Arbeitsauftrag: Implementierung des `ping-service` (Tracer Bullet)
|
||||
|
||||
Guten Tag,
|
||||
|
||||
deine nächste Aufgabe ist die Implementierung unseres ersten Microservices, des `ping-service`. Dieser Service ist von
|
||||
strategischer Bedeutung, da er als **"Tracer Bullet"** und **Blaupause** für alle zukünftigen fachlichen Services dient.
|
||||
|
||||
Mit dieser Implementierung validieren wir die gesamte Kette: von der Service-Registrierung bei Consul über das
|
||||
Gateway-Routing und die Security mit Keycloak bis hin zur Observability mit Zipkin.
|
||||
|
||||
Deine Expertise in Spring Boot, DDD und sauberer Architektur ist hier entscheidend, um eine qualitativ hochwertige und
|
||||
wiederverwendbare Vorlage zu schaffen.
|
||||
|
||||
## Deine Aufgaben im Detail:
|
||||
|
||||
1. Modulstruktur anlegen
|
||||
|
||||
Bitte lege die folgende Modulstruktur unter backend/services/ping an. Beachte die neue, klarere Benennung des
|
||||
Implementierungsmoduls:
|
||||
|
||||
- `:backend:services:ping:ping-api`: Enthält die KMP-kompatiblen DTOs.
|
||||
- `:backend:services:ping:ping-infrastructure`: Enthält die Spring Boot Anwendung, Controller und Konfiguration.
|
||||
|
||||
Stelle sicher, dass die Module in der `settings.gradle.kts` registriert sind.
|
||||
|
||||
```kotlin
|
||||
include(
|
||||
":platform:platform-bom",
|
||||
":platform:platform-testing",
|
||||
":backend:services:ping:ping-api",
|
||||
":backend:services:ping:ping-infrastructure",
|
||||
":backend:gateway",
|
||||
// ":backend:services:registry:registry-api",
|
||||
// ":backend:services:registry:registry-domain",
|
||||
```
|
||||
|
||||
2. API-Definition in `:ping-api`
|
||||
|
||||
Definiere in `ping-api/src/commonMain/kotlin` ein einfaches, serialisierbares DTO. Dieses Modul darf **keine
|
||||
JVM-spezifischen Abhängigkeiten** enthalten, um die KMP-Kompatibilität für das Frontend zu gewährleisten.
|
||||
|
||||
```kotlin
|
||||
PingResponse.kt
|
||||
```
|
||||
```kotlin
|
||||
package de.meldestelle.api.ping
|
||||
|
||||
import kotlinx . serialization . Serializable
|
||||
|
||||
@Serializable
|
||||
data class PingResponse(
|
||||
val message: String,
|
||||
val principal: String? = null
|
||||
)
|
||||
```
|
||||
|
||||
3. Service-Implementierung in :ping-infrastructure
|
||||
|
||||
Implementiere die Spring Boot Anwendung.
|
||||
|
||||
- **`PingController.kt`:**
|
||||
- **`GET /api/ping`:** Ein öffentlicher Endpunkt, der eine `PingResponse("Pong", "anonymous")` zurückgibt.
|
||||
- **`GET /api/ping/secure`:** Ein durch Spring Security geschützter Endpunkt. Er soll den `preferred_username` aus dem `Jwt` Principal extrahieren und in der `PingResponse` zurückgeben.
|
||||
|
||||
Hier ist ein Implementierungsvorschlag für den Controller:
|
||||
|
||||
```kotlin
|
||||
// in backend/services/ping/ping-infrastructure/src/main/kotlin/.../PingController.kt
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/ping")
|
||||
class PingController {
|
||||
|
||||
@GetMapping
|
||||
fun pingPublic(): PingResponse {
|
||||
return PingResponse(message = "Pong", principal = "anonymous")
|
||||
}
|
||||
|
||||
@GetMapping("/secure")
|
||||
fun pingSecure(principal: Jwt): PingResponse {
|
||||
val username = principal.getClaimAsString("preferred_username")
|
||||
return PingResponse(message = "Pong (Secure)", principal = username)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Konfiguration**
|
||||
|
||||
Erstelle die `application.yml` für den Service. Sie muss die Anwendung für unsere Infrastruktur korrekt konfigurieren:
|
||||
|
||||
- **Service Name:** ping-service
|
||||
- **Service Discovery:** Registrierung bei Consul.
|
||||
- **Security:** Konfiguration als Resource Server, der JWTs vom `issuer-uri` unseres Keycloak-Containers validiert.
|
||||
- **Observability:** Actuator-Endpunkte (`health`, `info`, `prometheus`) freigeben und Tracing aktivieren.
|
||||
|
||||
```yaml
|
||||
# in backend/services/ping/ping-infrastructure/src/main/resources/application.yml
|
||||
|
||||
server:
|
||||
port: 8081 # Eindeutiger Port für den Service
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: ping-service
|
||||
|
||||
# --- Consul Discovery ---
|
||||
cloud:
|
||||
consul:
|
||||
host: consul
|
||||
port: 8500
|
||||
discovery:
|
||||
instance-id: \${spring.application.name}:\${random.value}
|
||||
health-check-path: /actuator/health
|
||||
health-check-interval: 10s
|
||||
|
||||
# --- Security (Keycloak) ---
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
issuer-uri: http://keycloak:8080/realms/meldestelle
|
||||
|
||||
# --- Observability ---
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: "health,info,prometheus"
|
||||
tracing:
|
||||
sampling:
|
||||
probability: 1.0 # Trace every request
|
||||
|
||||
logging:
|
||||
pattern:
|
||||
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
|
||||
```
|
||||
|
||||
5. **Build-Konfiguration(`build.gradle.kts`)
|
||||
|
||||
Achte auf die korrekte und saubere Definition der Abhängigkeiten.
|
||||
|
||||
- `ping-api/build.gradle.kts`
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm() // Für die Nutzung im Backend
|
||||
js(IR) { browser() } // Für die Nutzung im Frontend
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `ping-infrastructure/build.gradle.kts`
|
||||
```kotlin
|
||||
plugins {
|
||||
alias(libs.plugins.spring.boot)
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// API-Modul einbinden
|
||||
implementation(project(":backend:services:ping:ping-api"))
|
||||
|
||||
// Unsere zentrale BOM für konsistente Versionen
|
||||
implementation(platform(project(":platform:platform-bom")))
|
||||
|
||||
// Spring Boot Starter
|
||||
implementation(libs.spring.boot.starter.web)
|
||||
implementation(libs.spring.boot.starter.actuator)
|
||||
implementation(libs.spring.boot.starter.security)
|
||||
implementation(libs.spring.boot.starter.oauth2.resource.server)
|
||||
|
||||
// Spring Cloud (Consul, OpenFeign etc.)
|
||||
implementation(libs.spring.cloud.starter.consul.discovery)
|
||||
|
||||
// Test-Abhängigkeiten
|
||||
testImplementation(platform(project(":platform:platform-testing")))
|
||||
testImplementation(libs.bundles.test.spring)
|
||||
}
|
||||
```
|
||||
|
||||
## Definition of Done:
|
||||
|
||||
Der Auftrag gilt als erledigt, wenn:
|
||||
1. Die Anwendung erfolgreich startet und sich im Consul UI als `UP` registriert.
|
||||
2. Ein `GET`-Request auf `http//localhost:8081/api/ping` (über das Gateway) den Status `200 OK` und die `{"message":"Pong", "principal":"anonymous"}` zurückgibt.
|
||||
3. Ein `GET`-Request auf `http//localhost:8081/api/ping/secure` ohne Token den Status `401 Unauthorized` zurückgibt.
|
||||
4. Ein `GET`-Request auf `http//localhost:8081/api/ping/secure` mit einem gültigen Keycloak-Token deb Status `200 OK` und eine Antwort mit dem korrekten Benutzernamen zurückgibt.
|
||||
5. Die Requests in der Zipkin UI als Trace sichtbar sind.
|
||||
|
||||
Bei Fragen zur Konfiguration oder zur Architektur stehe ich dir zur Verfügung.
|
||||
|
||||
@@ -39,7 +39,7 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
// Contract from backend
|
||||
implementation(projects.backend.services.ping.pingApi)
|
||||
implementation(projects.contracts.pingApi)
|
||||
|
||||
// UI Kit (Design System)
|
||||
implementation(projects.frontend.core.designSystem)
|
||||
|
||||
@@ -88,6 +88,10 @@ testcontainersKafka = "1.21.4"
|
||||
|
||||
# Gradle Plugins
|
||||
foojayResolver = "1.0.0"
|
||||
benManesVersions = "0.51.0"
|
||||
detekt = "1.23.6"
|
||||
ktlint = "12.1.1"
|
||||
dokka = "2.1.0"
|
||||
|
||||
[libraries]
|
||||
# ==============================================================================
|
||||
@@ -370,4 +374,7 @@ sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||
|
||||
# --- Tools ---
|
||||
foojayResolver = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "foojayResolver" }
|
||||
dokka = { id = "org.jetbrains.dokka", version = "2.1.0" }
|
||||
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
|
||||
benManesVersions = { id = "com.github.ben-manes.versions", version.ref = "benManesVersions" }
|
||||
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
|
||||
|
||||
+5
-1
@@ -28,6 +28,11 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// CONTRACTS
|
||||
// ==========================================================================
|
||||
include(":contracts:ping-api")
|
||||
|
||||
// ==========================================================================
|
||||
// Backend
|
||||
// ==========================================================================
|
||||
@@ -89,7 +94,6 @@ include(":backend:services:entries:entries-service")
|
||||
// include(":backend:services:members:members-service")
|
||||
|
||||
// --- PING (Ping Service) ---
|
||||
include(":backend:services:ping:ping-api")
|
||||
include(":backend:services:ping:ping-service")
|
||||
|
||||
// --- REGISTRY (Single Source of Truth) ---
|
||||
|
||||
Reference in New Issue
Block a user