PROBLEM: In an aggregate-state Axon SpringBoot application @AggregateIdentifier is of type UUID and PostgreSQL database column is of type UUID. When persisted on create-commands - identifier is successfully stored. When sending update-command warning rises and command is not delivered to aggregate handler, because @TargetAggregateIdentifier expects String type as described by Axon here:
java.lang.IllegalArgumentException: Provided id of the wrong type for class ....MyAggregate. Expected: class java.util.UUID, got class java.lang.String
RESEARCH:
Working solution is to refactor the domain to
Stringtypes for all aggregate indentifiers. Database@Idfiled also should be converted tovarchar(36)type to storeUUID.toString()as primary key. Major minus is that it's inefficient: 9 times bigger size and slower String reads.Minimal boilerplate and compromise solution is to refactor the domain to
Stringtypes for all aggregate indentifiers and usejavax.persistence.Convertersto convert String to UUID in JPA layer while persisting:@Converter public class UuidJpaConverter implements AttributeConverter<String, UUID> { @Override public UUID convertToDatabaseColumn(String uuid) { return fromString(uuid); } @Override public String convertToEntityAttribute(UUID uuid) { return uuid.toString(); } } ... @Aggregate @Entity @IdClass(UuidKey.class) public class MyAggregate implements Serializable { @AggregateIdentifier @Id private String uuid; ... } public class UuidKey implements Serializable { @Column(name = "uuid", nullable = false, updatable = false) @Convert(converter = UuidJpaConverter.class) private String uuid; }
But it results in:
o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: column "uuid" is of type uuid but expression is of type bytea
Use Axon identifier converter, but it requires custom GenericJpaRepository for each aggregate and actually never calls identifierConverter handler on a breakpoint:
@Configuration public class IdentifierConverter { @Bean public GenericJpaRepository<MyAggregate> aggregateJpaRepository( EntityManagerProvider provider, @Qualifier("eventBus") EventBus simpleEventBus) { GenericJpaRepository<MyAggregate> repository = GenericJpaRepository .builder(MyAggregate.class) -> .identifierConverter(name -> UUID.fromString(name)) .entityManagerProvider(provider) .eventBus(simpleEventBus) .build(); return repository; } }
And results in:
o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: column "uuid" is of type uuid but expression is of type character varying
There is also a suggested universal GenericJpaRepository solution to find the type of the aggregate identifier and - unless it is already a String - convert it via the Spring conversion service. But it is not clear where to bind it added it to the
registerAggregateBeanDefinitionsin the configurer when we have Axon autoconfiguration and beans are expected in favour of Configurer:final Class<?> aggregateIdentifierType = Stream.of( aggregateType.getDeclaredFields( ) ) .filter( field -> field.isAnnotationPresent( AggregateIdentifier.class ) ) .map( field -> field.getType( ) ) .findFirst( ) .orElseThrow( ( ) -> new IllegalStateException( "The aggregate '" + aggregate + "' does not have an identifier." ) ); aggregateConf.configureRepository( c -> GenericJpaRepository.builder( aggregateType ) .identifierConverter( string -> { if ( aggregateType == String.class ) { return string; } else { try { final ConversionService conversionService = beanFactory.getBean( ConversionService.class ); return conversionService.convert( string, aggregateIdentifierType ); } catch ( final NoSuchBeanDefinitionException ex ) { throw new IllegalStateException( "Unable to convert String to aggregate identifier of type '" + aggregateIdentifierType.getName( ) + "'. A conversion service is missing." ); } } } ) ... @Named final class MyIdConverter implements Converter<String, MyId> { ... @Override public MyId convert( final String source ) { return MyId.fromString( source ); } }
QUESTION: How to keep UUID type of aggregate identifier in PostgreSQL database - with UUID as preferred type for @AggregateIdentifier or at least String. Also why is only String currently supported if at 2010 UUID was widely used in Axon?