optimierungen auth-Modul und cache-Modul
This commit is contained in:
parent
abd2543caf
commit
63a1b97db7
|
|
@ -1,4 +1,4 @@
|
|||
# Infrastructure/Auth Module - Comprehensive Documentation
|
||||
# Infrastructure/Auth Modul – Aktuelle Dokumentation (Stand: September 2025)
|
||||
|
||||
## Überblick
|
||||
|
||||
|
|
@ -6,6 +6,21 @@ Das **Auth-Modul** ist die zentrale Komponente für die gesamte Authentifizierun
|
|||
|
||||
Als Identity Provider wird **Keycloak** verwendet. Dieses Modul kapselt die gesamte Interaktion mit Keycloak und stellt dem Rest des Systems eine einheitliche und vereinfachte Sicherheitsschicht zur Verfügung.
|
||||
|
||||
## Aufgabe des Moduls
|
||||
|
||||
- Zentrale Bereitstellung von Authentifizierungs- und Autorisierungsfunktionen für alle Services
|
||||
- Minimierung der Kopplung an Keycloak durch eine API/Client-Abstraktion (`auth-client`)
|
||||
- Einheitliche, typsichere Repräsentation von Rollen und Berechtigungen als Enums
|
||||
- Sichere Erzeugung, Validierung und Auswertung von JWTs (Issuer, Audience, Ablauf, Signatur)
|
||||
- Bereitstellung eines dedizierten Auth-Servers für Benutzer-Workflows (Login, optional Passwortänderung, Token-Ausstellung)
|
||||
|
||||
## Umsetzung (High-Level)
|
||||
|
||||
- Authentifizierung findet gegen Keycloak statt; der `auth-server` kapselt dessen Aufrufe.
|
||||
- Nach erfolgreicher Authentifizierung wird ein signiertes JWT erzeugt, das Rollen/Berechtigungen enthält.
|
||||
- Downstream-Services validieren das JWT über den `auth-client` und führen autorisierte Domänenaktionen aus.
|
||||
- Das API-Gateway kann JWTs vorvalidieren und Metadaten-Header weitergeben; vollständige Validierung sollte via `auth-client` erfolgen.
|
||||
|
||||
## Architektur
|
||||
|
||||
Das Auth-Modul ist in zwei spezialisierte Komponenten aufgeteilt, um eine klare Trennung der Verantwortlichkeiten zu gewährleisten:
|
||||
|
|
@ -20,12 +35,17 @@ infrastructure/auth/
|
|||
|
||||
Dieses Modul ist eine **wiederverwendbare Bibliothek** und kein eigenständiger Service. Es enthält die gesamte Logik, die andere Microservices (wie `masterdata-service`, `members-service` etc.) benötigen, um ihre Endpunkte abzusichern.
|
||||
|
||||
**Hauptaufgaben:**
|
||||
* **JWT-Management:** Stellt einen `JwtService` zur Erstellung und Validierung von JSON Web Tokens bereit.
|
||||
* **Modell-Definition:** Definiert die **Quelle der Wahrheit** für sicherheitsrelevante Konzepte wie `RolleE` und `BerechtigungE` als typsichere Kotlin-Enums. Dies stellt sicher, dass alle Services dieselbe "Sprache" für Berechtigungen sprechen.
|
||||
* **Schnittstellen:** Bietet saubere Schnittstellen wie `AuthenticationService` an, die von der konkreten Implementierung (z.B. Keycloak) abstrahieren.
|
||||
Aktueller Stand (09/2025):
|
||||
- Enthält ein typensicheres Rollen- und Berechtigungsmodell: `RolleE`, `BerechtigungE` (kotlinx.serialization-annotiert für konsistente JSON-Serialisierung).
|
||||
- Definiert die Schnittstelle `AuthenticationService` mit suspend-Funktionen und Result-Typen zur Authentifizierung und Passwortänderung. Rückgabewerte sind versiegelt (sealed) und decken Success/Failure/Locked ab. Dadurch klare, explizite Fehlerfälle ohne Exceptions in Kontrollflüssen.
|
||||
- Stellt den `JwtService` bereit, der via Spring konfiguriert werden kann und in Services zur Token-Erzeugung/-Validierung genutzt wird.
|
||||
|
||||
Jeder Microservice, der geschützte Endpunkte anbietet, bindet dieses Modul als Abhängigkeit ein.
|
||||
**Hauptaufgaben:**
|
||||
* **JWT-Management:** Stellt einen `JwtService` zur Erstellung und Validierung von JSON Web Tokens bereit (Signatur, Claims, Ablaufzeiten). Neue, result-basierte APIs erleichtern das Fehler-Handling.
|
||||
* **Modell-Definition:** Definiert die **Quelle der Wahrheit** für sicherheitsrelevante Konzepte wie `RolleE` und `BerechtigungE` als typsichere Kotlin-Enums. Dies stellt sicher, dass alle Services dieselbe "Sprache" für Berechtigungen sprechen.
|
||||
* **Schnittstellen:** Bietet saubere Schnittstellen wie `AuthenticationService` an, die von der konkreten Implementierung (z.B. Keycloak) abstrahieren. Dadurch können Implementierungen im `auth-server` oder in Tests (Mocks/Fakes) ausgetauscht werden.
|
||||
|
||||
Einbindung: Jeder Microservice, der geschützte Endpunkte anbietet, bindet dieses Modul als Abhängigkeit ein.
|
||||
|
||||
### `auth-server`
|
||||
|
||||
|
|
@ -36,6 +56,42 @@ Dies ist ein **eigenständiger Spring Boot Microservice**, der als Brücke zwisc
|
|||
* **Token-Endpunkte:** Ist verantwortlich für das Ausstellen von Tokens nach einer erfolgreichen Authentifizierung.
|
||||
* **Implementierung der `AuthenticationService`-Schnittstelle:** Enthält die konkrete Logik, die gegen Keycloak prüft, ob ein Benutzername und ein Passwort korrekt sind.
|
||||
|
||||
**Konfiguration (AuthServerConfiguration):**
|
||||
Der Service stellt einen konfigurierbaren `JwtService` per Spring-Bean bereit. Die dazugehörigen Properties werden über `auth.jwt.*` gesetzt:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
jwt:
|
||||
secret: <32+ Zeichen starkes Secret>
|
||||
issuer: meldestelle-auth-server
|
||||
audience: meldestelle-services
|
||||
expiration: 60 # Minuten
|
||||
```
|
||||
|
||||
Kotlin-Konfiguration (vereinfacht):
|
||||
```kotlin
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(JwtProperties::class)
|
||||
class AuthServerConfiguration {
|
||||
@Bean
|
||||
fun jwtService(props: JwtProperties) = JwtService(
|
||||
secret = props.secret,
|
||||
issuer = props.issuer,
|
||||
audience = props.audience,
|
||||
expiration = props.expiration.minutes
|
||||
)
|
||||
@ConfigurationProperties(prefix = "auth.jwt")
|
||||
data class JwtProperties(
|
||||
val secret: String,
|
||||
val issuer: String,
|
||||
val audience: String,
|
||||
val expiration: Long
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Hinweis: Standardwerte sind nur für lokale Entwicklung gedacht und müssen in Produktion überschrieben werden. Zusätzlich validiert der Auth-Server die JWT-Properties: Secret min. 32 Zeichen, issuer/audience nicht leer; bei Verwendung des Default-Secrets wird eine Laufzeit-Warnung ausgegeben.
|
||||
|
||||
## Zusammenspiel im System
|
||||
|
||||
1. Ein **Benutzer** meldet sich über eine Client-Anwendung am **`auth-server`** an.
|
||||
|
|
@ -43,10 +99,11 @@ Dies ist ein **eigenständiger Spring Boot Microservice**, der als Brücke zwisc
|
|||
3. Bei Erfolg erstellt der `auth-server` mit dem `JwtService` aus dem `auth-client` ein JWT, das die Berechtigungen des Benutzers enthält, und sendet es an den Client zurück.
|
||||
4. Der **Client** sendet eine Anfrage an einen anderen Microservice (z.B. `members-service`) und fügt das JWT als Bearer-Token in den Header ein.
|
||||
5. Der **`members-service`**, der ebenfalls den `auth-client` als Abhängigkeit hat, nutzt den `JwtService`, um das Token zu validieren und die Berechtigungen typsicher auszulesen.
|
||||
6. Das **Gateway** kann vorgelagert JWT-basierte Authentifizierung durchführen. Aktuell existiert ein `JwtAuthenticationFilter`, der über `gateway.security.jwt.enabled=true` aktiviert wird. In der vorliegenden Codebasis nutzt dieser noch eine vereinfachte Validierung; die geplante Integration ist die Nutzung des `auth-client` zur vollständigen Validierung und Claim-Extraktion.
|
||||
|
||||
Diese Architektur entkoppelt die Fach-Services von der Komplexität der Identitätsverwaltung und schafft eine robuste, zentrale Sicherheitsinfrastruktur.
|
||||
|
||||
## Modernisierungen (August 2025)
|
||||
## Modernisierungen (September 2025)
|
||||
|
||||
### Technische Verbesserungen
|
||||
|
||||
|
|
@ -68,6 +125,21 @@ Diese Architektur entkoppelt die Fach-Services von der Komplexität der Identit
|
|||
- Entfernung von `Thread.sleep()` für zuverlässigere Tests
|
||||
- Bessere Expired-Token-Tests mit eindeutigen Zeitstempel-Differenzen
|
||||
|
||||
### Token Claims und Struktur
|
||||
|
||||
Empfohlene Claims im JWT (Beispiel):
|
||||
- sub: Benutzer-ID (UUID)
|
||||
- pid: Personen-ID (UUID)
|
||||
- preferred_username: Loginname (derzeit intern als Claim "username" umgesetzt)
|
||||
- email: E-Mail-Adresse
|
||||
- roles: Liste von Rollen (`RolleE`)
|
||||
- perms: Liste von Berechtigungen (`BerechtigungE`)
|
||||
- iss: Issuer (z.B. meldestelle-auth-server)
|
||||
- aud: Audience (z.B. meldestelle-services)
|
||||
- iat/exp: Ausstellungs- und Ablaufzeitpunkt
|
||||
|
||||
Diese Claims werden vom `auth-client` gelesen und in typsichere Modelle abgebildet.
|
||||
|
||||
### API-Änderungen
|
||||
|
||||
**Neue Result-basierte APIs:**
|
||||
|
|
@ -255,6 +327,14 @@ Das Auth-Modul wurde von **kritisch untergetestet** auf **umfassend getestet** t
|
|||
└── kotlinx-serialization-json (JSON Serialization)
|
||||
```
|
||||
|
||||
## Aktualitäts-Check (Repo-Stand September 2025)
|
||||
|
||||
- `auth-client` enthält `AuthenticationService` mit suspend-Funktionen und versiegelten Result-Typen (Success/Failure/Locked; PasswordChangeResult inkl. WeakPassword). Diese Schnittstelle ist in dieser README beschrieben und aktuell.
|
||||
- `auth-server` stellt `JwtService` via `AuthServerConfiguration` bereit und liest Properties unter `auth.jwt.*`. Beispielkonfiguration ist oben dokumentiert und entspricht dem Code.
|
||||
- Das API-Gateway besitzt einen `JwtAuthenticationFilter`, der derzeit eine vereinfachte Tokenprüfung implementiert. Geplante nächste Stufe: Verwendung des `auth-client` zur echten JWT-Validierung und Claim-Extraktion. Property-Schalter: `gateway.security.jwt.enabled`.
|
||||
|
||||
Diese README wurde am 03.09.2025 aktualisiert und spiegelt den aktuellen Stand der Implementierung wider.
|
||||
|
||||
## Production-Readiness Status
|
||||
|
||||
### ✅ Production-Ready Bereiche
|
||||
|
|
@ -305,7 +385,7 @@ Das infrastructure/auth Modul ist **production-ready** und umfassend modernisier
|
|||
Die Transformation von "kritisch untergetestet" zu "production-ready" ist vollständig abgeschlossen und erfüllt alle Anforderungen für ein sicherheitskritisches Authentifizierungs-System in einer Microservice-Landschaft.
|
||||
|
||||
---
|
||||
**Letzte Aktualisierung**: 15. August 2025
|
||||
**Letzte Aktualisierung**: 3. September 2025
|
||||
**Status**: Production-Ready mit umfassender Test-Abdeckung
|
||||
**Dokumentation**: Vollständig konsolidiert aus allen Teilbereichen
|
||||
**Validierung**: Sicherheitstests erfolgreich bestanden (15.08.2025)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,21 @@ plugins {
|
|||
alias(libs.plugins.spring.dependencyManagement)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
|
||||
|
|
|
|||
|
|
@ -5,37 +5,37 @@ import com.benasher44.uuid.Uuid
|
|||
import java.time.LocalDateTime
|
||||
|
||||
/**
|
||||
* Service for user authentication and password management.
|
||||
* Service für Benutzerauthentifizierung und Passwortverwaltung.
|
||||
*/
|
||||
interface AuthenticationService {
|
||||
/**
|
||||
* Authenticates a user with the given username and password.
|
||||
* Authentifiziert einen Benutzer mit Benutzernamen und Passwort.
|
||||
*
|
||||
* @param username The username
|
||||
* @param password The password
|
||||
* @return The authentication result
|
||||
* @param username Der Benutzername
|
||||
* @param password Das Passwort
|
||||
* @return Das Authentifizierungsergebnis
|
||||
*/
|
||||
suspend fun authenticate(username: String, password: String): AuthResult
|
||||
|
||||
/**
|
||||
* Changes a user's password.
|
||||
* Ändert das Passwort eines Benutzers.
|
||||
*
|
||||
* @param userId The user ID
|
||||
* @param currentPassword The current password
|
||||
* @param newPassword The new password
|
||||
* @return The password change result
|
||||
* @param userId Die Benutzer-ID
|
||||
* @param currentPassword Das aktuelle Passwort
|
||||
* @param newPassword Das neue Passwort
|
||||
* @return Das Ergebnis der Passwortänderung
|
||||
*/
|
||||
suspend fun changePassword(userId: Uuid, currentPassword: String, newPassword: String): PasswordChangeResult
|
||||
|
||||
/**
|
||||
* Possible results of an authentication attempt.
|
||||
* Mögliche Ergebnisse eines Authentifizierungsversuchs.
|
||||
*/
|
||||
sealed class AuthResult {
|
||||
/**
|
||||
* Authentication was successful.
|
||||
* Authentifizierung war erfolgreich.
|
||||
*
|
||||
* @param token The JWT token
|
||||
* @param user The authenticated user
|
||||
* @param token Das JWT-Token
|
||||
* @param user Der authentifizierte Benutzer
|
||||
*/
|
||||
data class Success(val token: String, val user: AuthenticatedUser) : AuthResult()
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ class JwtService(
|
|||
) {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
init {
|
||||
require(secret.length >= 32) { "JWT secret must be at least 32 characters for HMAC512" }
|
||||
require(issuer.isNotBlank()) { "JWT issuer must not be blank" }
|
||||
require(audience.isNotBlank()) { "JWT audience must not be blank" }
|
||||
}
|
||||
|
||||
private val algorithm = Algorithm.HMAC512(secret)
|
||||
private val verifier = JWT.require(algorithm)
|
||||
.withIssuer(issuer)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class JwtServiceTest {
|
|||
@Test
|
||||
fun `validateToken should return false for token with wrong secret`() {
|
||||
// Arrange
|
||||
val otherService = JwtService("a-different-wrong-secret", testIssuer, testAudience)
|
||||
val otherService = JwtService("a-different-wrong-secret-that-is-long-enough-1234567890", testIssuer, testAudience)
|
||||
val token = otherService.generateToken("user-123", "test", emptyList())
|
||||
|
||||
// Act & Assert
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ plugins {
|
|||
alias(libs.plugins.spring.dependencyManagement)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
// Konfiguriert die Hauptklasse für das ausführbare JAR.
|
||||
springBoot {
|
||||
mainClass.set("at.mocode.infrastructure.auth.AuthServerApplicationKt")
|
||||
|
|
|
|||
|
|
@ -5,21 +5,29 @@ import org.springframework.boot.context.properties.ConfigurationProperties
|
|||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.validation.annotation.Validated
|
||||
import jakarta.validation.constraints.Min
|
||||
import jakarta.validation.constraints.NotBlank
|
||||
import jakarta.validation.constraints.Size
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
* Spring configuration for the Auth Server module.
|
||||
* Provides the necessary beans and configuration for JWT handling and authentication.
|
||||
* Spring-Konfiguration für das Auth-Server-Modul.
|
||||
* Stellt die notwendigen Beans und Einstellungen für JWT-Verarbeitung und Authentifizierung bereit.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AuthServerConfiguration.JwtProperties::class)
|
||||
class AuthServerConfiguration {
|
||||
|
||||
/**
|
||||
* Creates a JwtService bean with configuration from application properties.
|
||||
* Erstellt einen JwtService-Bean mit Konfiguration aus den Application Properties.
|
||||
*/
|
||||
@Bean
|
||||
fun jwtService(jwtProperties: JwtProperties): JwtService {
|
||||
// Basic safeguard: warn if default secret is used
|
||||
if (jwtProperties.secret == "default-secret-for-development-only-please-change-in-production") {
|
||||
System.err.println("[SECURITY WARNING] Using default JWT secret – DO NOT use this in production!")
|
||||
}
|
||||
return JwtService(
|
||||
secret = jwtProperties.secret,
|
||||
issuer = jwtProperties.issuer,
|
||||
|
|
@ -29,13 +37,19 @@ class AuthServerConfiguration {
|
|||
}
|
||||
|
||||
/**
|
||||
* Configuration properties for JWT settings.
|
||||
* Konfigurationseigenschaften für JWT-Einstellungen.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "auth.jwt")
|
||||
@Validated
|
||||
data class JwtProperties(
|
||||
@field:NotBlank
|
||||
@field:Size(min = 32, message = "JWT secret must be at least 32 characters for HMAC512")
|
||||
val secret: String = "default-secret-for-development-only-please-change-in-production",
|
||||
@field:NotBlank
|
||||
val issuer: String = "meldestelle-auth-server",
|
||||
@field:NotBlank
|
||||
val audience: String = "meldestelle-services",
|
||||
@field:Min(1)
|
||||
val expiration: Long = 60 // minutes
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
// Infrastructure Auth Module Container
|
||||
// This is a container module for authentication-related subprojects
|
||||
543
infrastructure/cache/README-INFRA-CACHE.md
vendored
543
infrastructure/cache/README-INFRA-CACHE.md
vendored
|
|
@ -1,495 +1,114 @@
|
|||
# Infrastructure/Cache Module - Comprehensive Documentation
|
||||
# Infrastructure/Cache – Modulbeschreibung und Implementierungsleitfaden
|
||||
|
||||
*Letzte Aktualisierung: 15. August 2025*
|
||||
Letzte Aktualisierung: 03. September 2025
|
||||
|
||||
## Überblick
|
||||
## Zweck und Aufgaben des Moduls
|
||||
Das Infrastructure/Cache-Modul stellt eine einheitliche, technologie‑neutrale Cache‑Schnittstelle für alle Services bereit und liefert mit einer Redis‑basierten Adapter‑Implementierung die produktionsreife Ausführung. Ziele:
|
||||
- Antwortzeiten reduzieren und Primärdatenbanken entlasten.
|
||||
- Einheitliche API für Lesen/Schreiben, Batch‑Operationen und TTLs.
|
||||
- Resilienz bei Redis‑Ausfällen durch lokalen Fallback.
|
||||
- Operative Transparenz durch einfache Metriken, Health‑Informationen und periodische Wartungsaufgaben.
|
||||
|
||||
Das **Cache-Modul** stellt eine zentrale, hochverfügbare und produktionsbereite Caching-Infrastruktur für alle Microservices bereit. Es dient der Verbesserung der Anwendungsperformance, der Reduzierung von Latenzen und der Entlastung der primären PostgreSQL-Datenbank.
|
||||
## Architektur (Port‑Adapter)
|
||||
- cache‑api: enthält die öffentlichen Verträge und Basistypen
|
||||
- DistributedCache: zentrale Port‑Schnittstelle für Cache‑Operationen
|
||||
- CacheEntry, CacheConfiguration, CacheSerializer
|
||||
- ConnectionStatusTracker/ConnectionStateListener zur Verbindungsüberwachung
|
||||
- redis‑cache: Adapter, der die Port‑Schnittstelle mit Spring Data Redis umsetzt
|
||||
- RedisDistributedCache: konkrete Implementierung inkl. Offline‑Fallback, Dirty‑Sync, Batchs, Key‑Prefixing, TTL‑Handling und einfachen Metriken
|
||||
- JacksonCacheSerializer: serialisiert Werte und CacheEntry per Jackson
|
||||
|
||||
**Status: ✅ PRODUKTIONSBEREIT** - Vollständig getestet mit 39 Tests (94.7% Success Rate)
|
||||
## Öffentliche API (Auszug)
|
||||
DistributedCache
|
||||
- get(key, clazz)/set(key, value, ttl?)
|
||||
- delete(key), exists(key)
|
||||
- multiGet(keys, clazz), multiSet(map, ttl?)
|
||||
- multiDelete(keys)
|
||||
- synchronize(keys?), markDirty(key), getDirtyKeys(), clear()
|
||||
|
||||
## Architektur: Port-Adapter-Muster
|
||||
Idiomatic Kotlin Extensions
|
||||
- cache.get<T>(key)
|
||||
- cache.multiGet<T>(keys)
|
||||
|
||||
Das Modul folgt streng dem **Port-Adapter-Muster** (Hexagonale Architektur), um eine saubere Trennung zwischen der Caching-Schnittstelle (dem "Port") und der konkreten Implementierung (dem "Adapter") zu gewährleisten.
|
||||
CacheConfiguration (DefaultCacheConfiguration vorhanden)
|
||||
- defaultTtl?, localCacheMaxSize?, offlineModeEnabled, synchronizationInterval, offlineEntryMaxAge?, keyPrefix, compressionEnabled, compressionThreshold
|
||||
|
||||
### Module-Struktur
|
||||
Hinweis: Die Kompression wird aktuell durch den Serializer bereitgestellt; Schwellwerte/Flags sind für zukünftiges Tuning vorgesehen.
|
||||
|
||||
* **`:infrastructure:cache:cache-api`**: Definiert den abstrakten "Vertrag" für das Caching (`DistributedCache`-Interface), ohne sich um die zugrunde liegende Technologie zu kümmern. Die Fach-Services programmieren ausschließlich gegen dieses Interface.
|
||||
* **`:infrastructure:cache:redis-cache`**: Die konkrete Implementierung des Vertrags, die **Redis** als hochperformantes Caching-Backend verwendet. Kapselt die gesamte Redis-spezifische Logik.
|
||||
## Implementierungsdetails (RedisDistributedCache)
|
||||
- Lokaler Fallback: ConcurrentHashMap als lokaler Cache speichert CacheEntry inkl. expiresAt. Bei Redis‑Ausfall werden Schreibvorgänge lokal gehalten und als „dirty“ markiert.
|
||||
- Dirty‑Synchronisation: Sobald die Verbindung wieder ONLINE ist, werden geänderte Schlüssel zu Redis synchronisiert (synchronize()).
|
||||
- Key‑Prefixing: Alle externen Keys werden mittels keyPrefix gekapselt, um Mandanten/Services zu isolieren.
|
||||
- TTL/Expiration: TTL wird einheitlich über kotlin.time.Duration angegeben und für Redis in java.time.Duration konvertiert. Lokale Einträge enthalten expiresAt und werden periodisch bereinigt.
|
||||
- Batch‑Operationen: multiGet/multiSet/multiDelete nutzen Redis‑Batching/Pipelining, lokal wird konsistent gespiegelt.
|
||||
- Größenbegrenzung Local Cache (neu): Wenn localCacheMaxSize gesetzt ist, werden bei Überschreitung die am längsten nicht mehr modifizierten Einträge aus dem lokalen Cache entfernt (LRM – least recently modified). Dadurch bleibt der lokale Fallback speichereffizient.
|
||||
- Periodische Aufgaben (@Scheduled):
|
||||
- Verbindungsprüfung: fixedDelayString = "${redis.connection-check-interval:10000}"
|
||||
- Lokale Bereinigung: fixedDelayString = "${redis.local-cache-cleanup-interval:60000}"
|
||||
- Dirty‑Sync: fixedDelayString = "${redis.sync-interval:300000}"
|
||||
- Metriken‑Log: fixedDelayString = "${redis.metrics-log-interval:300000}"
|
||||
|
||||
## Schlüsselfunktionen
|
||||
Wichtige Robustheitsdetails
|
||||
- Alle Redis‑Operationen fangen RedisConnectionFailureException ab und schalten den ConnectionState auf DISCONNECTED. Beim nächsten erfolgreichen Zugriff wird CONNECTED gesetzt und eine Synchronisation der dirty keys ausgelöst.
|
||||
- multiSet setzt TTLs bei Bedarf per Pipeline nach (pExpire); einzelne set‑Operationen nutzen expire via Duration.
|
||||
|
||||
### Core Features
|
||||
* **Offline-Fähigkeit & Resilienz:** Das Modul verfügt über einen In-Memory-Cache, der bei einem Ausfall der Redis-Verbindung als Fallback dient. Schreib-Operationen werden lokal als "dirty" markiert und automatisch mit Redis synchronisiert, sobald die Verbindung wiederhergestellt ist.
|
||||
* **Idiomatische Kotlin-API:** Bietet neben der Standard-API auch ergonomische Erweiterungsfunktionen mit `reified`-Typen für eine saubere und typsichere Verwendung in Kotlin-Code (`cache.get<User>("key")`).
|
||||
* **Projekweite Konsistenz:** Verwendet `kotlin.time.Duration` und `kotlin.time.Instant` für eine einheitliche Handhabung von Zeit- und Dauer-Angaben im gesamten Projekt.
|
||||
* **Automatisierte Verbindungsüberwachung:** Überprüft periodisch den Zustand der Redis-Verbindung und informiert Listener über Statusänderungen (`CONNECTED`, `DISCONNECTED`).
|
||||
|
||||
### Enterprise Features
|
||||
* **Multi-Tenant-Fähigkeit:** Key-Prefixes ermöglichen vollständige Isolation zwischen verschiedenen Anwendungen
|
||||
* **Konfigurierbare Kompression:** Automatische Kompression für große Datenstrukturen (konfigurierbar ab 1KB)
|
||||
* **Performance-Optimierung:** 5.000+ gleichzeitige Operationen mit >95% Erfolgsrate
|
||||
* **Unicode-Vollunterstützung:** Internationale Deployment-fähig mit Emojis, Umlauten, Chinesisch, Arabisch
|
||||
* **10MB+ Objektgrößen:** Automatische Kompression und Übertragung sehr großer Objekte
|
||||
|
||||
### Enhanced Monitoring & Operations (NEW)
|
||||
* **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit Erfolgsraten
|
||||
* **Strukturierte Metrics-Logging:** Periodische Performance-Reports mit detaillierten Metriken
|
||||
* **Cache Warming Utilities:** Produktions-bereite Warming-Strategien für optimale Performance
|
||||
* **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung
|
||||
* **Advanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
|
||||
|
||||
## Verwendung
|
||||
|
||||
Ein Microservice bindet `:infrastructure:cache:redis-cache` als Abhängigkeit ein und lässt sich das `DistributedCache`-Interface per Dependency Injection geben.
|
||||
|
||||
### Grundlegende Verwendung
|
||||
## Verwendung (Beispiele)
|
||||
Einbinden: Projekte hängen gegen :infrastructure:cache:redis-cache und injizieren DistributedCache.
|
||||
|
||||
Lesen/Schreiben mit TTL
|
||||
```kotlin
|
||||
@Service
|
||||
class MasterdataService(
|
||||
private val cache: DistributedCache // Nur das Interface wird verwendet!
|
||||
) {
|
||||
fun findCountryById(id: String): Country? {
|
||||
val cacheKey = "country:$id"
|
||||
|
||||
// 1. Versuche, aus dem Cache zu lesen (typsicher und sauber)
|
||||
val cachedCountry = cache.get<Country>(cacheKey)
|
||||
if (cachedCountry != null) {
|
||||
return cachedCountry
|
||||
}
|
||||
|
||||
// 2. Wenn nicht im Cache, aus der DB lesen
|
||||
val dbCountry = countryRepository.findById(id)
|
||||
|
||||
// 3. Ergebnis in den Cache schreiben für zukünftige Anfragen
|
||||
dbCountry?.let {
|
||||
cache.set(cacheKey, it, ttl = 1.hours) // Cache für 1 Stunde
|
||||
}
|
||||
return dbCountry
|
||||
}
|
||||
val user = cache.get<User>("user:42")
|
||||
if (user == null) {
|
||||
val loaded = userRepository.findById("42") ?: return null
|
||||
cache.set("user:42", loaded, ttl = 1.hours)
|
||||
}
|
||||
```
|
||||
|
||||
### Erweiterte Verwendung
|
||||
|
||||
Batch‑Lesezugriff
|
||||
```kotlin
|
||||
// Batch-Operationen für bessere Performance
|
||||
val userIds = listOf("user:1", "user:2", "user:3")
|
||||
val cachedUsers = cache.multiGet<User>(userIds)
|
||||
|
||||
// Bulk-Updates
|
||||
val newUsers = mapOf(
|
||||
"user:4" to User("Alice"),
|
||||
"user:5" to User("Bob")
|
||||
)
|
||||
cache.multiSet(newUsers, ttl = 30.minutes)
|
||||
|
||||
// Connection-State-Monitoring
|
||||
cache.registerConnectionListener(object : ConnectionStateListener {
|
||||
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
|
||||
logger.info { "Cache connection state changed to: $newState" }
|
||||
}
|
||||
})
|
||||
|
||||
// Performance Monitoring (NEW)
|
||||
val metrics = cache.getPerformanceMetrics()
|
||||
logger.info { "Current performance: ${metrics["successRate"]} success rate, ${metrics["totalOperations"]} operations" }
|
||||
|
||||
// Health Status Checking (NEW)
|
||||
val health = cache.getHealthStatus()
|
||||
if (health["healthy"] as Boolean) {
|
||||
logger.info { "Cache is healthy with ${health["successRate"]} success rate" }
|
||||
} else {
|
||||
logger.warn { "Cache health issue detected: ${health["connectionState"]}" }
|
||||
}
|
||||
|
||||
// Cache Warming (NEW)
|
||||
// Individual key warming with data loader
|
||||
cache.warmCache(listOf("user:1", "user:2", "user:3")) { key ->
|
||||
userService.loadUser(key.substringAfter(":"))
|
||||
}
|
||||
|
||||
// Bulk cache warming
|
||||
val preloadData = mapOf(
|
||||
"config:app" to applicationConfig,
|
||||
"config:features" to featureFlags
|
||||
)
|
||||
cache.warmCacheBulk(preloadData, ttl = 1.hours)
|
||||
val ids = listOf("user:1", "user:2", "user:3")
|
||||
val map = cache.multiGet<User>(ids)
|
||||
```
|
||||
|
||||
## Test-Suite: Vollständige Produktionsabdeckung
|
||||
|
||||
### Test-Übersicht
|
||||
- ✅ **39 Tests total** (12 Basis + 27 erweiterte Tests)
|
||||
- ✅ **6 Test-Klassen** vollständig optimiert
|
||||
- ✅ **94.7% Success Rate** (36/38 erfolgreich)
|
||||
- ✅ **Professionelles SLF4J/kotlin-logging** durchgängig
|
||||
|
||||
### Test-Kategorien
|
||||
|
||||
| Kategorie | Tests | Zweck | Status |
|
||||
|-----------|-------|-------|---------|
|
||||
| **Basis-Funktionalität** | 12 | Core Cache Operations | ✅ Stabil |
|
||||
| **Performance & Load** | 3 | Gleichzeitige Zugriffe, Speicherdruck, Bulk-Ops | ✅ Optimiert |
|
||||
| **Edge Cases** | 6 | Serialisierung, große Daten, Unicode, null-Werte | ✅ Robust |
|
||||
| **Resilience** | 6 | Timeouts, Verbindungsausfälle, Wiederverbindung | ✅ Resilient |
|
||||
| **Configuration** | 6 | TTL, Kompression, Prefixes, Cache-Größen | ✅ Flexibel |
|
||||
| **Integration** | 6 | Cross-Instance, Monitoring, Produktions-Szenarien | ✅ Produktionsready |
|
||||
|
||||
### Detaillierte Test-Abdeckung
|
||||
|
||||
#### Performance & Load Tests
|
||||
- **`test cache performance with high concurrent access`**: 100 Coroutines mit je 50 Operationen (5.000 gleichzeitige Ops)
|
||||
- **`test cache behavior under memory pressure`**: 500 Einträge mit kleinem Local-Cache (100)
|
||||
- **`test bulk operations performance`**: 1000 Einträge mit multiSet/multiGet (1000+ Einträge/Sekunde)
|
||||
|
||||
#### Edge Cases & Error Handling
|
||||
- **`test serialization with problematic objects`**: Zirkuläre Referenzen, tiefe Verschachtelung (50 Ebenen)
|
||||
- **`test cache with extremely large values`**: 10MB Strings mit automatischer Kompression
|
||||
- **`test special characters and unicode`**: Emojis, Umlaute, Chinesisch, Arabisch, gemischte Inhalte
|
||||
- **`test cache with null and empty values`**: Leere Strings, null-Felder, leere Collections
|
||||
- **`test complex nested objects`**: Verschachtelte Maps mit Listen und Metadaten
|
||||
- **`test malformed data scenarios`**: Nicht-existierende Keys, gemischte Batch-Operationen
|
||||
|
||||
#### Resilience & Timeout Tests
|
||||
- **`test connection timeout scenarios`**: 5-Sekunden-Delays simuliert, max. 10s Timeout
|
||||
- **`test partial Redis failures`**: Intermittierende Ausfälle alle 3 Operationen
|
||||
- **`test network partitioning simulation`**: Komplette Netzwerktrennung mit Offline-Mode
|
||||
- **`test reconnection and synchronization`**: Automatische Wiederverbindung mit Dirty-Key-Sync
|
||||
- **`test connection state listener notifications`**: Listener-Management und State-Tracking
|
||||
- **`test Redis restart simulation`**: Neustart-Szenarien mit lokaler Pufferung
|
||||
|
||||
#### Configuration Tests
|
||||
- **`test different cache configurations`**: Performance-, Storage- und Minimal-Configs
|
||||
- **`test compression threshold behavior`**: 50-Byte-Schwelle konfigurierbar getestet
|
||||
- **`test key prefix functionality`**: Vollständige Isolation zwischen "app1", "app2", ""
|
||||
- **`test TTL configuration variations`**: null, 100ms, 30min TTLs flexibel konfigurierbar
|
||||
- **`test offline mode configuration`**: Ein/Ausschalten des Offline-Modus
|
||||
- **`test local cache size limits`**: 3 vs. unlimited vs. 1000 Einträge mit Redis-Fallback
|
||||
|
||||
#### Integration & Monitoring Tests
|
||||
- **`test connection state listener functionality`**: Professionelles Listener-Management
|
||||
- **`test different Redis configurations`**: Multi-Config-Isolation und Cross-Compatibility
|
||||
- **`test cache warming scenarios`**: Bulk- (1000 Einträge <100ms), graduelle und selektive Vorwärmung
|
||||
- **`test metrics and monitoring integration`**: State-Tracking, Dirty-Keys-Monitoring, Performance-Metriken
|
||||
- **`test cross-instance synchronization`**: Multi-Instance-Datenaustausch mit kleinen Delays
|
||||
- **`test production-like scenarios`**: User-Sessions (1000), Config-Caching, API-Responses (100)
|
||||
|
||||
### Produktionstauglichkeits-Validierung
|
||||
|
||||
#### ✅ **Performance-Benchmarks bestanden:**
|
||||
- **5.000+ gleichzeitige Operationen** mit >95% Erfolgsrate
|
||||
- **Sub-100ms Performance** für Standard-Operationen
|
||||
- **1000+ Einträge/Sekunde** bei Bulk-Operationen
|
||||
- **Cache-Warming: 1000 Einträge in <100ms** möglich
|
||||
|
||||
#### ✅ **Robustheit validiert:**
|
||||
- **Graceful Degradation** bei allen Fehlersituationen
|
||||
- **Automatische Wiederverbindung** mit Dirty-Key-Synchronisation
|
||||
- **Speicher-effiziente** Local-Cache-Verwaltung mit Redis-Fallback
|
||||
- **Cross-Instance-Synchronisation** zwischen Services funktionsfähig
|
||||
|
||||
#### ✅ **Enterprise-Features getestet:**
|
||||
- **10MB+ Objektgrößen** mit automatischer Kompression
|
||||
- **Unicode-Vollunterstützung** für internationale Deployments
|
||||
- **Multi-Tenant-Fähigkeit** durch Key-Prefixes mit perfekter Isolation
|
||||
- **Vollständige Offline-Fähigkeit** bei Redis-Ausfällen
|
||||
|
||||
## Logging-Architektur: Professionelle Standards
|
||||
|
||||
### Implementierte Standards
|
||||
Das gesamte Modul verwendet professionelle SLF4J/kotlin-logging Standards:
|
||||
|
||||
Bulk‑Schreiben
|
||||
```kotlin
|
||||
// Konsistentes Pattern in allen Klassen:
|
||||
companion.object {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
}
|
||||
|
||||
// Strukturierte Logging-Calls:
|
||||
logger.info { "Cache operation completed with metrics: $metrics" }
|
||||
logger.warn { "Connection state changed: $oldState -> $newState" }
|
||||
logger.debug { "Processing batch of $size entries with config: $config" }
|
||||
cache.multiSet(mapOf(
|
||||
"cfg:app" to appConfig,
|
||||
"cfg:features" to features
|
||||
), ttl = 30.minutes)
|
||||
```
|
||||
|
||||
### Log-Level-Richtlinien
|
||||
|
||||
| Level | Verwendung | Beispiel |
|
||||
|-------|------------|----------|
|
||||
| **INFO** | Cache-Operationen, State-Changes, Metriken | `logger.info { "Performance test completed: $metrics" }` |
|
||||
| **DEBUG** | Detaillierte Ablaufinformationen | `logger.debug { "Processing batch of $size entries" }` |
|
||||
| **WARN** | Verbindungsprobleme, Performance-Issues | `logger.warn { "Success rate below threshold: $rate" }` |
|
||||
| **ERROR** | Kritische Fehler, Serialisierungsprobleme | `logger.error { "Unexpected exception in cache operation" }` |
|
||||
|
||||
### Logback-Konfiguration
|
||||
```xml
|
||||
<!-- Strukturierte Console-Ausgaben -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Cache-spezifische Logger -->
|
||||
<logger name="at.mocode.infrastructure.cache" level="DEBUG" />
|
||||
<logger name="RedisDistributedCachePerformanceTest" level="INFO" />
|
||||
|
||||
<!-- Reduzierte Verbosity für externe Komponenten -->
|
||||
<logger name="org.testcontainers" level="WARN" />
|
||||
<logger name="io.lettuce" level="WARN" />
|
||||
```
|
||||
|
||||
## Dependency-Management: Single Source of Truth
|
||||
|
||||
### Vollständige SINGLE SOURCE OF TRUTH Konformität
|
||||
Alle Dependencies verwenden jetzt zentrale `libs.versions.toml` Verwaltung:
|
||||
|
||||
```toml
|
||||
# Zentrale Versionen
|
||||
[versions]
|
||||
logback = "1.5.13"
|
||||
kotlinLogging = "3.0.5"
|
||||
|
||||
[libraries]
|
||||
kotlin-logging-jvm = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlinLogging" }
|
||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||
logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
|
||||
|
||||
[bundles]
|
||||
redis-cache = ["spring-boot-starter-data-redis", "lettuce-core", "jackson-module-kotlin", "jackson-datatype-jsr310"]
|
||||
testing-jvm = ["junit-jupiter-api", "junit-jupiter-engine", "mockk", "assertj-core", "kotlinx-coroutines-test"]
|
||||
```
|
||||
|
||||
### Build-Konfiguration
|
||||
```kotlin
|
||||
// redis-cache/build.gradle.kts - VOLLSTÄNDIG OPTIMIERT
|
||||
dependencies {
|
||||
// Alle Dependencies über libs-Referenzen
|
||||
implementation(libs.bundles.redis.cache)
|
||||
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.bundles.testing.jvm)
|
||||
testImplementation(libs.kotlin.test)
|
||||
testImplementation(libs.kotlin.logging.jvm)
|
||||
testImplementation(libs.logback.classic)
|
||||
testImplementation(libs.logback.core)
|
||||
}
|
||||
```
|
||||
|
||||
## Konfiguration & Deployment
|
||||
|
||||
### Cache-Konfigurationen für verschiedene Umgebungen
|
||||
|
||||
#### Performance-optimiert (High-Throughput)
|
||||
```kotlin
|
||||
val performanceConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "perf",
|
||||
defaultTtl = 5.minutes,
|
||||
localCacheMaxSize = 50000,
|
||||
compressionEnabled = false, // Für maximale Geschwindigkeit
|
||||
compressionThreshold = Int.MAX_VALUE
|
||||
)
|
||||
```
|
||||
|
||||
#### Storage-optimiert (Kompression)
|
||||
```kotlin
|
||||
val storageConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "storage",
|
||||
defaultTtl = 7.days,
|
||||
localCacheMaxSize = 1000,
|
||||
compressionEnabled = true,
|
||||
compressionThreshold = 100 // Kompression ab 100 Bytes
|
||||
)
|
||||
```
|
||||
|
||||
#### Minimal (Entwicklung)
|
||||
```kotlin
|
||||
val minimalConfig = DefaultCacheConfiguration(
|
||||
keyPrefix = "dev",
|
||||
defaultTtl = null, // Kein TTL
|
||||
localCacheMaxSize = null, // Unbegrenzt
|
||||
offlineModeEnabled = false // Für Entwicklung optional
|
||||
)
|
||||
```
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Connection-State-Monitoring
|
||||
Verbindungsstatus überwachen
|
||||
```kotlin
|
||||
cache.registerConnectionListener(object : ConnectionStateListener {
|
||||
override fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant) {
|
||||
when (newState) {
|
||||
ConnectionState.CONNECTED -> {
|
||||
logger.info { "Cache reconnected at $timestamp" }
|
||||
metricsCollector.increment("cache.reconnects")
|
||||
}
|
||||
ConnectionState.DISCONNECTED -> {
|
||||
logger.warn { "Cache disconnected at $timestamp" }
|
||||
alerting.sendAlert("Cache offline", "Redis connection lost")
|
||||
}
|
||||
ConnectionState.RECONNECTING -> {
|
||||
logger.info { "Cache attempting reconnection at $timestamp" }
|
||||
}
|
||||
}
|
||||
logger.info("Cache connection state: $newState at $timestamp")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Performance-Metriken
|
||||
```kotlin
|
||||
// Beispiel für strukturierte Metriken-Sammlung
|
||||
val metrics = mapOf(
|
||||
"totalOperations" to totalOperations,
|
||||
"successRate" to successRate,
|
||||
"averageLatency" to averageLatency,
|
||||
"operationsPerSecond" to opsPerSec,
|
||||
"dirtyKeysCount" to cache.getDirtyKeys().size,
|
||||
"connectionState" to cache.getConnectionState()
|
||||
)
|
||||
logger.info { "Cache performance metrics: $metrics" }
|
||||
```
|
||||
## Konfiguration
|
||||
DefaultCacheConfiguration bietet sinnvolle Defaults. Relevante Properties (optional via Spring @Scheduled Platzhalter):
|
||||
- redis.connection-check-interval: ms für Verbindungsprüfung (Default 10000)
|
||||
- redis.local-cache-cleanup-interval: ms für lokale Bereinigung (Default 60000)
|
||||
- redis.sync-interval: ms für Synchronisationsläufe (Default 300000)
|
||||
- redis.metrics-log-interval: ms für periodisches Metriken‑Logging (Default 300000)
|
||||
|
||||
### CI/CD Integration
|
||||
```yaml
|
||||
# Beispiel für GitHub Actions
|
||||
- name: Run Cache Tests with Structured Logging
|
||||
run: |
|
||||
./gradlew :infrastructure:cache:redis-cache:test --info
|
||||
Hinweise
|
||||
- keyPrefix sollte pro Service gesetzt werden (z. B. "masterdata"), um Kollisionen zu vermeiden.
|
||||
- localCacheMaxSize begrenzt die Größe des lokalen Fallback‑Caches. Bei null ist die Größe unbegrenzt.
|
||||
|
||||
# Log-Level für verschiedene Umgebungen:
|
||||
# Development: DEBUG (alle Details)
|
||||
# CI/CD: INFO (wichtige Ereignisse)
|
||||
# Production: WARN (nur Probleme)
|
||||
```
|
||||
## Betrieb & Monitoring
|
||||
- Health‑Infos: getHealthStatus() liefert eine einfache Einschätzung basierend auf ConnectionState und Erfolgsrate der Operationen.
|
||||
- Metriken: getPerformanceMetrics() liefert einfache Kennzahlen (Operations, Success‑Rate, Größe lokaler Cache, Anzahl dirty Keys). Periodisches Logging per @Scheduled möglich.
|
||||
- Cache Warming: warmCache(keys, loader) und warmCacheBulk(map) helfen, Hot‑Keys/gefragte Konfigurationen beim Start vorzuwärmen.
|
||||
|
||||
## Best Practices & Empfehlungen
|
||||
## Grenzen & bekannte Punkte
|
||||
- Kompression ist im Serializer implementiert; die konfigurierbaren Flags/Schwellenwerte sind derzeit nicht dynamisch an/aus‑geschaltet.
|
||||
- Offline‑Modus: Die Konfiguration offlineModeEnabled ist vorhanden; die Implementierung betreibt den lokalen Fallback standardmäßig bei Verbindungsproblemen. Eine harte Deaktivierung dieses Verhaltens ist aktuell nicht verdrahtet.
|
||||
|
||||
### Produktionseinsatz-Empfehlungen
|
||||
## Changelog (Kurz)
|
||||
- 2025‑09‑03: Fehlerbehebungen für @Scheduled‑Platzhalter, korrektes Logging im Cache‑Warming, lokale Cache‑Größenbegrenzung (LRM‑Eviction) hinzugefügt. Dokumentation aktualisiert (diese Datei).
|
||||
|
||||
#### **Priorität HOCH (sofort umsetzbar):**
|
||||
1. **Performance-Monitoring:** Strukturierte Logs für Produktions-Metriken nutzen
|
||||
2. **Connection-State-Überwachung:** Listener für Alerting bei Redis-Ausfällen einrichten
|
||||
3. **Cache-Warming:** Graduelle Warming-Strategien beim Service-Start implementieren
|
||||
|
||||
#### **Priorität MITTEL (mittelfristig):**
|
||||
1. **Kompression-Tuning:** Threshold je nach Datenanforderungen anpassen (Standard: 1KB)
|
||||
2. **Local-Cache-Größen:** Je nach verfügbarem RAM pro Service optimieren
|
||||
3. **TTL-Strategien:** Spezifische TTLs für verschiedene Datentypen definieren
|
||||
|
||||
#### **Priorität NIEDRIG (langfristig):**
|
||||
1. **Advanced Monitoring:** Integration mit Micrometer/Prometheus für detaillierte Metriken
|
||||
2. **Multi-Redis-Cluster:** Unterstützung für Redis-Cluster-Konfigurationen
|
||||
3. **Erweiterte Kompression:** Alternative Algorithmen (LZ4, Snappy) evaluieren
|
||||
|
||||
### Entwickler-Guidelines
|
||||
|
||||
#### **DO's ✅**
|
||||
- Verwende `cache.get<Type>(key)` für typsichere Operationen
|
||||
- Implementiere Connection-State-Listener für kritische Services
|
||||
- Nutze Batch-Operationen (`multiGet`, `multiSet`) für bessere Performance
|
||||
- Verwende aussagekräftige Key-Prefixes für Multi-Tenant-Szenarien
|
||||
- Teste Cache-Warming-Strategien in Integration-Tests
|
||||
|
||||
#### **DON'Ts ❌**
|
||||
- Niemals sensible Daten ohne Verschlüsselung cachen
|
||||
- Vermeide sehr große TTLs ohne Begründung (>24h)
|
||||
- Keine Hard-coded Cache-Keys - verwende Key-Factories
|
||||
- Vermeide Blocking-Operations in Connection-State-Listeners
|
||||
- Keine println() in Cache-bezogenem Code - verwende Logger
|
||||
|
||||
### Typische Anwendungsszenarien
|
||||
|
||||
#### User-Session-Caching
|
||||
```kotlin
|
||||
// TTL = Session-Timeout
|
||||
cache.set("user:session:${sessionId}", userSession, ttl = 30.minutes)
|
||||
```
|
||||
|
||||
#### API-Response-Caching
|
||||
```kotlin
|
||||
// Kurze TTL für häufig ändernde Daten
|
||||
cache.set("api:response:${endpoint}", response, ttl = 5.minutes)
|
||||
```
|
||||
|
||||
#### Configuration-Caching
|
||||
```kotlin
|
||||
// Lange TTL für stabile Konfiguration
|
||||
cache.set("config:${service}", config, ttl = 1.hours)
|
||||
```
|
||||
|
||||
#### Database-Result-Caching
|
||||
```kotlin
|
||||
// Mittlere TTL für Datenbankabfragen
|
||||
cache.set("db:${query.hash()}", results, ttl = 15.minutes)
|
||||
```
|
||||
|
||||
## Migration & Upgrade-Pfad
|
||||
|
||||
### Von Version < 1.0
|
||||
1. **Dependencies aktualisieren:** Umstellung auf libs.versions.toml
|
||||
2. **Logging modernisieren:** println() → SLF4J/kotlin-logging
|
||||
3. **Test-Suite erweitern:** Neue Test-Kategorien hinzufügen
|
||||
4. **Konfiguration migrieren:** Neue DefaultCacheConfiguration verwenden
|
||||
|
||||
### Backwards Compatibility
|
||||
- ✅ Alle bestehenden API-Calls funktionieren weiterhin
|
||||
- ✅ Bestehende Konfigurationen sind kompatibel
|
||||
- ✅ Migration kann schrittweise erfolgen
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2025-08-15 - Enhanced Monitoring & Operations Update v2.1
|
||||
- ✅ **Real-time Performance Metrics:** Automatisches Tracking aller Cache-Operationen mit detaillierter Erfolgsraten-Überwachung
|
||||
- ✅ **Strukturierte Metrics-Logging:** Periodische Performance-Reports alle 5 Minuten mit umfassenden Metriken
|
||||
- ✅ **Cache Warming Utilities:** Produktions-bereite Warming-Strategien mit Individual- und Bulk-Operations
|
||||
- ✅ **Health Status Monitoring:** Umfassende Gesundheitschecks mit automatischer Status-Bewertung (>90% Erfolgsrate)
|
||||
- ✅ **Enhanced Connection Tracking:** Erweiterte Verbindungsüberwachung mit detaillierten Zustandsinformationen
|
||||
- ✅ **Production-Ready Monitoring:** Integration hooks für Enterprise-Monitoring-Systeme
|
||||
- ✅ **Performance Optimization:** Verbesserte Metriken-Sammlung ohne Performance-Impact
|
||||
|
||||
### 2025-08-14 - Major Update v2.0
|
||||
- ✅ **Vollständige Test-Suite-Erweiterung:** Von 12 auf 39 Tests (94.7% Success Rate)
|
||||
- ✅ **Professionelle Logging-Architektur:** Komplette Umstellung auf SLF4J/kotlin-logging
|
||||
- ✅ **SINGLE SOURCE OF TRUTH:** Alle Dependencies über libs.versions.toml
|
||||
- ✅ **Edge-Cases-Korrekturen:** Serialisierungstests von 71.4% auf 100% Success Rate
|
||||
- ✅ **Enterprise-Features validiert:** 5.000+ concurrent operations, 10MB+ objects
|
||||
- ✅ **Produktionstauglichkeit erreicht:** Vollständige Performance-, Resilience- und Integration-Tests
|
||||
- ✅ **Erweiterte Konfigurierbarkeit:** Performance-, Storage- und Development-Presets
|
||||
- ✅ **Advanced Monitoring:** Connection-State-Listener und strukturierte Metriken
|
||||
|
||||
### 2025-08-14 - Previous
|
||||
- **Bug Fix:** Compiler-Warnungen in `JacksonCacheSerializer` bezüglich identity-sensitiver Operationen behoben
|
||||
- **Verbesserung:** Objects.equals() für sichere nullable Instant-Vergleiche
|
||||
|
||||
## Testing-Strategie: Zweistufig & Umfassend
|
||||
|
||||
### Integrationstests mit Testcontainers
|
||||
Die Kernfunktionalität wird gegen eine echte Redis-Datenbank getestet, die zur Laufzeit in einem Docker-Container gestartet wird. Dies garantiert 100%ige Kompatibilität und realistische Performance-Messungen.
|
||||
|
||||
### Unit-Tests mit MockK
|
||||
Die komplexe Logik der Offline-Fähigkeit und Synchronisation wird durch das Mocking des RedisTemplate getestet. So können Verbindungsausfälle, Timeouts und Netzwerkpartitionierung zuverlässig simuliert werden.
|
||||
|
||||
### End-to-End Produktionstests
|
||||
Production-like Scenarios testen realistische Anwendungsfälle:
|
||||
- User-Session-Management (1000 Sessions)
|
||||
- Configuration-Caching mit verschiedenen TTLs
|
||||
- API-Response-Caching (100 Endpoints)
|
||||
- Cross-Service-Kommunikation
|
||||
|
||||
## Fazit & Status
|
||||
|
||||
Das **Infrastructure/Cache-Modul** ist **vollständig produktionsbereit** und erfüllt alle Enterprise-Anforderungen:
|
||||
|
||||
- ✅ **94.7% Test Success Rate** mit 39 umfassenden Tests
|
||||
- ✅ **Professionelle Logging-Architektur** durchgängig etabliert
|
||||
- ✅ **Enterprise-Performance** validiert (5.000+ concurrent ops)
|
||||
- ✅ **Vollständige Resilience** bei Netzwerk- und Redis-Ausfällen
|
||||
- ✅ **SINGLE SOURCE OF TRUTH** für alle Dependencies
|
||||
- ✅ **Internationale Deployment-Fähigkeit** mit Unicode-Support
|
||||
- ✅ **Advanced Monitoring** mit Connection-State-Tracking
|
||||
- ✅ **Multi-Tenant-Capable** durch Key-Prefix-Isolation
|
||||
|
||||
**Empfehlung: ✅ BEREIT FÜR PRODUKTIONSEINSATZ**
|
||||
|
||||
Das Modul kann sofort in produktiven Umgebungen eingesetzt werden. Die umfassende Test-Suite und professionelle Architektur gewährleisten höchste Zuverlässigkeit und Performance.
|
||||
## Fazit
|
||||
Das Cache‑Modul bietet eine klare, wiederverwendbare Cache‑Schnittstelle mit einer robusten Redis‑Implementierung. Es unterstützt TTLs, Batch‑Operationen, lokalen Fallback bei Ausfällen und liefert einfache, praxistaugliche Betriebsinformationen. Mit keyPrefix und lokalen Limits ist der Einsatz in Multi‑Service‑Umgebungen unkompliziert und stabil.
|
||||
|
|
|
|||
16
infrastructure/cache/cache-api/build.gradle.kts
vendored
16
infrastructure/cache/cache-api/build.gradle.kts
vendored
|
|
@ -5,6 +5,12 @@ plugins {
|
|||
alias(libs.plugins.kotlin.jvm)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
// Erlaubt die Verwendung der kotlin.time API im gesamten Modul
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
|
|
@ -12,11 +18,15 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen.
|
||||
implementation(platform(projects.platform.platformBom))
|
||||
// Stellt gemeinsame Abhängigkeiten wie Logging bereit.
|
||||
implementation(projects.platform.platformDependencies)
|
||||
api(platform(projects.platform.platformBom))
|
||||
// Stellt gemeinsame Abhängigkeiten wie Logging bereit und exportiert sie für Konsumenten der API.
|
||||
api(projects.platform.platformDependencies)
|
||||
|
||||
// Stellt Test-Abhängigkeiten bereit.
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
|
|
|
|||
|
|
@ -1,56 +1,56 @@
|
|||
package at.mocode.infrastructure.cache.api
|
||||
|
||||
/**
|
||||
* Interface for serializing and deserializing cache entries.
|
||||
* Schnittstelle zum Serialisieren und Deserialisieren von Cache-Einträgen.
|
||||
*/
|
||||
interface CacheSerializer {
|
||||
/**
|
||||
* Serializes a value to a byte array.
|
||||
* Serialisiert einen Wert zu einem Byte-Array.
|
||||
*
|
||||
* @param value The value to serialize
|
||||
* @return The serialized value as a byte array
|
||||
* @param value Der zu serialisierende Wert
|
||||
* @return Der serialisierte Wert als Byte-Array
|
||||
*/
|
||||
fun <T : Any> serialize(value: T): ByteArray
|
||||
|
||||
/**
|
||||
* Deserializes a byte array to a value.
|
||||
* Deserialisiert ein Byte-Array zu einem Wert.
|
||||
*
|
||||
* @param bytes The byte array to deserialize
|
||||
* @param clazz The class of the value to deserialize to
|
||||
* @return The deserialized value
|
||||
* @param bytes Das zu deserialisierende Byte-Array
|
||||
* @param clazz Die Zielklasse des zu deserialisierenden Werts
|
||||
* @return Der deserialisierte Wert
|
||||
*/
|
||||
fun <T : Any> deserialize(bytes: ByteArray, clazz: Class<T>): T
|
||||
|
||||
/**
|
||||
* Serializes a cache entry to a byte array.
|
||||
* Serialisiert einen Cache-Eintrag zu einem Byte-Array.
|
||||
*
|
||||
* @param entry The cache entry to serialize
|
||||
* @return The serialized cache entry as a byte array
|
||||
* @param entry Der zu serialisierende Cache-Eintrag
|
||||
* @return Der serialisierte Cache-Eintrag als Byte-Array
|
||||
*/
|
||||
fun <T : Any> serializeEntry(entry: CacheEntry<T>): ByteArray
|
||||
|
||||
/**
|
||||
* Deserializes a byte array to a cache entry.
|
||||
* Deserialisiert ein Byte-Array zu einem Cache-Eintrag.
|
||||
*
|
||||
* @param bytes The byte array to deserialize
|
||||
* @param valueClass The class of the value in the cache entry
|
||||
* @return The deserialized cache entry
|
||||
* @param bytes Das zu deserialisierende Byte-Array
|
||||
* @param valueClass Die Klasse des Werts im Cache-Eintrag
|
||||
* @return Der deserialisierte Cache-Eintrag
|
||||
*/
|
||||
fun <T : Any> deserializeEntry(bytes: ByteArray, valueClass: Class<T>): CacheEntry<T>
|
||||
|
||||
/**
|
||||
* Compresses a byte array.
|
||||
* Komprimiert ein Byte-Array.
|
||||
*
|
||||
* @param bytes The byte array to compress
|
||||
* @return The compressed byte array
|
||||
* @param bytes Das zu komprimierende Byte-Array
|
||||
* @return Das komprimierte Byte-Array
|
||||
*/
|
||||
fun compress(bytes: ByteArray): ByteArray
|
||||
|
||||
/**
|
||||
* Decompresses a byte array.
|
||||
* Dekomprimiert ein Byte-Array.
|
||||
*
|
||||
* @param bytes The byte array to decompress
|
||||
* @return The decompressed byte array
|
||||
* @param bytes Das zu dekomprimierende Byte-Array
|
||||
* @return Das dekomprimierte Byte-Array
|
||||
*/
|
||||
fun decompress(bytes: ByteArray): ByteArray
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
package at.mocode.infrastructure.cache.api
|
||||
|
||||
/**
|
||||
* Kotlin-idiomatic extension function to retrieve a value from the cache
|
||||
* using reified types.
|
||||
* Kotlin-idiomatische Extension-Funktion, um einen Wert aus dem Cache zu lesen
|
||||
* – mit reified Typen.
|
||||
*
|
||||
* Example: `val user = cache.get<User>("user:123")`
|
||||
* Beispiel: `val user = cache.get<User>("user:123")`
|
||||
*/
|
||||
inline fun <reified T : Any> DistributedCache.get(key: String): T? {
|
||||
return this.get(key, T::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin-idiomatic extension function to retrieve multiple values from the cache
|
||||
* using reified types.
|
||||
* Kotlin-idiomatische Extension-Funktion, um mehrere Werte aus dem Cache zu lesen
|
||||
* – mit reified Typen.
|
||||
*
|
||||
* Example: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
|
||||
* Beispiel: `val users = cache.multiGet<User>(listOf("user:123", "user:124"))`
|
||||
*/
|
||||
inline fun <reified T : Any> DistributedCache.multiGet(keys: Collection<String>): Map<String, T> {
|
||||
return this.multiGet(keys, T::class.java)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,24 @@
|
|||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.spring)
|
||||
alias(libs.plugins.spring.boot)
|
||||
// Als Bibliothek benötigt dieses Modul das Spring Boot Plugin nicht.
|
||||
alias(libs.plugins.spring.dependencyManagement)
|
||||
}
|
||||
|
||||
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
|
||||
tasks.getByName("bootJar") {
|
||||
enabled = false
|
||||
// Stellt sicher, dass ein normales JAR gebaut wird (Bibliotheks-Modul).
|
||||
java {
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class RedisDistributedCache(
|
|||
// Store in a local cache
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
trackOperation(true)
|
||||
return entry.value
|
||||
|
|
@ -109,6 +110,7 @@ class RedisDistributedCache(
|
|||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
if (!isConnected()) {
|
||||
markDirty(key)
|
||||
|
|
@ -231,6 +233,7 @@ class RedisDistributedCache(
|
|||
// Store in a local cache
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[key] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
|
||||
// Add to result
|
||||
result[removePrefix(key)] = entry.value
|
||||
|
|
@ -263,6 +266,7 @@ class RedisDistributedCache(
|
|||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
localCache[prefixedKey] = entry as CacheEntry<Any>
|
||||
enforceLocalCacheSize()
|
||||
redisBatch[prefixedKey] = serializer.serializeEntry(entry)
|
||||
}
|
||||
|
||||
|
|
@ -437,6 +441,22 @@ class RedisDistributedCache(
|
|||
return if (config.keyPrefix.isEmpty()) key else key.substring(config.keyPrefix.length + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzwingt die maximale Größe des lokalen Caches, indem die am längsten nicht
|
||||
* mehr modifizierten Einträge entfernt werden.
|
||||
*/
|
||||
private fun enforceLocalCacheSize() {
|
||||
val max = config.localCacheMaxSize ?: return
|
||||
val overflow = localCache.size - max
|
||||
if (overflow <= 0) return
|
||||
val toEvict = localCache.entries
|
||||
.sortedBy { it.value.lastModifiedAt }
|
||||
.take(overflow)
|
||||
.map { it.key }
|
||||
toEvict.forEach { localCache.remove(it) }
|
||||
logger.debug("Evicted ${toEvict.size} entries to enforce local cache size limit $max")
|
||||
}
|
||||
|
||||
private fun handleConnectionFailure(e: Exception) {
|
||||
logger.warn("Redis connection failure: ${e.message}")
|
||||
setConnectionState(ConnectionState.DISCONNECTED)
|
||||
|
|
@ -468,9 +488,9 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Periodically check the connection to Redis.
|
||||
* Prüft periodisch die Verbindung zu Redis.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.connection-check-interval:10000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.connection-check-interval:10000}")
|
||||
fun checkConnection() {
|
||||
try {
|
||||
redisTemplate.hasKey("connection-test")
|
||||
|
|
@ -481,9 +501,9 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Periodically clean up expired entries from the local cache.
|
||||
* Bereinigt periodisch abgelaufene Einträge aus dem lokalen Cache.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.local-cache-cleanup-interval:60000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.local-cache-cleanup-interval:60000}")
|
||||
fun cleanupLocalCache() {
|
||||
val now = Clock.System.now()
|
||||
val expiredKeys = localCache.entries
|
||||
|
|
@ -498,9 +518,9 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Periodically synchronize dirty keys when connected.
|
||||
* Synchronisiert periodisch schmutzige Schlüssel, sobald verbunden.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.sync-interval:300000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.sync-interval:300000}")
|
||||
fun scheduledSync() {
|
||||
if (isConnected() && dirtyKeys.isNotEmpty()) {
|
||||
synchronize(null)
|
||||
|
|
@ -512,7 +532,7 @@ class RedisDistributedCache(
|
|||
//
|
||||
|
||||
/**
|
||||
* Track a cache operation for metrics
|
||||
* Zeichnet eine Cache-Operation für Metriken auf.
|
||||
*/
|
||||
private fun trackOperation(success: Boolean) {
|
||||
synchronized(this) {
|
||||
|
|
@ -522,7 +542,7 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Get current performance metrics
|
||||
* Liefert aktuelle Performance-Metriken.
|
||||
*/
|
||||
fun getPerformanceMetrics(): Map<String, Any> {
|
||||
val now = Clock.System.now()
|
||||
|
|
@ -543,9 +563,9 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Log performance metrics (called periodically)
|
||||
* Loggt Performance-Metriken (periodisch aufgerufen).
|
||||
*/
|
||||
@Scheduled(fixedDelayString = $$"${redis.metrics-log-interval:300000}")
|
||||
@Scheduled(fixedDelayString = "\${redis.metrics-log-interval:300000}")
|
||||
fun logPerformanceMetrics() {
|
||||
val metrics = getPerformanceMetrics()
|
||||
logger.info("Cache performance metrics: $metrics")
|
||||
|
|
@ -553,7 +573,7 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Cache warming utility - preloads specified keys
|
||||
* Cache-Warming-Helfer – lädt angegebene Schlüssel vor.
|
||||
*/
|
||||
fun warmCache(keys: Collection<String>, dataLoader: (String) -> Any?) {
|
||||
logger.info("Starting cache warming for ${keys.size} keys")
|
||||
|
|
@ -571,11 +591,11 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
val duration = Clock.System.now() - startTime
|
||||
logger.info("Cache warming completed: $warmedCount/$${keys.size} keys loaded in $duration")
|
||||
logger.info("Cache warming completed: $warmedCount/${keys.size} keys loaded in $duration")
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk cache warming with batch operations
|
||||
* Bulk-Cache-Warming mit Batch-Operationen.
|
||||
*/
|
||||
fun warmCacheBulk(keyDataMap: Map<String, Any>, ttl: Duration? = null) {
|
||||
logger.info("Starting bulk cache warming for ${keyDataMap.size} entries")
|
||||
|
|
@ -588,7 +608,7 @@ class RedisDistributedCache(
|
|||
}
|
||||
|
||||
/**
|
||||
* Get cache health status
|
||||
* Liefert den Cache-Gesundheitsstatus.
|
||||
*/
|
||||
fun getHealthStatus(): Map<String, Any> {
|
||||
val metrics = getPerformanceMetrics()
|
||||
|
|
|
|||
|
|
@ -12,49 +12,49 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
|
|||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Enhanced reactive security configuration for the Gateway.
|
||||
* Erweiterte reaktive Sicherheitskonfiguration für das Gateway.
|
||||
*
|
||||
* ARCHITECTURE OVERVIEW:
|
||||
* ARCHITEKTUR-ÜBERBLICK:
|
||||
* ======================
|
||||
* Diese Konfiguration stellt die grundlegende Sicherheits-Schicht für das Spring Cloud Gateway bereit.
|
||||
* Sie arbeitet zusammen mit mehreren weiteren Sicherheitskomponenten:
|
||||
*
|
||||
* 1. JwtAuthenticationFilter (GlobalFilter) – Validiert JWT-Tokens und authentifiziert Benutzer
|
||||
* 2. RateLimitingFilter (GlobalFilter) – Bietet IP-basiertes Rate-Limiting mit benutzerbezogenen Limits
|
||||
* 3. CorrelationIdFilter (GlobalFilter) – Fügt Request-Tracing-Fähigkeiten hinzu
|
||||
* 4. EnhancedLoggingFilter (GlobalFilter) – Liefert strukturiertes Request/Response-Logging
|
||||
*
|
||||
* SICHERHEITSSTRATEGIE:
|
||||
* =====================
|
||||
* This configuration establishes the foundational security layer for the Spring Cloud Gateway.
|
||||
* It works in conjunction with several other security components:
|
||||
* Das Gateway verwendet einen mehrschichtigen Sicherheitsansatz:
|
||||
* - Diese SecurityWebFilterChain liefert grundlegende Einstellungen (CORS, CSRF, Basis-Header)
|
||||
* - Der JwtAuthenticationFilter übernimmt die eigentliche Authentifizierung, wenn per Property aktiviert
|
||||
* - Die SecurityWebFilterChain bleibt permissiv (permitAll), damit der JWT-Filter den Zugriff steuert
|
||||
* - Rate-Limiting- und Logging-Filter liefern operative Sicherheit und Monitoring
|
||||
*
|
||||
* 1. JwtAuthenticationFilter (GlobalFilter) - Handles JWT token validation and user authentication
|
||||
* 2. RateLimitingFilter (GlobalFilter) - Provides IP-based rate limiting with user-aware limits
|
||||
* 3. CorrelationIdFilter (GlobalFilter) - Adds request tracing capabilities
|
||||
* 4. EnhancedLoggingFilter (GlobalFilter) - Provides structured request/response logging
|
||||
* ENTWURFSBEGRÜNDUNG:
|
||||
* ===================
|
||||
* - Während Tests ist Spring Security auf dem Classpath (testImplementation), was
|
||||
* die Auto-Konfiguration aktiviert und alle Endpunkte sperren kann, sofern keine SecurityWebFilterChain bereitgestellt wird
|
||||
* - Das Gateway erzwingt Authentifizierung über den JwtAuthenticationFilter (falls per Property aktiviert),
|
||||
* daher sollte die SecurityWebFilterChain permissiv bleiben und sich auf grundlegende Belange konzentrieren
|
||||
* - Explizite CORS-Konfiguration stellt eine korrekte Behandlung von Cross-Origin-Anfragen aus Web-Clients sicher
|
||||
* - Konfigurierbare Properties erlauben umgebungsspezifische Sicherheitseinstellungen ohne Codeänderungen
|
||||
* - CSRF-Schutz ist deaktiviert, da er für zustandslose JWT-basierte Authentifizierung nicht benötigt wird
|
||||
*
|
||||
* SECURITY STRATEGY:
|
||||
* ==================
|
||||
* The Gateway employs a layered security approach:
|
||||
* - This SecurityWebFilterChain provides foundational settings (CORS, CSRF, basic headers)
|
||||
* - JwtAuthenticationFilter handles actual authentication when enabled via property
|
||||
* - The SecurityWebFilterChain remains permissive (permitAll) to let the JWT filter control access
|
||||
* - Rate limiting and logging filters provide operational security and monitoring
|
||||
*
|
||||
* DESIGN RATIONALE:
|
||||
* CORS-INTEGRATION:
|
||||
* =================
|
||||
* - During tests, Spring Security is on the classpath (testImplementation), which enables
|
||||
* security autoconfiguration and can lock down all endpoints unless a SecurityWebFilterChain is provided
|
||||
* - The Gateway enforces authentication using JwtAuthenticationFilter when enabled via property,
|
||||
* so the SecurityWebFilterChain should stay permissive and focus on foundational concerns
|
||||
* - Explicit CORS configuration ensures proper handling of cross-origin requests from web clients
|
||||
* - Configurable properties allow environment-specific security settings without code changes
|
||||
* - CSRF protection is disabled as it's not needed for stateless JWT-based authentication
|
||||
* Die CORS-Konfiguration arbeitet mit der bestehenden Filterkette zusammen:
|
||||
* - Erlaubt Anfragen von konfigurierten Ursprüngen (Dev/Prod-Umgebungen)
|
||||
* - Gibt benutzerdefinierte Header aus Gateway-Filtern frei (Korrelations-IDs, Rate-Limits)
|
||||
* - Unterstützt Credentials für JWT-Authentifizierung
|
||||
* - Cacht Preflight-Antworten für bessere Performance
|
||||
*
|
||||
* CORS INTEGRATION:
|
||||
* =================
|
||||
* The CORS configuration works with the existing filter chain:
|
||||
* - Allows requests from configured origins (dev/prod environments)
|
||||
* - Exposes custom headers from Gateway filters (correlation IDs, rate limits)
|
||||
* - Supports credentials for JWT authentication
|
||||
* - Caches preflight responses for performance
|
||||
*
|
||||
* TESTING CONSIDERATIONS:
|
||||
* =======================
|
||||
* - Configuration is designed to work seamlessly with existing security tests
|
||||
* - Test profile can override CORS settings if needed
|
||||
* - Permissive authorization ensures tests can focus on filter-level security
|
||||
* TESTHINWEISE:
|
||||
* =============
|
||||
* - Die Konfiguration ist so gestaltet, dass sie nahtlos mit bestehenden Sicherheitstests funktioniert
|
||||
* - Das Test-Profil kann CORS-Einstellungen bei Bedarf überschreiben
|
||||
* - Eine permissive Autorisierung stellt sicher, dass Tests sich auf die Sicherheit der Filterebene konzentrieren können
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(GatewaySecurityProperties::class)
|
||||
|
|
@ -63,113 +63,113 @@ class SecurityConfig(
|
|||
) {
|
||||
|
||||
/**
|
||||
* Main Spring Security filter chain configuration.
|
||||
* Hauptkonfiguration der Spring-Security-Filterkette.
|
||||
*
|
||||
* This method configures the reactive security filter chain with:
|
||||
* - CSRF disabled for stateless API operation
|
||||
* - Explicit CORS configuration for cross-origin support
|
||||
* - Permissive authorization (authentication handled by JWT filter)
|
||||
* Diese Methode konfiguriert die reaktive Sicherheits-Filterkette mit:
|
||||
* - CSRF deaktiviert für zustandslosen API-Betrieb
|
||||
* - Expliziter CORS-Konfiguration für Cross-Origin-Unterstützung
|
||||
* - Permissiver Autorisierung (Authentifizierung durch den JWT-Filter)
|
||||
*
|
||||
* The configuration maintains compatibility with the existing filter architecture
|
||||
* while providing enhanced CORS control and configurability.
|
||||
* Die Konfiguration bleibt kompatibel mit der bestehenden Filterarchitektur
|
||||
* und bietet zugleich bessere CORS-Steuerung und Konfigurierbarkeit.
|
||||
*/
|
||||
@Bean
|
||||
fun springSecurityFilterChain(): SecurityWebFilterChain {
|
||||
return ServerHttpSecurity.http()
|
||||
.csrf { csrf ->
|
||||
// Disable CSRF for stateless API gateway
|
||||
// CSRF protection is not required for JWT-based stateless authentication
|
||||
// The Gateway operates as a stateless proxy with no session state
|
||||
// CSRF für zustandsloses API-Gateway deaktivieren
|
||||
// CSRF-Schutz ist für JWT-basierte zustandslose Authentifizierung nicht erforderlich
|
||||
// Das Gateway arbeitet als zustandsloser Proxy ohne Session-Zustand
|
||||
csrf.disable()
|
||||
}
|
||||
.cors { cors ->
|
||||
// Use explicit CORS configuration instead of default
|
||||
// This provides better control over cross-origin access policies
|
||||
// Explizite CORS-Konfiguration anstelle des Defaults verwenden
|
||||
// Dies ermöglicht eine bessere Kontrolle über Cross-Origin-Zugriffsrichtlinien
|
||||
cors.configurationSource(corsConfigurationSource())
|
||||
}
|
||||
.httpBasic { basic ->
|
||||
// Disable HTTP Basic auth for stateless API
|
||||
// HTTP Basic Auth für zustandslose API deaktivieren
|
||||
basic.disable()
|
||||
}
|
||||
.formLogin { form ->
|
||||
// Disable form login for API gateway
|
||||
// Formular-Login für API-Gateway deaktivieren
|
||||
form.disable()
|
||||
}
|
||||
.authorizeExchange { exchanges ->
|
||||
// Allow all requests through Spring Security
|
||||
// Authentication and authorization are handled by JwtAuthenticationFilter
|
||||
// This approach maintains the existing security architecture while
|
||||
// allowing the JWT filter to make granular access control decisions
|
||||
// Alle Anfragen durch Spring Security erlauben
|
||||
// Authentifizierung und Autorisierung erfolgen durch den JwtAuthenticationFilter
|
||||
// Dieser Ansatz bewahrt die bestehende Sicherheitsarchitektur und
|
||||
// ermöglicht dem JWT-Filter granulare Zugriffskontroll-Entscheidungen
|
||||
exchanges.anyExchange().permitAll()
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicit CORS configuration source.
|
||||
* Explizite CORS-Konfigurationsquelle.
|
||||
*
|
||||
* This bean provides detailed control over cross-origin resource sharing settings,
|
||||
* replacing the default empty CORS configuration with explicit, configurable settings.
|
||||
* Dieser Bean bietet eine detaillierte Steuerung der Cross-Origin-Resource-Sharing-Einstellungen
|
||||
* und ersetzt die leere Standard-CORS-Konfiguration durch explizite, konfigurierbare Einstellungen.
|
||||
*
|
||||
* Key features:
|
||||
* - Environment-specific allowed origins
|
||||
* - Comprehensive HTTP method support
|
||||
* - JWT-aware header configuration
|
||||
* - Integration with Gateway filter headers
|
||||
* - Performance-optimized preflight caching
|
||||
* Schlüsselfunktionen:
|
||||
* - Umgebungsspezifische erlaubte Ursprünge (Allowed Origins)
|
||||
* - Umfassende Unterstützung für HTTP-Methoden
|
||||
* - JWT-bewusste Header-Konfiguration
|
||||
* - Integration mit Headern aus Gateway-Filtern
|
||||
* - Performance-optimiertes Preflight-Caching
|
||||
*
|
||||
* The configuration is designed to work with typical web application architectures
|
||||
* where a JavaScript frontend makes API calls to the Gateway.
|
||||
* Die Konfiguration ist darauf ausgelegt, mit typischen Webanwendungs-Architekturen zu funktionieren,
|
||||
* bei denen ein JavaScript-Frontend API-Aufrufe an das Gateway sendet.
|
||||
*/
|
||||
@Bean
|
||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val configuration = CorsConfiguration().apply {
|
||||
// Allowed origins - configurable per environment
|
||||
// Development: localhost URLs for local testing
|
||||
// Production: domain-specific URLs for deployed applications
|
||||
// Erlaubte Ursprünge – pro Umgebung konfigurierbar
|
||||
// Entwicklung: localhost-URLs für lokale Tests
|
||||
// Produktion: domainspezifische URLs für ausgelieferte Anwendungen
|
||||
allowedOrigins = securityProperties.cors.allowedOrigins.toList()
|
||||
|
||||
|
||||
// Allowed HTTP methods - comprehensive REST API support
|
||||
// Includes all standard methods plus OPTIONS for preflight requests
|
||||
// Erlaubte HTTP-Methoden – umfassende REST-API-Unterstützung
|
||||
// Enthält alle Standardmethoden plus OPTIONS für Preflight-Anfragen
|
||||
allowedMethods = securityProperties.cors.allowedMethods.toList()
|
||||
|
||||
// Allowed request headers - includes JWT and custom headers
|
||||
// Authorization: for JWT Bearer tokens
|
||||
// X-Correlation-ID: for request tracing
|
||||
// Standard headers: Content-Type, Accept, etc.
|
||||
// Erlaubte Request-Header – beinhaltet JWT und benutzerdefinierte Header
|
||||
// Authorization: für JWT Bearer Tokens
|
||||
// X-Correlation-ID: für Request-Tracing
|
||||
// Standard-Header: Content-Type, Accept, etc.
|
||||
allowedHeaders = securityProperties.cors.allowedHeaders.toList()
|
||||
|
||||
// Exposed response headers - allows client access to custom headers
|
||||
// Includes headers added by Gateway filters:
|
||||
// - X-Correlation-ID from CorrelationIdFilter
|
||||
// - X-RateLimit-* from RateLimitingFilter
|
||||
// Sichtbare Response-Header – ermöglicht Client-Zugriff auf benutzerdefinierte Header
|
||||
// Beinhaltet Header, die von Gateway-Filtern hinzugefügt werden:
|
||||
// - X-Correlation-ID vom CorrelationIdFilter
|
||||
// - X-RateLimit-* vom RateLimitingFilter
|
||||
exposedHeaders = securityProperties.cors.exposedHeaders.toList()
|
||||
|
||||
// Allow credentials - required for JWT authentication
|
||||
// Enables cookies and authorization headers in cross-origin requests
|
||||
// Credentials erlauben – erforderlich für JWT-Authentifizierung
|
||||
// Aktiviert Cookies und Authorization-Header in Cross-Origin-Anfragen
|
||||
allowCredentials = securityProperties.cors.allowCredentials
|
||||
|
||||
// Preflight cache duration - performance optimization
|
||||
// Reduces the number of OPTIONS requests for repeated API calls
|
||||
// Preflight-Cache-Dauer – Performance-Optimierung
|
||||
// Reduziert die Anzahl an OPTIONS-Anfragen für wiederholte API-Aufrufe
|
||||
maxAge = securityProperties.cors.maxAge.seconds
|
||||
}
|
||||
|
||||
return UrlBasedCorsConfigurationSource().apply {
|
||||
// Apply CORS configuration to all Gateway routes
|
||||
// CORS-Konfiguration auf alle Gateway-Routen anwenden
|
||||
registerCorsConfiguration("/**", configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration properties for Gateway security settings.
|
||||
* Konfigurationseigenschaften für die Sicherheits-Einstellungen des Gateways.
|
||||
*
|
||||
* Enables environment-specific security configuration via application.yml/properties.
|
||||
* This approach allows different security settings across development, testing, and
|
||||
* production environments without requiring code changes.
|
||||
* Ermöglicht umgebungsspezifische Sicherheitskonfiguration über application.yml/-properties.
|
||||
* Dieser Ansatz erlaubt unterschiedliche Sicherheitseinstellungen für Entwicklung, Test und
|
||||
* Produktion, ohne Codeänderungen vornehmen zu müssen.
|
||||
*
|
||||
* Example application.yml configuration:
|
||||
* Beispielkonfiguration in application.yml:
|
||||
* ```yaml
|
||||
* gateway:
|
||||
* security:
|
||||
|
|
@ -192,26 +192,26 @@ data class GatewaySecurityProperties(
|
|||
)
|
||||
|
||||
/**
|
||||
* CORS-specific configuration properties with sensible defaults.
|
||||
* CORS-spezifische Konfigurationseigenschaften mit sinnvollen Defaults.
|
||||
*
|
||||
* Default values are chosen to work with typical development and production setups:
|
||||
* - Common development URLs (localhost with standard ports)
|
||||
* - Production domain pattern matching
|
||||
* - Full REST API method support
|
||||
* - JWT and Gateway filter header support
|
||||
* - Reasonable preflight cache duration
|
||||
* Die Default-Werte sind so gewählt, dass sie mit typischen Entwicklungs- und Produktions-Setups funktionieren:
|
||||
* - Übliche Entwicklungs-URLs (localhost mit Standardports)
|
||||
* - Produktions-Domain-Muster
|
||||
* - Volle Unterstützung der REST-API-Methoden
|
||||
* - Unterstützung für JWT- und Gateway-Filter-Header
|
||||
* - Sinnvolle Preflight-Cache-Dauer
|
||||
*/
|
||||
data class CorsProperties(
|
||||
/**
|
||||
* Allowed origins for CORS requests.
|
||||
* Erlaubte Ursprünge (Allowed Origins) für CORS-Anfragen.
|
||||
*
|
||||
* Defaults support common development and production scenarios:
|
||||
* - localhost:3000 - typical React development server
|
||||
* - localhost:8080 - common alternative development port
|
||||
* - localhost:4200 - typical Angular development server
|
||||
* - Specific meldestelle.at subdomains for production
|
||||
* Defaults unterstützen gängige Entwicklungs- und Produktionsszenarien:
|
||||
* - localhost:3000 – typischer React-Entwicklungsserver
|
||||
* - localhost:8080 – gängiger alternativer Entwicklungsport
|
||||
* - localhost:4200 – typischer Angular-Entwicklungsserver
|
||||
* - Spezifische Subdomains von meldestelle.at für die Produktion
|
||||
*
|
||||
* Can be overridden per environment as needed.
|
||||
* Kann je Umgebung bei Bedarf überschrieben werden.
|
||||
*/
|
||||
val allowedOrigins: Set<String> = setOf(
|
||||
"http://localhost:3000",
|
||||
|
|
@ -224,23 +224,23 @@ data class CorsProperties(
|
|||
|
||||
|
||||
/**
|
||||
* Allowed HTTP methods for CORS requests.
|
||||
* Erlaubte HTTP-Methoden für CORS-Anfragen.
|
||||
*
|
||||
* Includes all standard REST API methods plus OPTIONS for preflight
|
||||
* and HEAD for metadata requests.
|
||||
* Enthält alle Standard-REST-API-Methoden sowie OPTIONS für Preflight-
|
||||
* und HEAD für Metadaten-Anfragen.
|
||||
*/
|
||||
val allowedMethods: Set<String> = setOf(
|
||||
"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"
|
||||
),
|
||||
|
||||
/**
|
||||
* Allowed request headers for CORS requests.
|
||||
* Erlaubte Request-Header für CORS-Anfragen.
|
||||
*
|
||||
* Includes:
|
||||
* - Standard headers: Content-Type, Accept, etc.
|
||||
* - JWT authentication: Authorization
|
||||
* - Gateway tracing: X-Correlation-ID
|
||||
* - Cache control: Cache-Control, Pragma
|
||||
* Beinhaltet:
|
||||
* - Standard-Header: Content-Type, Accept, etc.
|
||||
* - JWT-Authentifizierung: Authorization
|
||||
* - Gateway-Tracing: X-Correlation-ID
|
||||
* - Cache-Steuerung: Cache-Control, Pragma
|
||||
*/
|
||||
val allowedHeaders: Set<String> = setOf(
|
||||
"Authorization",
|
||||
|
|
@ -254,13 +254,13 @@ data class CorsProperties(
|
|||
),
|
||||
|
||||
/**
|
||||
* Exposed response headers for CORS requests.
|
||||
* Sichtbare Response-Header für CORS-Anfragen.
|
||||
*
|
||||
* Headers that client JavaScript can access in responses.
|
||||
* Includes custom headers added by Gateway filters:
|
||||
* - X-Correlation-ID: request tracing (CorrelationIdFilter)
|
||||
* - X-RateLimit-*: rate limiting info (RateLimitingFilter)
|
||||
* - Standard headers: Content-Length, Date
|
||||
* Header, auf die Client-JavaScript in Antworten zugreifen darf.
|
||||
* Beinhaltet benutzerdefinierte Header, die von Gateway-Filtern hinzugefügt werden:
|
||||
* - X-Correlation-ID: Request-Tracing (CorrelationIdFilter)
|
||||
* - X-RateLimit-*: Informationen zum Rate-Limiting (RateLimitingFilter)
|
||||
* - Standard-Header: Content-Length, Date
|
||||
*/
|
||||
val exposedHeaders: Set<String> = setOf(
|
||||
"X-Correlation-ID",
|
||||
|
|
@ -272,21 +272,21 @@ data class CorsProperties(
|
|||
),
|
||||
|
||||
/**
|
||||
* Allow credentials in CORS requests.
|
||||
* Credentials in CORS-Anfragen erlauben.
|
||||
*
|
||||
* Set to true to support:
|
||||
* - JWT Bearer tokens in Authorization headers
|
||||
* - Cookies (if used)
|
||||
* - Client certificates (if used)
|
||||
* Auf true setzen, um zu unterstützen:
|
||||
* - JWT Bearer Tokens im Authorization-Header
|
||||
* - Cookies (falls verwendet)
|
||||
* - Client-Zertifikate (falls verwendet)
|
||||
*/
|
||||
val allowCredentials: Boolean = true,
|
||||
|
||||
/**
|
||||
* Maximum age for preflight request caching.
|
||||
* Maximales Alter für das Caching von Preflight-Anfragen.
|
||||
*
|
||||
* Duration that browsers can cache preflight responses, reducing
|
||||
* the number of OPTIONS requests for repeated API calls.
|
||||
* Default: 1 hour (reasonable balance of performance vs. flexibility)
|
||||
* Dauer, für die Browser Preflight-Antworten cachen können, wodurch
|
||||
* die Anzahl der OPTIONS-Anfragen für wiederholte API-Aufrufe reduziert wird.
|
||||
* Default: 1 Stunde (guter Kompromiss zwischen Performance und Flexibilität)
|
||||
*/
|
||||
val maxAge: Duration = Duration.ofHours(1)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,140 +35,142 @@ spring:
|
|||
max-idle-time: 15s
|
||||
max-life-time: 60s
|
||||
# Verbesserte CORS-Konfiguration
|
||||
globalcors:
|
||||
corsConfigurations:
|
||||
'[/**]':
|
||||
allowedOriginPatterns:
|
||||
- "https://*.meldestelle.at"
|
||||
- "http://localhost:*"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- OPTIONS
|
||||
allowedHeaders:
|
||||
- "*"
|
||||
allowCredentials: true
|
||||
maxAge: 3600
|
||||
# Antwort-Header bereinigen und globale Filter
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: defaultCircuitBreaker
|
||||
fallbackUri: forward:/fallback
|
||||
- name: Retry
|
||||
args:
|
||||
retries: 3
|
||||
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
|
||||
methods: GET,POST,PUT,DELETE
|
||||
backoff:
|
||||
firstBackoff: 50ms
|
||||
maxBackoff: 500ms
|
||||
factor: 2
|
||||
basedOnPreviousValue: false
|
||||
# Security Headers for enhanced protection
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-Content-Type-Options
|
||||
value: nosniff
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-Frame-Options
|
||||
value: DENY
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-XSS-Protection
|
||||
value: 1; mode=block
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: Referrer-Policy
|
||||
value: strict-origin-when-cross-origin
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: Cache-Control
|
||||
value: no-cache, no-store, must-revalidate
|
||||
# Route definitions with service discovery
|
||||
routes:
|
||||
# Health Check und Gateway Info Routes
|
||||
- id: gateway-info-route
|
||||
uri: http://localhost:${server.port}
|
||||
predicates:
|
||||
- Path=/
|
||||
- Method=GET
|
||||
filters:
|
||||
- SetStatus=200
|
||||
- SetResponseHeader=Content-Type,application/json
|
||||
|
||||
# Members Service Routes
|
||||
- id: members-service-route
|
||||
uri: lb://members-service
|
||||
predicates:
|
||||
- Path=/api/members/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
server:
|
||||
webflux:
|
||||
default-filters:
|
||||
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: membersCircuitBreaker
|
||||
fallbackUri: forward:/fallback/members
|
||||
args:
|
||||
name: defaultCircuitBreaker
|
||||
fallbackUri: forward:/fallback
|
||||
- name: Retry
|
||||
args:
|
||||
retries: 3
|
||||
statuses: BAD_GATEWAY,GATEWAY_TIMEOUT
|
||||
methods: GET,POST,PUT,DELETE
|
||||
backoff:
|
||||
firstBackoff: 50ms
|
||||
maxBackoff: 500ms
|
||||
factor: 2
|
||||
basedOnPreviousValue: false
|
||||
# Security Headers for enhanced protection
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-Content-Type-Options
|
||||
value: nosniff
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-Frame-Options
|
||||
value: DENY
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: X-XSS-Protection
|
||||
value: 1; mode=block
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: Referrer-Policy
|
||||
value: strict-origin-when-cross-origin
|
||||
- name: AddResponseHeader
|
||||
args:
|
||||
name: Cache-Control
|
||||
value: no-cache, no-store, must-revalidate
|
||||
routes:
|
||||
# Health Check und Gateway Info Routes
|
||||
- id: gateway-info-route
|
||||
uri: http://localhost:${server.port}
|
||||
predicates:
|
||||
- Path=/
|
||||
- Method=GET
|
||||
filters:
|
||||
- SetStatus=200
|
||||
- SetResponseHeader=Content-Type,application/json
|
||||
|
||||
# Horses Service Routes
|
||||
- id: horses-service-route
|
||||
uri: lb://horses-service
|
||||
predicates:
|
||||
- Path=/api/horses/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: horsesCircuitBreaker
|
||||
fallbackUri: forward:/fallback/horses
|
||||
# Members Service Routes
|
||||
- id: members-service-route
|
||||
uri: lb://members-service
|
||||
predicates:
|
||||
- Path=/api/members/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: membersCircuitBreaker
|
||||
fallbackUri: forward:/fallback/members
|
||||
|
||||
# Events Service Routes
|
||||
- id: events-service-route
|
||||
uri: lb://events-service
|
||||
predicates:
|
||||
- Path=/api/events/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: eventsCircuitBreaker
|
||||
fallbackUri: forward:/fallback/events
|
||||
# Horses Service Routes
|
||||
- id: horses-service-route
|
||||
uri: lb://horses-service
|
||||
predicates:
|
||||
- Path=/api/horses/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: horsesCircuitBreaker
|
||||
fallbackUri: forward:/fallback/horses
|
||||
|
||||
# Masterdata Service Routes
|
||||
- id: masterdata-service-route
|
||||
uri: lb://masterdata-service
|
||||
predicates:
|
||||
- Path=/api/masterdata/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: masterdataCircuitBreaker
|
||||
fallbackUri: forward:/fallback/masterdata
|
||||
# Events Service Routes
|
||||
- id: events-service-route
|
||||
uri: lb://events-service
|
||||
predicates:
|
||||
- Path=/api/events/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: eventsCircuitBreaker
|
||||
fallbackUri: forward:/fallback/events
|
||||
|
||||
# Auth Service Routes (if exists)
|
||||
- id: auth-service-route
|
||||
uri: lb://auth-service
|
||||
predicates:
|
||||
- Path=/api/auth/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: authCircuitBreaker
|
||||
fallbackUri: forward:/fallback/auth
|
||||
# Masterdata Service Routes
|
||||
- id: masterdata-service-route
|
||||
uri: lb://masterdata-service
|
||||
predicates:
|
||||
- Path=/api/masterdata/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: masterdataCircuitBreaker
|
||||
fallbackUri: forward:/fallback/masterdata
|
||||
|
||||
# Ping Service Routes (existing)
|
||||
- id: ping-service-route
|
||||
uri: lb://ping-service
|
||||
predicates:
|
||||
- Path=/api/ping/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
# Auth Service Routes (if exists)
|
||||
- id: auth-service-route
|
||||
uri: lb://auth-service
|
||||
predicates:
|
||||
- Path=/api/auth/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
- name: CircuitBreaker
|
||||
args:
|
||||
name: authCircuitBreaker
|
||||
fallbackUri: forward:/fallback/auth
|
||||
|
||||
# Ping Service Routes (existing)
|
||||
- id: ping-service-route
|
||||
uri: lb://ping-service
|
||||
predicates:
|
||||
- Path=/api/ping/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
globalcors:
|
||||
cors-configurations:
|
||||
'[/**]':
|
||||
allowedOriginPatterns:
|
||||
- "https://*.meldestelle.at"
|
||||
- "http://localhost:*"
|
||||
allowedMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
- OPTIONS
|
||||
allowedHeaders:
|
||||
- "*"
|
||||
allowCredentials: true
|
||||
maxAge: 3600
|
||||
|
||||
# Circuit Breaker Configuration
|
||||
resilience4j:
|
||||
|
|
@ -228,14 +230,14 @@ management:
|
|||
probes:
|
||||
enabled: true
|
||||
metrics:
|
||||
enabled: true
|
||||
access: unrestricted
|
||||
info:
|
||||
enabled: true
|
||||
access: unrestricted
|
||||
prometheus:
|
||||
enabled: true
|
||||
access: unrestricted
|
||||
gateway:
|
||||
enabled: true
|
||||
circuitbreakers:
|
||||
access: unrestricted
|
||||
circuit breakers:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user