Need Advice on our first Saga use case

Having read a bit around Sagas I believe they are ideal for solving our problem.

Multiple events are fired SyncProcessed. This event contains a property group_id against which the Saga would be associated.

I’d like for the Saga to accumulate the state from multiple SyncProcessed events. When it reaches a certain number it will produce a new event ideally or command SyncGroupProcessed

I’m not sure how to define the lifecycle for the saga for the following use case. I’m also not sure if it’s ok to start and end sagas with events. Does every EventHandler in a saga have to produce a command or event or can it output nothing?

Hi Michael,

I prefer to use Saga for handling some sort of invariants between different Aggregates or between different Aggregate instances of the same type.
For example:

This Saga is simple (no state and logic in there) and all the logic (validations) are on the Aggregates which are the core building blocks of DDD, and you should encapsulate your logic there.

Your case is feasible, and it will work. You can react on events and send commands/events/void. I would consider changing the design a bit, and move the logic to the aggregates if possible (it looks like your logic is leaking to Saga at the moment).

Best,
Ivan

Ivan,
Thank you for you for your suggestions. Due to SpringOne and a few other issues that have come I’ve not had time until now to circle back to getting Sagas functional.
Having the Saga written it looks like it’s failing to be instantiated. I’m not sure what I may be missing in my configuration at the moment that is resulting in the following exception:

org.axonframework.modelling.saga.repository.SagaCreationException: An error occurred while attempting to create a new managed instance at org.axonframework.modelling.saga.repository.AnnotatedSagaRepository.doCreateInstance(AnnotatedSagaRepository.java:149) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.repository.AnnotatedSagaRepository.doCreateInstance(AnnotatedSagaRepository.java:56) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.repository.LockingSagaRepository.createInstance(LockingSagaRepository.java:76) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.AbstractSagaManager.startNewSaga(AbstractSagaManager.java:105) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.AbstractSagaManager.handle(AbstractSagaManager.java:93) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.MultiEventHandlerInvoker.handle(MultiEventHandlerInvoker.java:79) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.AbstractEventProcessor.lambda$null$1(AbstractEventProcessor.java:157) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:57) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.messaging.interceptors.CorrelationDataInterceptor.handle(CorrelationDataInterceptor.java:65) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.TrackingEventProcessor.lambda$new$1(TrackingEventProcessor.java:160) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.AbstractEventProcessor.lambda$processInUnitOfWork$2(AbstractEventProcessor.java:165) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.messaging.unitofwork.BatchingUnitOfWork.executeWithResult(BatchingUnitOfWork.java:86) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.AbstractEventProcessor.processInUnitOfWork(AbstractEventProcessor.java:151) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.TrackingEventProcessor.processBatch(TrackingEventProcessor.java:395) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.TrackingEventProcessor.processingLoop(TrackingEventProcessor.java:269) ~[axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.TrackingEventProcessor$TrackingSegmentWorker.run(TrackingEventProcessor.java:990) [axon-messaging-4.1.2.jar:4.1.2] at org.axonframework.eventhandling.TrackingEventProcessor$WorkerLauncher.run(TrackingEventProcessor.java:1102) [axon-messaging-4.1.2.jar:4.1.2] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_162] Caused by: org.axonframework.modelling.saga.SagaInstantiationException: Exception while trying to instantiate a new Saga at org.axonframework.modelling.saga.AbstractSagaManager$Builder.newInstance(AbstractSagaManager.java:243) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.AbstractSagaManager$Builder.lambda$new$0(AbstractSagaManager.java:236) ~[axon-modelling-4.1.2.jar:4.1.2] at org.axonframework.modelling.saga.repository.AnnotatedSagaRepository.doCreateInstance(AnnotatedSagaRepository.java:131) ~[axon-modelling-4.1.2.jar:4.1.2] ... 19 common frames omitted Caused by: java.lang.InstantiationException: com.aramark.ess.command.domain.saga.SyncGroupProcessing at java.lang.Class.newInstance(Class.java:427) ~[na:1.8.0_162] at org.axonframework.modelling.saga.AbstractSagaManager$Builder.newInstance(AbstractSagaManager.java:241) ~[axon-modelling-4.1.2.jar:4.1.2] ... 21 common frames omitted Caused by: java.lang.NoSuchMethodException: com.aramark.ess.command.domain.saga.SyncGroupProcessing.<init>() at java.lang.Class.getConstructor0(Class.java:3082) ~[na:1.8.0_162] at java.lang.Class.newInstance(Class.java:412) ~[na:1.8.0_162] ... 22 common frames omitted

If we decided to use an Aggregate. Is it a good idea to have an Aggregate end() it’s own lifecycle and be removed?
Within our use case we are looking at very short lived processes who’s history is not interesting or worth persisting to the domain.

It looks like you Saga lacks a default constructor. Depending on the jvm (XStream can use some jvm-specific workarounds), you need to have a default (no-arg) constructor on your Saga.

Cheers,

Allard

twitter-icon_128x128.png

Thank you Allard!
Moving the injections from the constructor to @AutoWired within each method solved the problem above.

Unfortunately a new issue is event routing (not sure if it’s worth creating a new thread for this).

There are 4 methods inside the Saga.

`

@StartSaga
@SagaEventHandler(associationProperty = "syncGroupId")
fun on(event: SyncPointTriggered,
       @Autowired syncPointService: SyncPointService,
       @Autowired syncModelService: SyncModelService,
       @Autowired syncGroupService: SyncGroupService)

`

`

@SagaEventHandler(associationProperty = "syncGroupId")
fun on(event: SyncProcessed,
       @Autowired @Qualifier("eventBus") eventBus: EventBus)

`

`

@SagaEventHandler(associationProperty = "syncGroupId")
fun on(event: SyncProcessFailed,
       @Autowired @Qualifier("eventBus") eventBus: EventBus,
       @Autowired syncPointService: SyncPointService)

`

`

@EndSaga
@SagaEventHandler(associationProperty = "syncGroupId")
fun on(event: SyncGroupProcessed,
       @Autowired syncPointService: SyncPointService,
       @Autowired @Qualifier("eventBus") eventBus: EventBus)

`

In all above events there is a property of syncGroupId with the same spelling as the associationProperty.

The problem is that only the first event is processed by the Saga SyncPointTriggered. Meanwhile SyncProcessed is never processed by the event handler.
SyncProcessed is processed in a different non-saga eventhandler.

Both the Saga and the external event handler are using the same TEP.

Do you or anyone else know why this might be happening?

A bit tired but I hope to dive into the source a bit later to try and understand why this is happening.

Hi Michael,

The Saga Event Handler which is annotated with @StartSaga (thus the SyncPointTriggered event handler) ensures that the association property key syncGroupId with its value from the event is stored in the Association Value Entry table.

If storing the association values is for some reason unsuccessful, I’d dupe that the reason the other handlers aren’t being called.

Additionally, just for tries, I’d test whether you Saga event handlers work if you remove all the autowired parameters from them.
If the Parameter Resolvers are unable to resolve any/some of them, the method might not be called at all.
Thus first verifying if the handler is called with just the events might proof beneficial as another assurance test.

If it’s the latter problem, then the SpringBeanParameterResolverFactory is incapable of finding a bean of type EventBus with the qualifier eventBus, thus causing the method to never be called.

Hope this gets you in the right direction Michael!

Cheers,
Steven

Thank you both. Resolved the issue.
Simple mistake on our part. The service firing SyncProcessed was firing it with an old event signature. This event signature was being processed by one event handler that did not have a dependency on syncGroupId but due to the missing parameter it of course failed to associate.