chore(cleanup): remove unused FallbackController and outdated GatewayDependencies.txt

- Deleted `FallbackController` as it is no longer required, with alternatives already in place.
- Removed `GatewayDependencies.txt` to clean up outdated and redundant dependency tracking files.
This commit is contained in:
2026-01-04 22:47:11 +01:00
parent a2faf2166a
commit 6e58af1b5b
26 changed files with 495 additions and 1078 deletions
@@ -1,61 +1,38 @@
// 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)
// FINALE BEREINIGUNG: Das `dependencyManagement`-Plugin wird entfernt.
// alias(libs.plugins.spring.dependencyManagement)
alias(libs.plugins.kotlinJvm)
alias(libs.plugins.kotlinSpring)
alias(libs.plugins.kotlinJpa)
alias(libs.plugins.spring.boot)
}
// Configure the main class for the executable JAR
springBoot {
mainClass.set("at.mocode.ping.service.PingServiceApplicationKt")
kotlin {
compilerOptions {
// Aktiviert die experimentelle UUID API von Kotlin 2.3.0
freeCompilerArgs.add("-opt-in=kotlin.uuid.ExperimentalUuidApi")
}
}
dependencies {
// Die `platform`-Deklaration ist der einzig korrekte Weg.
implementation(platform(projects.platform.platformBom))
// === Project Dependencies ===
implementation(projects.backend.services.ping.pingApi)
implementation(projects.platform.platformDependencies)
// Platform und Core Dependencies
implementation(projects.platform.platformDependencies)
implementation(projects.backend.services.ping.pingApi)
implementation(projects.backend.infrastructure.monitoring.monitoringClient)
// === Spring Boot & Cloud ===
implementation(libs.bundles.spring.boot.service.complete)
// WICHTIG: Da wir JPA (blockierend) nutzen, brauchen wir Spring MVC (nicht WebFlux)
implementation(libs.spring.boot.starter.web)
implementation(libs.bundles.spring.cloud.gateway) // Für Discovery Client
// 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)
// === Database & Persistence ===
implementation(libs.bundles.database.complete)
// Datenbank (PostgresQL) Driver
implementation(libs.postgresql.driver)
// === Resilience ===
implementation(libs.bundles.resilience)
// Web-Server (Tomcat) explizit hinzufügen!
implementation(libs.spring.boot.starter.web)
// Jackson Kotlin Support Bundle
implementation(libs.bundles.jackson.kotlin)
// Kotlin Reflection (now from version catalog)
implementation(libs.kotlin.reflect)
// Service Discovery
implementation(libs.spring.cloud.starter.consul.discovery)
// Caching (Caffeine for Spring Cloud LoadBalancer)
implementation(libs.caffeine)
implementation(libs.spring.web) // Provides spring-context-support
// 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)
// === Testing ===
testImplementation(libs.bundles.testing.jvm)
}
tasks.test {
useJUnitPlatform()
}
@@ -1,13 +1,13 @@
package at.mocode.ping.service
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.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.reactive.config.CorsRegistry
@SpringBootApplication
// Scannt explizit alle Sub-Packages (infrastructure, application, domain)
@EnableAspectJAutoProxy
class PingServiceApplication {
@@ -0,0 +1,44 @@
package at.mocode.ping.application
import at.mocode.ping.domain.Ping
import at.mocode.ping.domain.PingRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Application Service.
* Implementiert den Use Case und orchestriert Domain & Repository.
* Hier darf Spring (@Service, @Transactional) verwendet werden, da es "Application Logic" ist.
*/
@Service
@OptIn(ExperimentalUuidApi::class)
class PingService(
private val repository: PingRepository
) : PingUseCase {
private val logger = LoggerFactory.getLogger(PingService::class.java)
@Transactional
override fun executePing(message: String): Ping {
logger.info("Executing ping with message: {}", message)
// Domain Logic: Erstelle neue Entity (generiert UUID v7 automatisch)
val ping = Ping(message = message)
// Persistence
return repository.save(ping)
}
@Transactional(readOnly = true)
override fun getPingHistory(): List<Ping> {
return repository.findAll()
}
@Transactional(readOnly = true)
override fun getPing(id: Uuid): Ping? {
return repository.findById(id)
}
}
@@ -0,0 +1,16 @@
package at.mocode.ping.application
import at.mocode.ping.domain.Ping
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Primary Port (Inbound Port).
* Definiert die fachlichen Operationen, die von außen (Controller) aufgerufen werden können.
*/
@OptIn(ExperimentalUuidApi::class)
interface PingUseCase {
fun executePing(message: String): Ping
fun getPingHistory(): List<Ping>
fun getPing(id: Uuid): Ping?
}
@@ -0,0 +1,16 @@
package at.mocode.ping.domain
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import java.time.Instant
/**
* Domain Entity für einen Ping.
* Unabhängig von Frameworks (Pure Kotlin).
*/
@OptIn(ExperimentalUuidApi::class)
data class Ping(
val id: Uuid = Uuid.generateV7(), // Kotlin 2.3.0 UUID v7
val message: String,
val timestamp: Instant = Instant.now()
)
@@ -0,0 +1,15 @@
package at.mocode.ping.domain
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Secondary Port (Outbound Port).
* Definiert, wie Pings gespeichert werden, ohne die Technologie (DB) zu kennen.
*/
@OptIn(ExperimentalUuidApi::class)
interface PingRepository {
fun save(ping: Ping): Ping
fun findAll(): List<Ping>
fun findById(id: Uuid): Ping?
}
@@ -0,0 +1,24 @@
package at.mocode.ping.infrastructure.persistence
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.Instant
import java.util.UUID
/**
* JPA Entity (Infrastructure Detail).
* Spiegelt die Datenbank-Tabelle wider.
* Nutzt java.util.UUID für JPA-Kompatibilität (bis Hibernate kotlin.uuid nativ unterstützt).
*/
@Entity
@Table(name = "pings")
class PingJpaEntity(
@Id
val id: UUID,
val message: String,
val timestamp: Instant
) {
// Default constructor for JPA
protected constructor() : this(UUID.randomUUID(), "", Instant.now())
}
@@ -0,0 +1,49 @@
package at.mocode.ping.infrastructure.persistence
import at.mocode.ping.domain.Ping
import at.mocode.ping.domain.PingRepository
import org.springframework.stereotype.Component
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlin.uuid.toJavaUuid
import kotlin.uuid.toKotlinUuid
/**
* Driven Adapter.
* Implementiert den Domain-Port `PingRepository` mithilfe von Spring Data JPA.
* Mappt zwischen Domain-Entity und JPA-Entity.
*/
@Component
@OptIn(ExperimentalUuidApi::class)
class PingRepositoryAdapter(
private val jpaRepository: SpringDataPingRepository
) : PingRepository {
override fun save(ping: Ping): Ping {
val jpaEntity = PingJpaEntity(
id = ping.id.toJavaUuid(),
message = ping.message,
timestamp = ping.timestamp
)
val saved = jpaRepository.save(jpaEntity)
return mapToDomain(saved)
}
override fun findAll(): List<Ping> {
return jpaRepository.findAll().map { mapToDomain(it) }
}
override fun findById(id: Uuid): Ping? {
return jpaRepository.findById(id.toJavaUuid())
.map { mapToDomain(it) }
.orElse(null)
}
private fun mapToDomain(entity: PingJpaEntity): Ping {
return Ping(
id = entity.id.toKotlinUuid(),
message = entity.message,
timestamp = entity.timestamp
)
}
}
@@ -0,0 +1,8 @@
package at.mocode.ping.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
interface SpringDataPingRepository : JpaRepository<PingJpaEntity, UUID>
@@ -0,0 +1,96 @@
package at.mocode.ping.infrastructure.web
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import at.mocode.ping.api.PingApi
import at.mocode.ping.api.PingResponse
import at.mocode.ping.application.PingUseCase
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.*
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import kotlin.random.Random
/**
* Driving Adapter (REST Controller).
* Nutzt den Application Port (PingUseCase).
*/
@RestController
@CrossOrigin(allowedHeaders = ["*"], allowCredentials = "true")
class PingController(
private val pingUseCase: PingUseCase
) : PingApi {
private val logger = LoggerFactory.getLogger(PingController::class.java)
private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
companion object {
const val PING_CIRCUIT_BREAKER = "pingCircuitBreaker"
}
@GetMapping("/ping/simple")
override suspend fun simplePing(): PingResponse {
// Ruft Use Case auf -> Speichert in DB
val domainPing = pingUseCase.executePing("Simple Ping")
return PingResponse(
status = "pong",
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
service = "ping-service"
)
}
@GetMapping("/ping/enhanced")
@CircuitBreaker(name = PING_CIRCUIT_BREAKER, fallbackMethod = "fallbackPing")
override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse {
val start = System.nanoTime()
if (simulate && Random.nextDouble() < 0.6) {
throw RuntimeException("Simulated service failure")
}
// Use Case Aufruf
val domainPing = pingUseCase.executePing("Enhanced Ping")
val elapsedMs = (System.nanoTime() - start) / 1_000_000
return EnhancedPingResponse(
status = "pong",
timestamp = domainPing.timestamp.atOffset(ZoneOffset.UTC).format(formatter),
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = elapsedMs
)
}
// Fallback muss public sein für Resilience4j Proxy
fun fallbackPing(simulate: Boolean, ex: Exception): EnhancedPingResponse {
logger.warn("Circuit breaker fallback triggered: {}", ex.message)
return EnhancedPingResponse(
status = "fallback",
timestamp = java.time.OffsetDateTime.now().format(formatter),
service = "ping-service-fallback",
circuitBreakerState = "OPEN",
responseTime = 0
)
}
@GetMapping("/ping/health")
override suspend fun healthCheck(): HealthResponse {
return HealthResponse(
status = "up",
timestamp = java.time.OffsetDateTime.now().format(formatter),
service = "ping-service",
healthy = true
)
}
// Zusätzlicher Endpunkt um die DB zu prüfen (History)
@GetMapping("/ping/history")
fun getHistory() = pingUseCase.getPingHistory().map {
mapOf("id" to it.id.toString(), "message" to it.message, "time" to it.timestamp.toString())
}
}
@@ -1,40 +0,0 @@
package at.mocode.ping.service
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import at.mocode.ping.api.PingApi
import at.mocode.ping.api.PingResponse
import org.springframework.web.bind.annotation.*
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
@RestController
@CrossOrigin(
origins = ["http://localhost:8080", "http://localhost:8083", "http://localhost:4000"],
methods = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS],
allowedHeaders = ["*"],
allowCredentials = "true"
)
class PingController(
private val pingService: PingServiceCircuitBreaker
) : PingApi {
// Contract endpoints
@GetMapping("/ping/simple")
override suspend fun simplePing(): PingResponse {
val now = OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
return PingResponse(
status = "pong",
timestamp = now,
service = "ping-service"
)
}
@GetMapping("/ping/enhanced")
override suspend fun enhancedPing(
@RequestParam(required = false, defaultValue = "false") simulate: Boolean
): EnhancedPingResponse = pingService.ping(simulate)
@GetMapping("/ping/health")
override suspend fun healthCheck(): HealthResponse = pingService.healthCheck()
}
@@ -1,109 +0,0 @@
package at.mocode.ping.service
import at.mocode.ping.api.EnhancedPingResponse
import at.mocode.ping.api.HealthResponse
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.random.Random
/**
* Service demonstrating a Circuit Breaker pattern with Resilience
*
* This service simulates potential failures and uses circuit breaker
* to handle service degradation gracefully with fallback responses.
*/
@Service
class PingServiceCircuitBreaker {
private val logger = LoggerFactory.getLogger(PingServiceCircuitBreaker::class.java)
companion object {
const val PING_CIRCUIT_BREAKER = "pingCircuitBreaker"
private val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME //.ofPattern("yyyy-MM-dd HH:mm:ss")
}
/**
* Primary ping method with circuit breaker protection returning DTO directly
*
* @param simulateFailure - if true, randomly throws exceptions to test circuit breaker
*/
@CircuitBreaker(name = PING_CIRCUIT_BREAKER, fallbackMethod = "fallbackPing")
fun ping(simulateFailure: Boolean = false): EnhancedPingResponse {
val start = System.nanoTime()
logger.info("Executing ping service call...")
if (simulateFailure && Random.nextDouble() < 0.6) {
logger.warn("Simulating service failure for circuit breaker testing")
throw RuntimeException("Simulated service failure")
}
val currentTime = LocalDateTime.now().atOffset(java.time.ZoneOffset.UTC).format(formatter)
val elapsedMs = (System.nanoTime() - start) / 1_000_000
logger.info("Ping service call successful")
return EnhancedPingResponse(
status = "pong",
timestamp = currentTime,
service = "ping-service",
circuitBreakerState = "CLOSED",
responseTime = elapsedMs
)
}
/**
* Fallback method called when circuit breaker is OPEN
*
* @param simulateFailure - original parameter (ignored in fallback)
* @param exception - the exception that triggered the fallback
*/
fun fallbackPing(simulateFailure: Boolean = false, exception: Exception): EnhancedPingResponse {
val start = System.nanoTime()
// Die volle Exception nur loggen, nicht an den Client weitergeben.
logger.warn("Circuit breaker fallback triggered due to: {}", exception.toString())
val currentTime = LocalDateTime.now().atOffset(java.time.ZoneOffset.UTC).format(formatter)
val elapsedMs = (System.nanoTime() - start) / 1_000_000
return EnhancedPingResponse(
status = "fallback",
timestamp = currentTime,
service = "ping-service-fallback",
circuitBreakerState = "OPEN",
responseTime = elapsedMs
)
}
/**
* Health check method with circuit breaker protection returning DTO directly
*/
@CircuitBreaker(name = PING_CIRCUIT_BREAKER, fallbackMethod = "fallbackHealth")
fun healthCheck(): HealthResponse {
logger.info("Executing health check...")
val currentTime = LocalDateTime.now().atOffset(java.time.ZoneOffset.UTC).format(formatter)
return HealthResponse(
status = "pong",
timestamp = currentTime,
service = "ping-service",
healthy = true
)
}
/**
* Fallback for health check returning DTO
*/
fun fallbackHealth(exception: Exception): HealthResponse {
logger.warn("Health check fallback triggered: {}", exception.message)
val currentTime = LocalDateTime.now().atOffset(java.time.ZoneOffset.UTC).format(formatter)
return HealthResponse(
status = "down",
timestamp = currentTime,
service = "ping-service",
healthy = false
)
}
}