Stopping and starting a SagaManager throws error

Hi all, I’m trying to d what seems to be a easy thing. I have a AsyncAnnotatedSagaManager instantiated somewhere by Spring from a SagaManagerFactoryBean, that is being used by the entire application. However, I have a event replayer specific to replay events to this saga and for that I have to signal the saga that she is being replayed. So I have this piece of code to do that:

final GenericSagaFactory factory = new GenericSagaFactory() {
    @Override
    public AuditProcessWorkflowSaga createSaga(Class sagaType) {
        AuditProcessWorkflowSaga saga = super.createSaga(ProcessWorkflowSaga.class);
        saga.beforeReplay();
        return saga;
    }
};
factory.setResourceInjector(resourceInjector);

Hi António,

I’d like to answer something somewhat different than your title suggests.

Apart from stopping/starting a Axon 2.6 SagaManager, I’d like to state that we typically suggest against replaying any events on a saga.

A saga is a component which introduces side effects.
View it like a event handling component which sends out emails. You’d probably don’t want those emails to be sent a second time.

Identically, sending commands to aggregates is typically also a thing you wouldn’t want to happen again.

The request does occur more often though, and typically stems from the desire for that specific action/command to be executed again as you’ve adjusted the logic in the command handler or the state you’re collecting to issue the command is changed.

Hi Steven, thanks for you quick reply, but with all respect :slight_smile: I do know what Sagas are and how to use them. That’s why in my post I included the code of my Saga that determines when it is being replayed or is working live,


OK, I found out the problem and it’s impossible to do it… To put a long story short, the sagaFactory only creates a empty Saga but then the Saga is instantiated again when read from the repository.

In the AnnotatedSagaManager I’m doing my stuff in the preProcessSagaHow method tha is invoked both before and after the Saga is read from the repository, so it’s works fine.

However in the AsyncAnnotatedSagaManager I’m doing it in the only place I saw available to extend, in the createSaga itself, but this one is not called - naturally - after the Saga is read from the repository. Moreover this read from repository is done in AsyncSagaEventProcessor::onEvent and this class is unfortunately final, so no way to extend this onEvent.

So basically without any extension point in prepareSagas or in invokeSagas the only chance I got is to extend the load method from the JdbcSagaRepository itself but that is so far removed from my original code that I don’t really know if it’s worth the trouble…

Well, if somebody can suggest a way to access a saga before any of it’s listeners being invoked please let me know.

Cheers.

Hi António,

I hoped my reply did not seem to suggest I thought you didn’t know how Sagas work; that was definitely not my intention, so sorry if it did!
What I tried to point out is that the desire to replay a saga typically suggests a problem is being tackled from the wrong angle.
Hence why I provided the example with the calculator and the compensating action, as that’s an approach which does not require you to replay the events of a saga and still gives you the ‘side-effect’/command-being-published you’re very looking for if you’re doing a replay.

Nonetheless, I am guessing from your replay that you do have another valid scenario were a replay of a saga is absolutely necessary
I might have a suggestion to solve your problem; just thinking out loud here by the way, so please correct me if this wont work in this case.
Would it be possible to have some form of service which you can query if a certain saga/cluster is in replay?
That service can then be wired into your Saga, either by injecting it as a field or as a parameter (through the ParameterResolver set up in Axon).
In doing so, you’d effectively change your:

if(!isReplaying) { … }

call to:

if(replayQueryService.isNotReplaying()) { … }

Would that work out for you? Or could a compensating action do the trick?

Cheers,
Steven

Hi Steven, no worries about that, I did added a smiley to my comment… :slight_smile:

I noticed I didn’t explain why I want to replay events for Sagas, we actually do that for different reasons but in this case is just to recreate the internal state of the Saga. This happens when I change the Saga class itself and I have to update the serialized sagas. In certain cases like, say, having a property removed

//    private int subtasks = 0;

if I still have it the serialized sagas I’ll get a error when loading. This particular scenario is easy to fix since a simple update with a XSLT identity transform will do. However, if I change something like

private Map subtasks = new HashMap<UUID, Boolean>();

into

private Map subtasks = new ConcurrentHashMap<UUID, Boolean>();

(actually my case is even more complicated since my map extends those)

This case is particularly tricky specially if the serialized map is already populated. So in this cases the best option (AFAIK) is to delete the Saga from the DB and recreate it by replaying events. Can you think of another way?

Your suggestion of having a service seems a overkill to this scenario, and would add too much unnecessary complexity, AFAICS. And also, the way we designed our “replayer” was with the intention of having a SagaManager/EventBus instantiated internally, and not the ones the application normally use, to allow us to pick and choose which Sagas/Eeven listeners we do want to replay to.

The other suggestion of having a ParameterResolver may be useful but to be frank that would need more investigation and I spend too much time looking at this, and I can kust use the current replayer with a AnnotatedSagaManager that until now only failed in a fringe situation of a Saga receiving events from two diferent sources.

Cheers.