State-Stored Aggregate: AggregateIdentifier has to be String
See original GitHub issueHi everyone,
we use state-based aggregates in our application.
We encountered that we have to use String for the aggregate identifier. Otherwise an exception occurs while sending a command to the aggregate by annotating the id field with @TargetAggregateIdentifier. In this example I tried to use a java.util.UUID:
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:816) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:797) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:324) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:139) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
at de.libutzki.axon.axonhierarchical.AxonHierarchicalApplication.main(AxonHierarchicalApplication.java:20) ~[classes/:na]
Caused by: java.lang.IllegalArgumentException: Provided id of the wrong type for class de.libutzki.axon.axonhierarchical.module2.entity.Module2Aggregate. Expected: class java.util.UUID, got class java.lang.String
at org.hibernate.internal.SessionImpl.find(SessionImpl.java:3503) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl.find(SessionImpl.java:3456) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:308) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at com.sun.proxy.$Proxy71.find(Unknown Source) ~[na:na]
at org.axonframework.modelling.command.GenericJpaRepository.doLoadWithLock(GenericJpaRepository.java:110) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.GenericJpaRepository.doLoadWithLock(GenericJpaRepository.java:53) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.LockingRepository.doLoad(LockingRepository.java:118) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.LockingRepository.doLoad(LockingRepository.java:52) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.AbstractRepository.lambda$load$4(AbstractRepository.java:122) ~[axon-modelling-4.1.1.jar:4.1.1]
at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1133) ~[na:na]
at org.axonframework.modelling.command.AbstractRepository.load(AbstractRepository.java:121) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.AggregateAnnotationCommandHandler$AggregateCommandHandler.handle(AggregateAnnotationCommandHandler.java:349) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.AggregateAnnotationCommandHandler$AggregateCommandHandler.handle(AggregateAnnotationCommandHandler.java:337) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.AggregateAnnotationCommandHandler.handle(AggregateAnnotationCommandHandler.java:125) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.modelling.command.AggregateAnnotationCommandHandler.handle(AggregateAnnotationCommandHandler.java:44) ~[axon-modelling-4.1.1.jar:4.1.1]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:57) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.messaging.interceptors.CorrelationDataInterceptor.handle(CorrelationDataInterceptor.java:65) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:74) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.SimpleCommandBus.handle(SimpleCommandBus.java:176) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:141) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:110) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.gateway.AbstractCommandGateway.send(AbstractCommandGateway.java:75) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.gateway.DefaultCommandGateway.send(DefaultCommandGateway.java:73) ~[axon-messaging-4.1.1.jar:4.1.1]
at org.axonframework.commandhandling.gateway.DefaultCommandGateway.sendAndWait(DefaultCommandGateway.java:90) ~[axon-messaging-4.1.1.jar:4.1.1]
at de.libutzki.axon.axonhierarchical.module2.Module2Runner.run(Module2Runner.java:42) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at com.sun.proxy.$Proxy85.run(Unknown Source) ~[na:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:813) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE]
... 4 common frames omitted
Caused by: org.hibernate.TypeMismatchException: Provided id of the wrong type for class de.libutzki.axon.axonhierarchical.module2.entity.Module2Aggregate. Expected: class java.util.UUID, got class java.lang.String
at org.hibernate.event.internal.DefaultLoadEventListener.checkIdClass(DefaultLoadEventListener.java:169) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1256) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl.access$1900(SessionImpl.java:207) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.doLoad(SessionImpl.java:2866) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2847) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
at org.hibernate.internal.SessionImpl.find(SessionImpl.java:3482) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final]
... 46 common frames omitted
While researching I stumbled upon org.axonframework.modelling.command.GenericJpaRepository.Builder.identifierConverter(Function<String, ?>)
But there are two major problems:
- I use Spring Boot and the GenericJpaRepositories are built in org.axonframework.spring.config.SpringAxonAutoConfigurer.registerAggregateBeanDefinitions(Configurer, BeanDefinitionRegistry) which cannot be customized easily. I have to override (nearly) the whole class to adjust the behaviour. Anyway, by doing this and setting
.identifierConverter(UUID::fromString)
the command can be handled. - The solution only works if I use a type which’s toString() result can be reverted back to the type (like UUID.fromString(String) does). In practice I don’t want to use a UUID as id but a value object like CustomerId which wraps a UUID. Having this I do not see any chance to annotate it with @AggregateIdentifier and to use it in a state-based aggregate.
As stated in #484 you also prefer the usage of value objects, but unfortunately I do not find a way to do this. Maybe it’s a lack of documentation, but I fear it’s a missing feature. That’s the reason why I decided to create this issue.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:2
- Comments:10 (9 by maintainers)
Top Results From Across the Web
State Stored Aggregates - Axon Reference Guide
An Aggregate Root must declare a field that contains the Aggregate Identifier. This identifier must be initialized at the latest when the first...
Read more >cqrs - Understanding of @AggregateIdentifier ...
1 Answer 1 · for event sourced aggregates all state changing events are stored in a event store · for state stored aggregates...
Read more >State-Stored Aggregate - Ecotone
An Aggregate is a regular object, which contains state and methods to alter that state. It can be described as Entity, which carry...
Read more >GenericJPARepository and EventStore at the same time
protected void setPublicationID(String uuid) ... private String demandeur; ... The aggregate identifier field is given a value identical to the event ...
Read more >CQRS using Java and Axon - Command module
@Aggregate public class ProductAggregate { @AggregateIdentifier private Long id; private String name; private int quantity; ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Hi,
We tested some possible solutions in the meantime. The suggested idea of @smcvb works well (custom JPA repository with suitable identifier converter), but this would mean a custom repository for each state stored aggregate. Therefore we decided to use another approach. We used a more generic identifier converter, which would use the Spring conversion mechanism, and added it to the registerAggregateBeanDefinitions in the configurer.
So the basic idea is to find the type of the aggregate identifier and - unless it is already a String - convert it via the Spring conversion service. One would then have a suitable converter for each identifier.
In case of UUID identifiers one would only need the Spring conversion service, as it is already equipped with a String to UUID converter.
The only disadvantage of this approach is that we have to override the whole class to modify the private method. Maybe it would be possible to improve the code in this regard?
Best regards
Nils
Reopening seems reasonable to me, so let’s do that 😃