-
Notifications
You must be signed in to change notification settings - Fork 0
Using kotlin TypeId type‐safe ids with Spring
AdiT edited this page May 21, 2024
·
2 revisions
To enable conversion from/to TypeId created identities create converters and use with an UUID database type.
- Reading converter:
import earth.adi.typeid.Id
import earth.adi.typeid.TypeId
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.data.convert.ReadingConverter
@ReadingConverter
class IdReadingConverter : GenericConverter {
override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair> {
return mutableSetOf(GenericConverter.ConvertiblePair(UUID::class.java, Id::class.java))
}
override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any {
val entityType = targetType.resolvableType.resolveGenerics()[0]
if (targetType.type == Id::class.java) {
return TypeId.of(entityType, source as UUID)
} else {
throw IllegalArgumentException("Unsupported targetType: $targetType")
}
}
}
- Writing converter:
import earth.adi.typeid.Id
import java.util.UUID
import org.springframework.core.convert.converter.Converter
import org.springframework.data.convert.WritingConverter
@WritingConverter
class IdWritingConverter : Converter<Id<*>, UUID> {
override fun convert(source: Id<*>): UUID {
return source.uuid
}
}
Register the converters:
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration
@Configuration
class DataModuleConfiguration : AbstractJdbcConfiguration() {
override fun userConverters(): List<*> {
return listOf(
IdReadingConverter(),
IdWritingConverter(),
)
}
}
To enable typed parameters on the rest controller interfaces, create a formatters configurer and add it to the registry:
import earth.adi.typeid.Id
import earth.adi.typeid.TypeId
import java.util.*
import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.format.FormatterRegistry
import org.springframework.stereotype.Component
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Component
class FormattersConfigurer : WebMvcConfigurer {
override fun addFormatters(registry: FormatterRegistry) {
registry.addConverter(IdConverter())
}
class IdConverter : GenericConverter {
override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair> {
return mutableSetOf(GenericConverter.ConvertiblePair(String::class.java, Id::class.java))
}
override fun convert(
source: Any?,
sourceType: TypeDescriptor,
targetType: TypeDescriptor
): Any {
val entityType = targetType.resolvableType.resolveGenerics()[0]
if (targetType.type == Id::class.java) {
return TypeId.parse(entityType, source as String)
} else {
throw IllegalArgumentException("Unsupported targetType: $targetType")
}
}
}
}
Then use it like:
import org.springframework.web.bind.annotation.RequestHeader
// ...
@RequestHeader("X-User-Id") userId: UserId,
To enable body ids to json conversions, add the TypeId.jacksonModule()
, example configuration:
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.SerializationFeature
import earth.adi.typeid.TypeId
import java.time.Instant
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
@Configuration
class JsonConfiguration {
@Bean
fun jsonCustomizer(): Jackson2ObjectMapperBuilderCustomizer {
return Jackson2ObjectMapperBuilderCustomizer { builder: Jackson2ObjectMapperBuilder ->
configureMapperBuilder(builder)
}
}
@Bean
fun apiObjectMapper(): ObjectMapper {
return configureMapperBuilder(Jackson2ObjectMapperBuilder()).build()
}
companion object {
private fun configureMapperBuilder(
builder: Jackson2ObjectMapperBuilder
): Jackson2ObjectMapperBuilder {
return builder
.modulesToInstall(TypeId.jacksonModule())
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.featuresToEnable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.indentOutput(true)
}
}
}