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