refactor(infra-monitoring)
refactor(infra-gateway)
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
Zusammengefasst ergibt sich daraus folgender, konkreter Fahrplan:
|
||||||
|
|
||||||
|
1. **Schritt 0: Aufräumen (ca. 1-2 Stunden)**
|
||||||
|
* [ ] Entfernen Sie den auskommentierten Ktor-Code aus der `infrastructure:gateway:build.gradle.kts`.
|
||||||
|
* [ ] Refaktorieren Sie die Test-Route in `GatewayApplicationTests.kt` auf die Kotlin DSL von Spring Cloud Gateway.
|
||||||
|
* [ ] **(Optional)** Führen Sie `value class`es für stark typisierte IDs oder Konfigurationsparameter im `core`-Modul ein.
|
||||||
|
|
||||||
|
2. **Schritt 1: Phase 2 - Den "Ping-Service" bauen**
|
||||||
|
* [ ] Erstellen Sie ein neues Gradle-Modul `:temp:ping-service`.
|
||||||
|
* [ ] Implementieren Sie eine simple Spring Boot Anwendung darin.
|
||||||
|
* [ ] Fügen Sie die Abhängigkeiten zu `spring-boot-starter-web`, `spring-cloud-starter-consul-discovery` und Ihrem `platform:platform-dependencies` hinzu.
|
||||||
|
* [ ] Erstellen Sie einen `RestController` mit einem `GET /ping` Endpunkt, der `mapOf("status" to "pong")` zurückgibt.
|
||||||
|
* [ ] Konfigurieren Sie die `application.yml` des Services, damit er sich bei Consul registriert und einen eindeutigen Namen (`spring.application.name=ping-service`) hat.
|
||||||
|
|
||||||
|
3. **Schritt 2: Phase 3 - Gateway-Route konfigurieren**
|
||||||
|
* [ ] Fügen Sie in der `application.yml` Ihres Gateways eine Route hinzu, die Anfragen von `/api/ping` an den `ping-service` weiterleitet (Load Balanced via `lb://ping-service`).
|
||||||
|
|
||||||
|
4. **Schritt 3: Phase 4 - Gesamtsystem testen**
|
||||||
|
* [ ] Starten Sie Consul, den Gateway und den Ping-Service.
|
||||||
|
* [ ] Rufen Sie die Gateway-URL (z.B. `http://localhost:8080/api/ping`) auf und verifizieren Sie, dass Sie die `{"status": "pong"}`-Antwort erhalten.
|
||||||
|
* [ ] Erstellen Sie den minimalen "Ping"-Button in Ihrer Client-Anwendung und testen Sie den gesamten Weg.
|
||||||
|
|
||||||
|
Wenn Sie diesen Plan abarbeiten, haben Sie nicht nur Ihre Architektur validiert, sondern auch einige Stellen modernisiert und aufgeräumt. Sie sind auf einem exzellenten Weg
|
||||||
@@ -1,56 +1,3 @@
|
|||||||
/*plugins {
|
|
||||||
alias(libs.plugins.kotlin.jvm)
|
|
||||||
alias(libs.plugins.kotlin.serialization)
|
|
||||||
alias(libs.plugins.ktor)
|
|
||||||
alias(libs.plugins.spring.dependencyManagement)
|
|
||||||
application
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass.set("at.mocode.infrastructure.gateway.ApplicationKt")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(platform(libs.spring.boot.dependencies))
|
|
||||||
implementation(projects.platform.platformDependencies)
|
|
||||||
implementation(projects.core.coreDomain)
|
|
||||||
implementation(projects.core.coreUtils)
|
|
||||||
|
|
||||||
implementation(projects.infrastructure.auth.authClient)
|
|
||||||
implementation(projects.infrastructure.monitoring.monitoringClient)
|
|
||||||
|
|
||||||
// --- Ktor Server ---
|
|
||||||
implementation(libs.ktor.server.core)
|
|
||||||
implementation(libs.ktor.server.netty)
|
|
||||||
implementation(libs.ktor.server.contentNegotiation)
|
|
||||||
implementation(libs.ktor.server.serialization.kotlinx.json)
|
|
||||||
implementation(libs.ktor.server.cors)
|
|
||||||
implementation(libs.ktor.server.callLogging)
|
|
||||||
implementation(libs.ktor.server.defaultHeaders)
|
|
||||||
implementation(libs.ktor.server.statusPages)
|
|
||||||
implementation(libs.ktor.server.auth)
|
|
||||||
implementation(libs.ktor.server.authJwt)
|
|
||||||
implementation(libs.ktor.server.rateLimit)
|
|
||||||
implementation(libs.ktor.server.metrics.micrometer)
|
|
||||||
|
|
||||||
// --- OpenAPI & Swagger for Ktor ---
|
|
||||||
implementation(libs.ktor.server.openapi)
|
|
||||||
implementation(libs.ktor.server.swagger)
|
|
||||||
|
|
||||||
// --- Ktor Client (damit der Gateway Anfragen an die Backend-Services weiterleiten kann) ---
|
|
||||||
implementation(libs.ktor.client.core)
|
|
||||||
implementation(libs.ktor.client.cio)
|
|
||||||
implementation(libs.ktor.client.contentNegotiation)
|
|
||||||
implementation(libs.ktor.client.serialization.kotlinx.json)
|
|
||||||
|
|
||||||
// --- Monitoring ---
|
|
||||||
implementation(libs.micrometer.prometheus)
|
|
||||||
|
|
||||||
// --- Testing ---
|
|
||||||
testImplementation(projects.platform.platformTesting)
|
|
||||||
testImplementation(libs.ktor.server.tests)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt
|
// Dieses Modul ist das API-Gateway und der einzige öffentliche Einstiegspunkt
|
||||||
// für alle externen Anfragen an das Meldestelle-System.
|
// für alle externen Anfragen an das Meldestelle-System.
|
||||||
plugins {
|
plugins {
|
||||||
@@ -75,6 +22,8 @@ dependencies {
|
|||||||
|
|
||||||
// Stellt die Spring Cloud Gateway und Consul Discovery Abhängigkeiten bereit
|
// Stellt die Spring Cloud Gateway und Consul Discovery Abhängigkeiten bereit
|
||||||
implementation(libs.bundles.spring.cloud.gateway)
|
implementation(libs.bundles.spring.cloud.gateway)
|
||||||
|
// Sichert den reaktiven Webserver (Netty) explizit ab, um Test-/Kontext-Probleme zu vermeiden
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||||
|
|
||||||
// Bindet die wiederverwendbare Logik zur JWT-Validierung ein.
|
// Bindet die wiederverwendbare Logik zur JWT-Validierung ein.
|
||||||
implementation(projects.infrastructure.auth.authClient)
|
implementation(projects.infrastructure.auth.authClient)
|
||||||
@@ -85,5 +34,7 @@ dependencies {
|
|||||||
// Stellt alle Test-Abhängigkeiten gebündelt bereit.
|
// Stellt alle Test-Abhängigkeiten gebündelt bereit.
|
||||||
testImplementation(projects.platform.platformTesting)
|
testImplementation(projects.platform.platformTesting)
|
||||||
testImplementation(libs.bundles.testing.jvm)
|
testImplementation(libs.bundles.testing.jvm)
|
||||||
|
// Security im Testkontext, um eine permissive Security-Konfiguration bereitstellen zu können
|
||||||
|
testImplementation(libs.spring.boot.starter.security)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -2,13 +2,10 @@ package at.mocode.infrastructure.gateway
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableDiscoveryClient
|
|
||||||
class GatewayApplication
|
class GatewayApplication
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|
||||||
runApplication<GatewayApplication>(*args)
|
runApplication<GatewayApplication>(*args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ spring:
|
|||||||
name: api-gateway
|
name: api-gateway
|
||||||
cloud:
|
cloud:
|
||||||
gateway:
|
gateway:
|
||||||
|
# HTTP Client-Timeouts für stabile Upstream-Verbindungen
|
||||||
|
httpclient:
|
||||||
|
connect-timeout: 5000 # in Millisekunden
|
||||||
|
response-timeout: 30s
|
||||||
|
# Globales CORS-Setup (kann pro Umgebung überschrieben werden)
|
||||||
|
globalcors:
|
||||||
|
corsConfigurations:
|
||||||
|
'[/**]':
|
||||||
|
allowedOrigins: "*"
|
||||||
|
allowedMethods: "*"
|
||||||
|
allowedHeaders: "*"
|
||||||
|
# Antwort-Header bereinigen (verhindert doppelte CORS-Header)
|
||||||
|
default-filters:
|
||||||
|
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
|
||||||
# Aktiviert die automatische Routen-Erstellung basierend auf Consul
|
# Aktiviert die automatische Routen-Erstellung basierend auf Consul
|
||||||
discovery:
|
discovery:
|
||||||
locator:
|
locator:
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package at.mocode.infrastructure.gateway
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.context.annotation.Import
|
||||||
|
import org.springframework.test.web.reactive.server.WebTestClient
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.cloud.gateway.route.RouteLocator
|
||||||
|
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
|
||||||
|
import java.time.Duration
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||||
|
import org.springframework.boot.test.context.TestConfiguration
|
||||||
|
|
||||||
|
@SpringBootTest(
|
||||||
|
classes = [GatewayApplication::class],
|
||||||
|
webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = [
|
||||||
|
// Use a random port and disable discovery/consul for the test
|
||||||
|
"server.port=0",
|
||||||
|
"spring.cloud.discovery.enabled=false",
|
||||||
|
"spring.cloud.consul.enabled=false",
|
||||||
|
"spring.cloud.consul.config.enabled=false",
|
||||||
|
"spring.cloud.consul.discovery.register=false",
|
||||||
|
// Disable security autoconfiguration for tests
|
||||||
|
"spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration",
|
||||||
|
// Force a reactive web application so that Spring Cloud Gateway auto-config activates
|
||||||
|
"spring.main.web-application-type=reactive",
|
||||||
|
// Gateway discovery locator off; we use explicit test routes
|
||||||
|
"spring.cloud.gateway.discovery.locator.enabled=false"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@AutoConfigureWebTestClient
|
||||||
|
@Import(GatewayApplicationTests.TestRoutes::class, GatewayApplicationTests.InternalHelloController::class, GatewayApplicationTests.TestSecurityConfig::class)
|
||||||
|
class GatewayApplicationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var client: WebTestClient
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
lateinit var routeLocator: RouteLocator
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun contextLoads() {
|
||||||
|
// If the application context fails to load, this test will fail.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun forwardRouteShouldReturnResponseFromInternalController() {
|
||||||
|
client.get()
|
||||||
|
.uri("/hello")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk
|
||||||
|
.expectBody(String::class.java)
|
||||||
|
.isEqualTo("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
class InternalHelloController {
|
||||||
|
@GetMapping("/internal/hello")
|
||||||
|
fun hello(): String = "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class TestRoutes {
|
||||||
|
@Bean
|
||||||
|
fun routeLocator(builder: RouteLocatorBuilder): RouteLocator = builder.routes()
|
||||||
|
.route("test-forward") { r -> r.path("/hello").uri("forward:/internal/hello") }
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestConfiguration
|
||||||
|
class TestSecurityConfig {
|
||||||
|
@Bean
|
||||||
|
fun springSecurityFilterChain(): org.springframework.security.web.server.SecurityWebFilterChain =
|
||||||
|
org.springframework.security.config.web.server.ServerHttpSecurity
|
||||||
|
.http()
|
||||||
|
.csrf { it.disable() }
|
||||||
|
.authorizeExchange { exchanges -> exchanges.anyExchange().permitAll() }
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-8
@@ -1,18 +1,22 @@
|
|||||||
package at.mocode.infrastructure.messaging.client
|
package at.mocode.infrastructure.messaging.client
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.kafka.core.ProducerFactory
|
|
||||||
import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate
|
import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate
|
||||||
import reactor.kafka.sender.SenderOptions
|
import reactor.kafka.sender.SenderOptions
|
||||||
|
|
||||||
@Configuration
|
/**
|
||||||
|
* Reactive Kafka configuration utilities for creating a ReactiveKafkaProducerTemplate.
|
||||||
|
*/
|
||||||
class ReactiveKafkaConfig {
|
class ReactiveKafkaConfig {
|
||||||
|
|
||||||
@Bean
|
/**
|
||||||
fun reactiveKafkaProducerTemplate(producerFactory: ProducerFactory<String, Any>): ReactiveKafkaProducerTemplate<String, Any> {
|
* Create a ReactiveKafkaProducerTemplate using the configuration from the given ProducerFactory.
|
||||||
// Nutzt die ProducerFactory aus dem messaging-config-Modul
|
*/
|
||||||
val senderOptions = SenderOptions.create<String, Any>(producerFactory.configurationProperties)
|
fun reactiveKafkaProducerTemplate(
|
||||||
|
producerFactory: DefaultKafkaProducerFactory<String, Any>
|
||||||
|
): ReactiveKafkaProducerTemplate<String, Any> {
|
||||||
|
val props: Map<String, Any> = producerFactory.configurationProperties
|
||||||
|
val senderOptions: SenderOptions<String, Any> = SenderOptions.create(props)
|
||||||
return ReactiveKafkaProducerTemplate(senderOptions)
|
return ReactiveKafkaProducerTemplate(senderOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-3
@@ -36,7 +36,7 @@ class KafkaIntegrationTest {
|
|||||||
val kafkaConfig = KafkaConfig().apply {
|
val kafkaConfig = KafkaConfig().apply {
|
||||||
bootstrapServers = kafkaContainer.bootstrapServers
|
bootstrapServers = kafkaContainer.bootstrapServers
|
||||||
}
|
}
|
||||||
producerFactory = kafkaConfig.producerFactory() as DefaultKafkaProducerFactory<String, Any>
|
producerFactory = kafkaConfig.producerFactory()
|
||||||
|
|
||||||
val reactiveKafkaConfig = ReactiveKafkaConfig()
|
val reactiveKafkaConfig = ReactiveKafkaConfig()
|
||||||
val reactiveTemplate = reactiveKafkaConfig.reactiveKafkaProducerTemplate(producerFactory)
|
val reactiveTemplate = reactiveKafkaConfig.reactiveKafkaProducerTemplate(producerFactory)
|
||||||
@@ -60,9 +60,18 @@ class KafkaIntegrationTest {
|
|||||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
||||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java,
|
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java,
|
||||||
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest",
|
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest",
|
||||||
JsonDeserializer.TRUSTED_PACKAGES to "*"
|
JsonDeserializer.TRUSTED_PACKAGES to "*",
|
||||||
|
JsonDeserializer.USE_TYPE_INFO_HEADERS to false,
|
||||||
|
JsonDeserializer.VALUE_DEFAULT_TYPE to TestEvent::class.java.name
|
||||||
)
|
)
|
||||||
val receiverOptions = ReceiverOptions.create<String, TestEvent>(consumerProps).subscription(listOf(testTopic))
|
|
||||||
|
val jsonValueDeserializer = JsonDeserializer(TestEvent::class.java).apply {
|
||||||
|
addTrustedPackages("*")
|
||||||
|
}
|
||||||
|
val receiverOptions = ReceiverOptions.create<String, TestEvent>(consumerProps)
|
||||||
|
.withKeyDeserializer(StringDeserializer())
|
||||||
|
.withValueDeserializer(jsonValueDeserializer)
|
||||||
|
.subscription(listOf(testTopic))
|
||||||
|
|
||||||
// Der Mono, der das nächste empfangene Ereignis darstellt
|
// Der Mono, der das nächste empfangene Ereignis darstellt
|
||||||
val receivedEvent = KafkaReceiver.create(receiverOptions)
|
val receivedEvent = KafkaReceiver.create(receiverOptions)
|
||||||
|
|||||||
+24
-43
@@ -1,57 +1,38 @@
|
|||||||
package at.mocode.infrastructure.messaging.config
|
package at.mocode.infrastructure.messaging.config
|
||||||
|
|
||||||
import org.apache.kafka.clients.consumer.ConsumerConfig
|
|
||||||
import org.apache.kafka.clients.producer.ProducerConfig
|
import org.apache.kafka.clients.producer.ProducerConfig
|
||||||
import org.apache.kafka.common.serialization.StringDeserializer
|
|
||||||
import org.apache.kafka.common.serialization.StringSerializer
|
import org.apache.kafka.common.serialization.StringSerializer
|
||||||
import org.springframework.beans.factory.annotation.Value
|
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
import org.springframework.kafka.core.DefaultKafkaProducerFactory
|
||||||
import org.springframework.kafka.core.KafkaTemplate
|
|
||||||
import org.springframework.kafka.core.ProducerFactory
|
import org.springframework.kafka.core.ProducerFactory
|
||||||
import org.springframework.kafka.support.serializer.JsonDeserializer
|
|
||||||
import org.springframework.kafka.support.serializer.JsonSerializer
|
import org.springframework.kafka.support.serializer.JsonSerializer
|
||||||
|
|
||||||
@Configuration
|
/**
|
||||||
|
* Central Kafka producer configuration used across modules.
|
||||||
|
*
|
||||||
|
* This class can be instantiated programmatically (as done in tests) or
|
||||||
|
* registered as a Spring @Configuration with @Bean methods in an application context.
|
||||||
|
*/
|
||||||
class KafkaConfig {
|
class KafkaConfig {
|
||||||
|
|
||||||
// KORREKTUR: Von lateinit zu einer public var mit Standardwert, um Tests zu ermöglichen
|
/**
|
||||||
@Value($$"${spring.kafka.bootstrap-servers:localhost:9092}")
|
* Comma-separated list of host:port pairs used for establishing the initial connection to the Kafka cluster.
|
||||||
|
*/
|
||||||
var bootstrapServers: String = "localhost:9092"
|
var bootstrapServers: String = "localhost:9092"
|
||||||
|
|
||||||
@Value("\${spring.kafka.consumer.group-id:meldestelle-group}")
|
/**
|
||||||
private lateinit var consumerGroupId: String
|
* Common producer properties with sensible defaults (String keys, JSON values).
|
||||||
|
*/
|
||||||
|
fun producerConfigs(): Map<String, Any> = mapOf(
|
||||||
|
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers,
|
||||||
|
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java,
|
||||||
|
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to JsonSerializer::class.java,
|
||||||
|
// Avoid adding type info headers; keeps payloads simple and interoperable.
|
||||||
|
JsonSerializer.ADD_TYPE_INFO_HEADERS to false
|
||||||
|
)
|
||||||
|
|
||||||
@Bean
|
/**
|
||||||
fun producerFactory(): ProducerFactory<String, Any> {
|
* Strongly typed producer factory to avoid unchecked casts in consumers/tests.
|
||||||
val configProps = mapOf(
|
*/
|
||||||
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers,
|
fun producerFactory(): DefaultKafkaProducerFactory<String, Any> =
|
||||||
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG to StringSerializer::class.java,
|
DefaultKafkaProducerFactory(producerConfigs())
|
||||||
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG to JsonSerializer::class.java,
|
|
||||||
ProducerConfig.ACKS_CONFIG to "all",
|
|
||||||
ProducerConfig.RETRIES_CONFIG to 3,
|
|
||||||
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG to true,
|
|
||||||
ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION to 1
|
|
||||||
)
|
|
||||||
return DefaultKafkaProducerFactory(configProps)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
fun kafkaTemplate(producerFactory: ProducerFactory<String, Any>): KafkaTemplate<String, Any> {
|
|
||||||
return KafkaTemplate(producerFactory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEU: Stellt eine zentrale Map mit den Basis-Konfigurationen für alle Consumer bereit.
|
|
||||||
@Bean
|
|
||||||
fun kafkaConsumerConfiguration(): Map<String, Any> {
|
|
||||||
return mapOf(
|
|
||||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers,
|
|
||||||
ConsumerConfig.GROUP_ID_CONFIG to consumerGroupId,
|
|
||||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
|
||||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java,
|
|
||||||
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG to "earliest", // Beginne davon am Anfang, wenn kein Offset existiert
|
|
||||||
JsonDeserializer.TRUSTED_PACKAGES to "*" // Erlaube Deserialisierung aller unserer Klassen
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,9 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(libs.plugins.kotlin.spring)
|
alias(libs.plugins.kotlin.spring)
|
||||||
alias(libs.plugins.spring.boot)
|
|
||||||
alias(libs.plugins.spring.dependencyManagement)
|
alias(libs.plugins.spring.dependencyManagement)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deaktiviert die Erstellung eines ausführbaren Jars für dieses Bibliotheks-Modul.
|
|
||||||
tasks.getByName("bootJar") {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package at.mocode.infrastructure.monitoring.client
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfiguration
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
|
||||||
|
import org.springframework.context.annotation.PropertySource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutoConfiguration für das Monitoring-Client-Modul.
|
||||||
|
*
|
||||||
|
* Lädt konservative Default-Properties mit niedriger Priorität, die in jeder Anwendung
|
||||||
|
* leicht per application.properties/-yaml überschrieben werden können.
|
||||||
|
*/
|
||||||
|
@AutoConfiguration
|
||||||
|
@ConditionalOnClass(name = [
|
||||||
|
"org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration",
|
||||||
|
"io.micrometer.core.instrument.MeterRegistry"
|
||||||
|
])
|
||||||
|
@PropertySource("classpath:monitoring-defaults.properties")
|
||||||
|
class MonitoringClientAutoConfiguration
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
at.mocode.infrastructure.monitoring.client.MonitoringClientAutoConfiguration
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# ===================================================================
|
||||||
|
# MELDENSTELLE - MONITORING CLIENT DEFAULTS (via AutoConfiguration)
|
||||||
|
# Diese Konfigurationen werden automatisch von jedem Service übernommen,
|
||||||
|
# der das monitoring-client-Modul einbindet. Sie können in der Anwendung
|
||||||
|
# jederzeit überschrieben werden.
|
||||||
|
# ===================================================================
|
||||||
|
|
||||||
|
# --- Spring Boot Actuator ---
|
||||||
|
# Stellt die /actuator Endpunkte bereit (health, info, prometheus)
|
||||||
|
management.endpoints.web.exposure.include=health,info,prometheus
|
||||||
|
|
||||||
|
# --- Micrometer Tracing ---
|
||||||
|
# Aktiviert das Tracing
|
||||||
|
management.tracing.enabled=true
|
||||||
|
# Definiert, dass Traces immer gesammelt werden sollen (1.0 = 100%)
|
||||||
|
management.tracing.sampling.probability=1.0
|
||||||
|
|
||||||
|
# --- Micrometer Observation (für Metriken UND Tracing) ---
|
||||||
|
# Aktiviert die "Beobachtung" von HTTP Server Requests.
|
||||||
|
# Dies erzeugt automatisch Metriken (Timer) UND Traces für eingehende Anfragen.
|
||||||
|
management.observations.http.server.requests.enabled=true
|
||||||
|
|
||||||
|
# Fügt Anwendungs-Informationen zu den Metriken hinzu
|
||||||
|
management.info.env.enabled=true
|
||||||
|
|
||||||
|
# Definiert den Standard-Endpunkt, an den die Traces gesendet werden.
|
||||||
|
management.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package at.mocode.infrastructure.monitoring.client
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigurations
|
||||||
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner
|
||||||
|
|
||||||
|
class MonitoringClientAutoConfigurationTest {
|
||||||
|
|
||||||
|
private val contextRunner = ApplicationContextRunner()
|
||||||
|
.withConfiguration(AutoConfigurations.of(MonitoringClientAutoConfiguration::class.java))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should load monitoring properties correctly into the environment`() {
|
||||||
|
// Arrange
|
||||||
|
val expectedPropertyValue = "true"
|
||||||
|
val propertyKey = "management.observations.http.server.requests.enabled"
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
contextRunner.run { context ->
|
||||||
|
val actualPropertyValue = context.environment.getProperty(propertyKey)
|
||||||
|
assertThat(actualPropertyValue).isEqualTo(expectedPropertyValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package at.mocode.infrastructure.monitoring.client
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
|
|
||||||
|
// Minimaler Test-Application-Context für Library-Tests.
|
||||||
|
@SpringBootApplication
|
||||||
|
class MonitoringClientTestApplication
|
||||||
+2
@@ -2,7 +2,9 @@ package at.mocode.infrastructure.monitoring
|
|||||||
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||||
import org.springframework.boot.runApplication
|
import org.springframework.boot.runApplication
|
||||||
|
import zipkin2.server.internal.EnableZipkinServer
|
||||||
|
|
||||||
|
@EnableZipkinServer
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
class MonitoringServerApplication
|
class MonitoringServerApplication
|
||||||
|
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package at.mocode.infrastructure.monitoring
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
|
||||||
|
// Startet den ApplicationContext mit Webserver auf zufälligem Port und sicherer Testkonfiguration.
|
||||||
|
@SpringBootTest(
|
||||||
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = [
|
||||||
|
"server.port=0",
|
||||||
|
"management.server.port=0",
|
||||||
|
"zipkin.storage.type=mem",
|
||||||
|
"zipkin.self-tracing.enabled=false",
|
||||||
|
"management.tracing.enabled=false",
|
||||||
|
"management.zipkin.tracing.endpoint="
|
||||||
|
]
|
||||||
|
)
|
||||||
|
class MonitoringServerApplicationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `context loads successfully`() {
|
||||||
|
// Test ist bestanden, wenn der Kontext ohne Exception startet.
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user