UUID Identifier for Axon Aggregate


As noted in this issue, the identifier for an Axon aggregate most be a String data type. This prevents us from using UUID as a data type which would give us this error whenever we try to update the aggregate:

java.lang.IllegalArgumentException: Provided id of the wrong type for class ....<aggregate>. Expected: class java.util.UUID, got class java.lang.String

 

If we wish to continue to use the UUID type we must define a custom GenericJpaRepository for each aggregate which will provide an identifier converter method UUID::fromString .

Here is a Spring Boot configuration class which does that. Similar to how Spring Boot performs a component scan from a base package, this configuration class will define a base package to start from for scanning for classes with the @Aggregate annotation. In this example the base package would be defined in a properties file but you may replace that with a literal. For each aggregate class found from the scan, a GenericJpaRepository bean will be instantiated and registered with the name axon<AggregateName>Repository.


@Configuration
public class GenericJpaRepositoryConfig {

    @Value("${base-package}")
    private String basePackage;

    @Autowired
    public void registerGenericJpaRepositories(ApplicationContext applicationContext,
                                               EntityManagerProvider provider,
                                               @Qualifier("eventBus") EventBus simpleEventBus) throws ClassNotFoundException {

        List<Class<?>> classes = this.getAggregateClasses();

        ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();

        for (Class<?> clazz : classes) {
            GenericJpaRepository<?> repository = aggregateJpaRepository(provider, simpleEventBus, clazz);
            beanFactory.registerSingleton("axon" + clazz.getSimpleName() + "Repository", repository);
        }

    }

    private <T> GenericJpaRepository<T> aggregateJpaRepository(
            EntityManagerProvider provider,
            EventBus simpleEventBus,
            Class<T> className) {

        return GenericJpaRepository
                .builder(className)
                .identifierConverter(UUID::fromString)
                .entityManagerProvider(provider)
                .eventBus(simpleEventBus)
                .build();
    }

    private List<Class<?>> getAggregateClasses() throws ClassNotFoundException {
        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(true);

        List<Class<?>> classes = new ArrayList<>();

        if (basePackage == null || basePackage.isEmpty()) {
            basePackage = this.getClass().getPackageName();
        }

        for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
            Class<?> clazz = Class.forName(bd.getBeanClassName());
            Aggregate annotation = clazz.getAnnotation(Aggregate.class);

            if (annotation != null) {
                classes.add(clazz);
            }

        }

        return classes;
    }
}
 

 

Now we just need to add the @Aggregate(repository = "axon<AggregateName>Repository") annotation to the aggregate class so that Axon knows to use the custom GenericJpaRepository.

If we choose, we can change the format of the bean name for the repository on the line:

beanFactory.registerSingleton("axon" + clazz.getSimpleName() + "Repository", repository);

 

The naming convention is arbitrary. The important thing is that it matches the value in the @Aggregate annotation on the aggregate class.

After introducing this configuration class and properly setting the repository name in the @Aggregate annotation on our aggregate class, we may successfully use UUID as the aggregate identifier type!

Leave a Reply

Your email address will not be published. Required fields are marked *