I have a lot of value objects that basically represent UUIDs as the primary identifier of the entity / aggregate:
@Singleton
class FooIdConverter : AttributeConverter<FooId, UUID> {
override fun convertToPersistedValue(
@Nullable entityValue: FooId?, @NonNull context: ConversionContext
): UUID? {
return entityValue?.id
}
override fun convertToEntityValue(
persistedValue: UUID?, context: ConversionContext
): FooId? {
return if (persistedValue == null) null else FooId(persistedValue)
}
}
@TypeDef(type = DataType.UUID, converter = FooIdConverter::class)
data class FooId(val id: UUID)
Of course, I could copy the converter for every other ID value object (BarIdConverter
, FooBarIdConverter
, ...) but this does not seem to be really DRY.
Is there a better way to do this in Kotlin?
I tried something like this but it does not work:
abstract class EntityId(open val id: UUID)
fun <Id : EntityId> createConverterFor(clazz: KClass<Id>): KClass<out AttributeConverter<Id, UUID>> {
return object : AttributeConverter<Id, UUID> {
override fun convertToPersistedValue(entityValue: Id?, context: ConversionContext): UUID? {
return entityValue?.id
}
override fun convertToEntityValue(persistedValue: UUID?, context: ConversionContext): Id? {
return if (persistedValue == null) null else clazz.primaryConstructor!!.call(
persistedValue
)
}
}::class
}
val ProjectIdConverter = createConverterFor(FooId::class)
@TypeDef(type = DataType.UUID, converter = ProjectIdConverter)
data class FooId(override val id: UUID) : EntityId(id)
// EDIT (Solution):
Thanks to @Denis, I have now two solutions. The first and most straight forward:
@Singleton
class EntityIdConverter() : AttributeConverter<EntityId, UUID> {
override fun convertToPersistedValue(
@Nullable entityValue: EntityId?, @NonNull context: ConversionContext
): UUID? {
return entityValue?.id
}
override fun convertToEntityValue(
persistedValue: UUID?, context: ConversionContext
): EntityId? {
val ctx = context as ArgumentConversionContext<*>
return if (persistedValue == null) {
null
} else {
ctx.argument.type.getDeclaredConstructor(UUID::class.java)
.newInstance(persistedValue) as EntityId
}
}
}
@TypeDef(type = DataType.UUID, converter = EntityIdConverter::class)
abstract class EntityId(open val id: UUID)
data class ProjectId(override val id: UUID) : EntityId(id)
The second option is to use KSP and generate the AttributeConverter
. This option is described in an answer by myself.
You can have a shared converted and recognize the required type using the context which should be of type ArgumentConversionContext
and getArgument
will return the type.