When dealing with exception thrown in @EventHandler methods, we use @ExceptionHandler reside in the same class to catch "known exceptions" and take a resilience manner for handling those broken "events".
@Slf4j
@RequiredArgsConstructor
@Service
public class PersonProjection {
private final PersonRepository personRepository;
@EventHandler
public void on(PersonCreatedEvent event) throws CheckedBusinessException {
if(event.someUncheckedRulesViolated()) {
throw new CheckedBusinessException(event);
}
Person person = new Person();
// this will throw exception if not checked like above.
Objects.requireNonNull(person.getIdentityCard());
BeanUtils.copyProperties(event, person);
personRepository.save(person);
}
@ExceptionHandler
public void handle(CheckedBusinessException exception) {
log.error("CheckedBusinessException handled");
}
}
This way, we can log and ignore such accidently happened historical events, because Axon will only retry those Runtime exceptions.
But if we have a DataAccessException/ConstraintViolationException (Such as duplicate key) occurring at the personRepository.saveAndFlush()'s committing phase, we are not able to handle the exception like this.
@EventHandler
public void on(PersonCreatedEvent event) {
try {
Person person = new Person();
BeanUtils.copyProperties(event, person);
// we use saveAndFlush() to catch the DataAccessException.
personRepository.saveAndFlush(person);
} catch (DataAccessException ex) {
log.error("catch DataAccessException.");
throw new CheckedDataAccessException(event);
}
}
@ExceptionHandler
public void handle(CheckedDataAccessException exception) {
// Why Axon will still enter Retry mode event if we handled this type of exception here?
log.error("CheckedDataAccessException handled");
}
After check some of Error handling logic, we know there's an UnexpectedRollbackException occurring, so that Axon will retry it anyway.
The question is, how should I handle these events if we know the exception could happen only upon committing the unitOfWork?
I have created a repo to demo this behavior here at: Github Repository showcase_axon_exception.
2021-11-01 11:54:37.177 WARN 16076 --- [cation.query]-0] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1062, SQLState: 23000
2021-11-01 11:54:37.177 ERROR 16076 --- [cation.query]-0] o.h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry 'string' for key 'ux_person_information_name'
2021-11-01 11:54:37.746 ERROR 16076 --- [cation.query]-0] i.c.s.a.query.PersonEventHandler : CheckedDataAccessException handled
info.cepheus.showcase_axon_exception.coreapi.exception.CheckedDataAccessException: DataAccessException occurred while persist event: { personId: f16f76f8-db33-422f-80a0-4d0f15ea19a0, name: string }
at info.cepheus.showcase_axon_exception.infrastructure.PersonQueryServiceImpl.save(PersonQueryServiceImpl.java:49) ~[main/:na]
at info.cepheus.showcase_axon_exception.infrastructure.PersonQueryServiceImpl$$FastClassBySpringCGLIB$$1c640112.invoke(<generated>) ~[main/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.12.jar:5.3.12]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.12.jar:5.3.12]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.12.jar:5.3.12]
at info.cepheus.showcase_axon_exception.infrastructure.PersonQueryServiceImpl$$EnhancerBySpringCGLIB$$3de709c.save(<generated>) ~[main/:na]
at info.cepheus.showcase_axon_exception.application.query.PersonEventHandler.on(PersonEventHandler.java:40) ~[main/: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:564) ~[na:na]
at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.handle(AnnotatedMessageHandlingMember.java:144) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$NoMoreInterceptors.handle(AnnotatedHandlerInspector.java:372) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.lambda$handle$0(AnnotatedHandlerInspector.java:352) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.MessageHandlerInterceptorDefinition$ResultHandlingInterceptorMember.handle(MessageHandlerInterceptorDefinition.java:80) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.doHandle(AnnotatedHandlerInspector.java:358) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.lambda$handle$1(AnnotatedHandlerInspector.java:353) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.InterceptorChainParameterResolverFactory.callWithInterceptorChain(InterceptorChainParameterResolverFactory.java:75) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.handle(AnnotatedHandlerInspector.java:352) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.lambda$handle$0(AnnotatedHandlerInspector.java:352) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.MessageHandlerInterceptorDefinition$ResultHandlingInterceptorMember.handle(MessageHandlerInterceptorDefinition.java:80) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.doHandle(AnnotatedHandlerInspector.java:358) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.lambda$handle$1(AnnotatedHandlerInspector.java:353) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.InterceptorChainParameterResolverFactory.callWithInterceptorChain(InterceptorChainParameterResolverFactory.java:75) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$ChainedMessageHandlerInterceptorMember.handle(AnnotatedHandlerInspector.java:352) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.AnnotationEventHandlerAdapter.handle(AnnotationEventHandlerAdapter.java:94) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.SimpleEventHandlerInvoker.handle(SimpleEventHandlerInvoker.java:112) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.MultiEventHandlerInvoker.handle(MultiEventHandlerInvoker.java:89) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.AbstractEventProcessor.lambda$null$1(AbstractEventProcessor.java:165) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:57) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.interceptors.CorrelationDataInterceptor.handle(CorrelationDataInterceptor.java:65) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.extensions.tracing.OpenTraceHandlerInterceptor.handle(OpenTraceHandlerInterceptor.java:106) ~[axon-tracing-4.5.jar:4.5]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor.lambda$new$1(TrackingEventProcessor.java:185) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.AbstractEventProcessor.lambda$processInUnitOfWork$2(AbstractEventProcessor.java:173) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.BatchingUnitOfWork.executeWithResult(BatchingUnitOfWork.java:86) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.AbstractEventProcessor.processInUnitOfWork(AbstractEventProcessor.java:159) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor.processBatch(TrackingEventProcessor.java:451) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor.processingLoop(TrackingEventProcessor.java:294) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor$TrackingSegmentWorker.run(TrackingEventProcessor.java:1005) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor$WorkerLauncher.run(TrackingEventProcessor.java:1151) ~[axon-messaging-4.5.4.jar:4.5.4]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
2021-11-01 11:54:38.788 ERROR 16076 --- [cation.query]-0] i.c.s.config.MyAxonErrorHandler : Transaction silently rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:752) ~[spring-tx-5.3.12.jar:5.3.12]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.3.12.jar:5.3.12]
at org.axonframework.spring.messaging.unitofwork.SpringTransactionManager.commitTransaction(SpringTransactionManager.java:80) ~[axon-spring-4.5.4.jar:4.5.4]
at org.axonframework.spring.messaging.unitofwork.SpringTransactionManager$1.commit(SpringTransactionManager.java:63) ~[axon-spring-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.UnitOfWork.lambda$attachTransaction$0(UnitOfWork.java:273) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.MessageProcessingContext.notifyHandlers(MessageProcessingContext.java:72) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.BatchingUnitOfWork.lambda$notifyHandlers$2(BatchingUnitOfWork.java:155) ~[axon-messaging-4.5.4.jar:4.5.4]
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
at org.axonframework.messaging.unitofwork.BatchingUnitOfWork.notifyHandlers(BatchingUnitOfWork.java:155) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.changePhase(AbstractUnitOfWork.java:222) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commitAsRoot(AbstractUnitOfWork.java:83) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commit(AbstractUnitOfWork.java:71) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.messaging.unitofwork.BatchingUnitOfWork.executeWithResult(BatchingUnitOfWork.java:111) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.AbstractEventProcessor.processInUnitOfWork(AbstractEventProcessor.java:159) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor.processBatch(TrackingEventProcessor.java:451) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor.processingLoop(TrackingEventProcessor.java:294) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor$TrackingSegmentWorker.run(TrackingEventProcessor.java:1005) ~[axon-messaging-4.5.4.jar:4.5.4]
at org.axonframework.eventhandling.TrackingEventProcessor$WorkerLauncher.run(TrackingEventProcessor.java:1151) ~[axon-messaging-4.5.4.jar:4.5.4]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
2021-11-01 11:54:38.788 WARN 16076 --- [cation.query]-0] o.a.e.TrackingEventProcessor : Releasing claim on token and preparing for retry in 4s
2021-11-01 11:54:38.798 INFO 16076 --- [cation.query]-0] o.a.e.TrackingEventProcessor : Released claim