Refactoring where events are (packaging-wise), what happens to events already in evenstore (MongoDB)

Hi,

I’ve run some tests and events are persisted in MongoDB. I then refactor in which package the events are placed (com.foo.SomeEvent -> com.foo.someotherpackage.SomeEvent). When I rerun some tests the old events are fetched from MongoDB and I get an exception like this:

2014-05-25 10:06:57,791 [main] WARN - Command resulted in exception: com.navarsete.stand012.supplier.command.SendOrderConfirmationCommand
org.axonframework.serializer.UnknownSerializedTypeException: Could not deserialize a message. The serialized type is unknown: com.navarsete.stand012.supplier.event.OrderConfirmationSentEvent (rev. null)
at org.axonframework.serializer.AbstractXStreamSerializer.classForType(AbstractXStreamSerializer.java:236)
at org.axonframework.serializer.LazyDeserializingObject.(LazyDeserializingObject.java:61)
at org.axonframework.serializer.SerializedMessage.(SerializedMessage.java:59)
at org.axonframework.serializer.SerializedEventMessage.(SerializedEventMessage.java:56)
at org.axonframework.serializer.SerializedDomainEventMessage.(SerializedDomainEventMessage.java:56)
at org.axonframework.upcasting.UpcastUtils.upcastAndDeserialize(UpcastUtils.java:71)
at org.axonframework.eventstore.mongo.DocumentPerEventStorageStrategy$EventEntry.getDomainEvents(DocumentPerEventStorageStrategy.java:237)
at org.axonframework.eventstore.mongo.DocumentPerEventStorageStrategy.extractEventMessages(DocumentPerEventStorageStrategy.java:89)
at org.axonframework.eventstore.mongo.MongoEventStore$CursorBackedDomainEventStream.initializeNextItem(MongoEventStore.java:316)
at org.axonframework.eventstore.mongo.MongoEventStore$CursorBackedDomainEventStream.next(MongoEventStore.java:302)
at org.axonframework.eventsourcing.EventSourcingRepository$CapturingEventStream.next(EventSourcingRepository.java:328)
at org.axonframework.eventsourcing.AbstractEventSourcedAggregateRoot.initializeState(AbstractEventSourcedAggregateRoot.java:59)
at org.axonframework.eventsourcing.EventSourcingRepository.doLoad(EventSourcingRepository.java:177)
at org.axonframework.eventsourcing.EventSourcingRepository.doLoad(EventSourcingRepository.java:56)
at org.axonframework.repository.AbstractRepository.load(AbstractRepository.java:78)
at org.axonframework.repository.LockingRepository.load(LockingRepository.java:102)
at org.axonframework.repository.LockingRepository.load(LockingRepository.java:51)
at org.axonframework.commandhandling.annotation.AggregateAnnotationCommandHandler.loadAggregate(AggregateAnnotationCommandHandler.java:263)
at org.axonframework.commandhandling.annotation.AggregateAnnotationCommandHandler.access$100(AggregateAnnotationCommandHandler.java:52)
at org.axonframework.commandhandling.annotation.AggregateAnnotationCommandHandler$AggregateCommandHandler.handle(AggregateAnnotationCommandHandler.java:295)
at org.axonframework.commandhandling.annotation.AggregateAnnotationCommandHandler.handle(AggregateAnnotationCommandHandler.java:258)
at org.axonframework.commandhandling.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:63)
at org.axonframework.commandhandling.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:69)
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:127)
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:103)
at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:70)
at org.axonframework.commandhandling.gateway.AbstractCommandGateway.sendAndForget(AbstractCommandGateway.java:86)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$DispatchOnInvocationHandler.invoke(GatewayProxyFactory.java:407)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$DispatchOnInvocationHandler.invoke(GatewayProxyFactory.java:367)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$FireAndForget.invoke(GatewayProxyFactory.java:570)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$NullOnTimeout.invoke(GatewayProxyFactory.java:475)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$NullOnInterrupted.invoke(GatewayProxyFactory.java:493)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$WrapNonDeclaredCheckedExceptions.invoke(GatewayProxyFactory.java:450)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$GatewayInvocationHandler.invoke(GatewayProxyFactory.java:350)
at com.sun.proxy.$Proxy15.send(Unknown Source)
at com.navarsete.stand012.supplier.SupplierSaga.handle(SupplierSaga.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.axonframework.common.annotation.MethodMessageHandler.invoke(MethodMessageHandler.java:85)
at org.axonframework.saga.annotation.SagaMethodMessageHandler.invoke(SagaMethodMessageHandler.java:206)
at org.axonframework.saga.annotation.AbstractAnnotatedSaga.handle(AbstractAnnotatedSaga.java:80)
at org.axonframework.saga.AbstractSagaManager.loadAndInvoke(AbstractSagaManager.java:249)
at org.axonframework.saga.AbstractSagaManager.invokeExistingSagas(AbstractSagaManager.java:135)
at org.axonframework.saga.AbstractSagaManager.handle(AbstractSagaManager.java:106)
at org.axonframework.eventhandling.SimpleCluster.doPublish(SimpleCluster.java:65)
at org.axonframework.eventhandling.AbstractCluster.publish(AbstractCluster.java:77)
at org.axonframework.eventhandling.ClusteringEventBus$SimpleEventBusTerminal.publish(ClusteringEventBus.java:132)
at org.axonframework.eventhandling.ClusteringEventBus.publish(ClusteringEventBus.java:94)
at org.axonframework.unitofwork.DefaultUnitOfWork.publishEvents(DefaultUnitOfWork.java:270)
at org.axonframework.unitofwork.DefaultUnitOfWork.doCommit(DefaultUnitOfWork.java:140)
at org.axonframework.unitofwork.NestableUnitOfWork.commit(NestableUnitOfWork.java:59)
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:137)
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:103)
at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:70)
at org.axonframework.commandhandling.gateway.AbstractCommandGateway.sendAndForget(AbstractCommandGateway.java:86)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$DispatchOnInvocationHandler.invoke(GatewayProxyFactory.java:407)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$DispatchOnInvocationHandler.invoke(GatewayProxyFactory.java:367)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$FireAndForget.invoke(GatewayProxyFactory.java:570)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$NullOnTimeout.invoke(GatewayProxyFactory.java:475)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$NullOnInterrupted.invoke(GatewayProxyFactory.java:493)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$WrapNonDeclaredCheckedExceptions.invoke(GatewayProxyFactory.java:450)
at org.axonframework.commandhandling.gateway.GatewayProxyFactory$GatewayInvocationHandler.invoke(GatewayProxyFactory.java:350)
at com.sun.proxy.$Proxy15.send(Unknown Source)
at com.navarsete.stand012.wholesaler.RunWholesalerSaga.main(RunWholesalerSaga.java:29)
Caused by: com.thoughtworks.xstream.mapper.CannotResolveClassException: com.navarsete.stand012.supplier.event.OrderConfirmationSentEvent
at com.thoughtworks.xstream.mapper.DefaultMapper.realClass(DefaultMapper.java:79)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.DynamicProxyMapper.realClass(DynamicProxyMapper.java:55)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.PackageAliasingMapper.realClass(PackageAliasingMapper.java:88)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.ClassAliasingMapper.realClass(ClassAliasingMapper.java:79)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.ArrayMapper.realClass(ArrayMapper.java:74)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.MapperWrapper.realClass(MapperWrapper.java:30)
at com.thoughtworks.xstream.mapper.CachingMapper.realClass(CachingMapper.java:45)
at org.axonframework.serializer.AbstractXStreamSerializer.classForType(AbstractXStreamSerializer.java:234)
… 65 more

What is the correct way of approaching a change like this (assuming I also have the system in production with many existing events that may be impacted in the same way)? Is it an Upcaster I need to write in this case?

Regards,
Viggo

Hi Viggo,

XStream finds a class based on the fully qualified name, by default. So if you move a package, that fqn isn’t valid anymore.
Normally, upcasters are the solution. However, they are like using a canon to shoot a fly. If you use XStream, you can also use aliases.

If you have package com.mycompany.a which has been moved to com.mycompany.b, then you would configure 2 aliases in XStream:

xStream.aliasPackage(“com.mycompany.a”, “com.mycompany.b”);
and also:
xStream.aliasPackage(“com.mycompany.b”, “com.mycompany.b”);

the last one ensures that the b package is also stored as such. Aliases work in 2 directions…
Check out the alias tutorial in XStream: http://xstream.codehaus.org/alias-tutorial.html

My advice would be to create a alias for the package prefix that your application uses. That makes the serialized form of events smaller.

Cheers,

Allard

Hi,

I encountered the same problem when trying to consume events from amqp. the SpringAMQPMessageSource in configured to use axonframework.serialization.Serializer. So how is it configured to it to solve this problem.

Hi Harvey,

if you use the XStreamSerializer, you can do getXStream() on it to get the XStream instance that does the actual serialization. Just configure the aliases as shown in the history of this thread.

Cheers,

Allard

Hi,

Am I getting right, that achieving the same in case of JacksonSerializer would be like overriding JacksonSerializer#resolveClassName ?
This is the best I could come up with, but isn’t that considered too hacky? What is the best way of handling the case of event package refactoring
with JacksonSerializer if one would like to avoid using upcasters?

Thanks,
Regards

Hi Vilmos,

Overriding that function is actually ‘the best’ way to introduce a form of aliases for you message/event class names.

Other than that, an upcaster, like you’re pointing out, could also do the trick.
Sorry that I cannot provide you with a cleaner solution to this.
Nonetheless, I hope this clarifies some things.

Cheers,
Steven

Hi,

Thanks a lot!

Regards