April 30, 2022
All Posts, Programming
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!