Selectively starting sagas

In my application, there are a bunch of saga classes that manage some highly variable partner-specific logic for a common set of business events. Right now I implement it with code something like this:

     @SagaEventHandler(associationProperty = "transactionId")
     @StartSaga
     public void handle(PaymentSucceededEvent event) {
         if ("partner 1".equals(event.getPartnerName())) {
             kickOffBusinessProcess(event);
         } else {
             end();
         }
     }

This works well, but is kind of ugly: Axon ends up having to instantiate each of the many saga classes (and autowire them, etc.) to find the one that'll end up actually handling the event. With a large enough number of classes, that starts to add up to measurable overhead, and it only gets worse as the number of partners grows.

The obvious alternative, which seems to be the most natural fit for the way Axon does event routing, is to do something like

     @SagaEventHandler(associationProperty = "transactionId")
     @StartSaga
     public void handle(PaymentFromPartner1SucceededEvent event) {
         kickOffBusinessProcess(event);
     }

but then we end up with a proliferation of identical event classes which increases code size and maintenance cost. This approach also means that the code that's publishing the events needs to know which specific event class to instantiate even though the distinction is otherwise irrelevant to that part of the code (though of course that knowledge could be moved to a factory class). And at a conceptual level, the distinction among those classes has no useful business meaning so it seems like bad domain modeling.

Another option would be to have a single saga class which is just a thin wrapper that delegates all its event handling to partner-specific helper objects:

     private PartnerHelper helper;

     @SagaEventHandler(associationProperty = "transactionId")
     @StartSaga
     public void handle(PaymentSucceededEvent event) {
         helper = partnerHelperFactory.forPartner(event.getPartnerName());
         injectDependencies(helper);
         helper.kickOffBusinessProcess(event);
     }

This avoids the problems with the other two approaches but it makes Axon API calls a bit less convenient since the helper object doesn't subclass AbstractAnnotatedSaga and thus doesn't have helper methods like associateWith() or end(); you end up with mutual delegation where the helper class calls back into the saga class to do those things. Which works fine, but circular dependencies like that are kind of a bad code smell. That said, this is the approach I'm currently leaning toward since the downsides seem the least bad.

How have other people approached this kind of setup?

-Steve

Hi.

I am just wondering, if it would be feasible to use a signleton EventListener Bean for the “PaymentSucceededEvent”. Have this one figure out for which partner you would like to kick off the process. Then just publish a “PaymentPartnerXSucceded” event to the event bus.

Then you can have the specific business logic for each partner X in its own Saga which is triggered by “PaymentPartnerXSucceded”.

The eventlistener is very lightweight, and you clean up your code by having each process in its own saga.

And only the specific Saga is instantiated instead all listening to the more generic event.

I have not tried it myself. Just a thought.

Dominic

Hi,

in case it doesn’t make sense to use a singleton bean instead, there isn’t any other means of filtering messages before they get to the saga instance.

However, in Axon 3, you can define a HandlerEnhancerDefinition. With that, you can define an annotation that will cause a habdler to be wrapped, allowing you to set some extra contraints of the message. So you could have a canHandle() method return false based on certain criteria, even if the actual handler could handle the message.

Hope this helps.
Cheers,

Allard