Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State-Stored Aggregate: AggregateIdentifier has to be String #1062

Open
OLibutzki opened this issue Apr 18, 2019 · 4 comments

Comments

Projects
None yet
2 participants
@OLibutzki
Copy link
Contributor

commented Apr 18, 2019

Hi 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:

  1. 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.
  2. 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.

@smcvb

This comment has been minimized.

Copy link
Member

commented May 7, 2019

Hi @OLibutzki, I think I can alleviate the two major problems you've noted here:

  1. When you're using the Spring configuration of Axon, you will thus also be using the @Aggregate annotation I'd assume. The @Aggregate annotation has a field repository of type String. If you fill in the name of the repository you'd want to use for that Aggregate instance, it'll be wired upon start up. Thus, you should be able to create your GenericJpaRepository through it's Builder and provide the identifierConverter you wish.
  2. In practice, I'd assume the 'typed ID'/value object you'd use as the Aggregate Identifier would always have a to string format. From that point, I do not see why it would be to big a problem to have a fromString function as well. When I am leveraging typed ID's for Aggregate/Entities, more often that class contains a UUID any how (which as we now, has a fromString function.

Concluding, I think point 1 is luckily covered by the framework.
I trust in you that this wasn't clear from the Reference Guide; I can assure you we're consistently looking to improve the guide. It sounds like the Spring configuration is one of those areas which still needs some improvement.

Point 2 is something I to be honest do not view as to big of a struggle. If my assumptions on your question were incorrect in this area, please tell me so.

If you're however okay with the above given answer, I think we can close this issue of for now. :-)

@OLibutzki

This comment has been minimized.

Copy link
Contributor Author

commented May 7, 2019

Thanks for your answer @smcvb! I will try your suggestion and will come back to this issue after my evaluation. I suggest we keep this issue opened until I can provide some feedback.

@smcvb

This comment has been minimized.

Copy link
Member

commented May 7, 2019

Of course we'll keep it open @OLibutzki! Let us know as soon as you've had to chance to look into this.

@smcvb

This comment has been minimized.

Copy link
Member

commented May 7, 2019

By the way, I've checked whether the Reference Guide stated the setting of the repository when using Spring. You can find the bit I've just described here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.