chore: erweitere Resilience4j-Bundle um Kotlin-Support, aktualisiere PingController um Fallback-Logik, füge Fehlerhandler hinzu, verbessere PingControllerTest, synchronisiere .env und dc-infra.yaml

This commit is contained in:
2026-04-19 21:50:27 +02:00
parent 54f91c7309
commit 83adb4ae07
9 changed files with 338 additions and 23 deletions
@@ -2,6 +2,7 @@ package at.mocode.ping.infrastructure.web
import at.mocode.ping.api.*
import at.mocode.ping.application.PingUseCase
import at.mocode.ping.domain.Ping
import at.mocode.ping.infrastructure.PingProperties
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.slf4j.LoggerFactory
@@ -20,7 +21,7 @@ import kotlin.uuid.ExperimentalUuidApi
*/
@RestController
@OptIn(ExperimentalUuidApi::class)
class PingController(
open class PingController(
private val pingUseCase: PingUseCase,
private val properties: PingProperties
) : PingApi {
@@ -43,10 +44,16 @@ class PingController(
override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse {
logger.info("Enhanced ping requested, simulate: {}", simulate)
val start = System.nanoTime()
if (simulate && Random.nextDouble() < 0.6) {
throw RuntimeException("Simulated service failure")
if (simulate) {
if (Random.nextDouble() < 0.6) {
logger.info("Simulating service failure now...")
throw SimulatedException("Simulated service failure")
} else {
logger.info("Simulation mode ACTIVE, but this time lucky: Request passed!")
}
}
val domainPing = pingUseCase.executePing("Enhanced Ping")
@@ -61,6 +68,8 @@ class PingController(
)
}
class SimulatedException(message: String) : RuntimeException(message)
// Neue Endpunkte
@GetMapping("/ping/public")
@@ -70,7 +79,7 @@ class PingController(
}
@GetMapping("/ping/secure")
@PreAuthorize("hasRole('MELD_USER') or hasRole('MELD_ADMIN')") // Beispiel-Rollen
@PreAuthorize("hasRole('ROLE_MELD_USER') or hasRole('ROLE_MELD_ADMIN')") // Beispiel-Rollen
override suspend fun securePing(): PingResponse {
val domainPing = pingUseCase.executePing("Secure Ping")
return createResponse(domainPing, "secure-pong")
@@ -79,7 +88,7 @@ class PingController(
@GetMapping("/ping/sync")
override suspend fun syncPings(
// Changed the parameter name to 'since' to match SyncManager convention
@RequestParam(required = false, defaultValue = "0") since: Long
@RequestParam(name = "lastSyncTimestamp", required = false, defaultValue = "0") since: Long
): List<PingEvent> {
return pingUseCase.getPingsSince(since).map {
PingEvent(
@@ -91,7 +100,7 @@ class PingController(
}
// Helper
private fun createResponse(domainPing: at.mocode.ping.domain.Ping, status: String) = PingResponse(
private fun createResponse(domainPing: Ping, status: String) = PingResponse(
status = status,
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
service = properties.serviceName
@@ -99,8 +108,8 @@ class PingController(
// Fallback
@Suppress("unused", "UNUSED_PARAMETER")
fun fallbackPing(simulate: Boolean, ex: Exception): EnhancedPingResponse {
logger.warn("Circuit breaker fallback triggered: {}", ex.message)
open fun fallbackPing(simulate: Boolean, ex: Throwable): EnhancedPingResponse {
logger.error("CIRCUIT BREAKER FALLBACK TRIGGERED! Reason: {}", ex.message, ex)
return EnhancedPingResponse(
status = "fallback",
timestamp = java.time.OffsetDateTime.now().format(formatter),
@@ -0,0 +1,32 @@
package at.mocode.ping.infrastructure.web
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.ProblemDetail
import org.springframework.security.access.AccessDeniedException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
@RestControllerAdvice
class PingExceptionHandler {
private val log = LoggerFactory.getLogger(PingExceptionHandler::class.java)
@ExceptionHandler(AccessDeniedException::class)
fun handleAccessDenied(ex: AccessDeniedException): ProblemDetail {
log.warn("Zugriff verweigert: ${ex.message}")
return ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, "Nicht berechtigt: ${ex.message}")
}
@ExceptionHandler(Exception::class)
fun handleAll(ex: Exception): ProblemDetail {
log.error("Unerwarteter Fehler: ", ex)
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Ein interner Fehler ist aufgetreten")
}
@ExceptionHandler(RuntimeException::class)
fun handleRuntime(ex: RuntimeException): ProblemDetail {
log.error("Interner Fehler: ", ex)
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.message ?: "Unbekannter Fehler")
}
}