mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add schema validator
use ConfigurationProperties instead of Value for configuration
This commit is contained in:
@@ -9,17 +9,19 @@ import com.example.demo.services.database.city.CityServiceImpl
|
||||
import com.example.demo.services.database.product.ProductService
|
||||
import com.example.demo.services.database.product.ProductServiceImpl
|
||||
import com.example.demo.services.kafka.Producer
|
||||
import com.example.demo.services.kafka.SchemaValidator
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(KafkaProperties::class)
|
||||
class AppConfig(
|
||||
@Value("\${kafka.producer.product.default-sync-topic}")
|
||||
private val defaultProductSyncTopic: String
|
||||
@Autowired private val kafkaProperties: KafkaProperties,
|
||||
) {
|
||||
@Bean
|
||||
fun objectMapper(): ObjectMapper {
|
||||
@@ -38,12 +40,15 @@ class AppConfig(
|
||||
@Autowired productRepository: ProductRepository,
|
||||
@Autowired producer: Producer,
|
||||
): ProductService = ProductServiceImpl(
|
||||
defaultProductSyncTopic,
|
||||
kafkaProperties.producer.product.defaultSyncTopic,
|
||||
productRepository,
|
||||
producer,
|
||||
)
|
||||
|
||||
@Bean
|
||||
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
||||
|
||||
@Bean
|
||||
fun schemaValidator(): SchemaValidator = SchemaValidator(kafkaProperties.validation.schema)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.example.demo.services.kafka.Consumer
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig
|
||||
import org.apache.kafka.common.serialization.StringDeserializer
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
|
||||
@@ -14,23 +13,18 @@ import org.springframework.kafka.core.DefaultKafkaConsumerFactory
|
||||
|
||||
@Configuration
|
||||
class KafkaConsumerConfig(
|
||||
@Value("\${kafka.bootstrap-servers}")
|
||||
val servers: String,
|
||||
@Value("\${kafka.consumer.group-id}")
|
||||
val consumerGroup: String,
|
||||
@Autowired val kafkaProperties: KafkaProperties
|
||||
) {
|
||||
@Bean
|
||||
fun consumer(
|
||||
@Autowired cityService: CityService,
|
||||
): Consumer = Consumer(
|
||||
cityService = cityService,
|
||||
)
|
||||
): Consumer = Consumer(cityService)
|
||||
|
||||
@Bean
|
||||
fun consumerFactory(): ConsumerFactory<String, String> {
|
||||
val configs = mapOf(
|
||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to servers,
|
||||
ConsumerConfig.GROUP_ID_CONFIG to consumerGroup,
|
||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to kafkaProperties.bootstrapServers,
|
||||
ConsumerConfig.GROUP_ID_CONFIG to kafkaProperties.consumer.groupId,
|
||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java,
|
||||
)
|
||||
|
||||
@@ -2,10 +2,10 @@ package com.example.demo.config
|
||||
|
||||
import com.example.demo.services.kafka.Producer
|
||||
import com.example.demo.services.kafka.ProducerImpl
|
||||
import com.example.demo.services.kafka.SchemaValidator
|
||||
import org.apache.kafka.clients.producer.ProducerConfig
|
||||
import org.apache.kafka.common.serialization.StringSerializer
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
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
|
||||
@@ -14,14 +14,13 @@ import org.springframework.kafka.core.ProducerFactory
|
||||
|
||||
@Configuration
|
||||
class KafkaProducerConfig(
|
||||
@Value("\${kafka.bootstrap-servers}")
|
||||
val servers: String
|
||||
@Autowired val kafkaProperties: KafkaProperties
|
||||
) {
|
||||
@Bean
|
||||
fun producerFactory(): ProducerFactory<String, Any> {
|
||||
val configProps: MutableMap<String, Any> = HashMap()
|
||||
|
||||
configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = servers
|
||||
configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = kafkaProperties.bootstrapServers
|
||||
configProps[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
|
||||
configProps[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
|
||||
|
||||
@@ -36,7 +35,9 @@ class KafkaProducerConfig(
|
||||
@Bean
|
||||
fun producer(
|
||||
@Autowired kafkaTemplate: KafkaTemplate<String, Any>,
|
||||
@Autowired schemaValidator: SchemaValidator,
|
||||
): Producer = ProducerImpl(
|
||||
kafkaTemplate,
|
||||
schemaValidator,
|
||||
)
|
||||
}
|
||||
30
src/main/kotlin/com/example/demo/config/KafkaProperties.kt
Normal file
30
src/main/kotlin/com/example/demo/config/KafkaProperties.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.example.demo.config
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding
|
||||
|
||||
@ConfigurationProperties("kafka")
|
||||
data class KafkaProperties @ConstructorBinding constructor(
|
||||
val bootstrapServers: String,
|
||||
val producer: Producer,
|
||||
val consumer: Consumer,
|
||||
val validation: Validation,
|
||||
) {
|
||||
data class Producer(
|
||||
val product: Product,
|
||||
) {
|
||||
data class Product(
|
||||
val defaultSyncTopic: String
|
||||
)
|
||||
}
|
||||
|
||||
data class Consumer(
|
||||
val groupId: String,
|
||||
val topics: String,
|
||||
val autoStartup: Boolean,
|
||||
)
|
||||
|
||||
data class Validation(
|
||||
val schema: Map<String, String>
|
||||
)
|
||||
}
|
||||
@@ -13,10 +13,14 @@ import org.springframework.stereotype.Service
|
||||
@Service
|
||||
class ProducerImpl(
|
||||
private val kafkaTemplate: KafkaTemplate<String, Any>,
|
||||
private val schemaValidator: SchemaValidator,
|
||||
): Producer {
|
||||
override fun produceProductInfo(topicName: String, product: Product) {
|
||||
|
||||
val serializedProduct = Json.encodeToJsonElement(ProductDto(product))
|
||||
|
||||
schemaValidator.validate("product-sync", serializedProduct)
|
||||
|
||||
val message: Message<String> = MessageBuilder
|
||||
.withPayload(serializedProduct.toString())
|
||||
.setHeader(KafkaHeaders.TOPIC, topicName)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.example.demo.services.kafka
|
||||
|
||||
import io.github.optimumcode.json.schema.JsonSchema
|
||||
import io.github.optimumcode.json.schema.ValidationError
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import org.springframework.util.ResourceUtils
|
||||
|
||||
class SchemaValidator(
|
||||
private val schemaMap: Map<String, String>,
|
||||
) {
|
||||
private val loadedSchema: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
fun validate(schemaName: String, value: JsonElement) {
|
||||
|
||||
val schema = JsonSchema.fromDefinition(
|
||||
getSchema(schemaName),
|
||||
)
|
||||
|
||||
val errors = mutableListOf<ValidationError>()
|
||||
|
||||
val valid = schema.validate(value, errors::add)
|
||||
if (!valid) {
|
||||
// todo throw another exception
|
||||
println(errors.toString())
|
||||
|
||||
throw RuntimeException("invalid schema")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSchema(schemaName: String): String {
|
||||
val loaded = loadedSchema[schemaName]
|
||||
if (loaded != null) {
|
||||
return loaded
|
||||
}
|
||||
|
||||
val schemaFile = schemaMap[schemaName]
|
||||
?: // todo throw another exception
|
||||
throw RuntimeException("unknown json-schema")
|
||||
|
||||
val schema = ResourceUtils.getFile("classpath:json-schemas/$schemaFile")
|
||||
.readText(Charsets.UTF_8)
|
||||
loadedSchema[schemaName] = schema
|
||||
|
||||
return schema
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user