Selectively routing events to different saga classes?

My application acts as an intermediary between clients and several different back-end payment processing services that have very different APIs and request semantics. The idea is that my application’s clients get a unified API that abstracts away the differences between the services.

A “payment” aggregate root gets created when a new payment request arrives. It of course fires a “payment created” event, which it uses to initialize its own state. That event should also be received by a saga that’ll manage the interactions with the external services.

Different services will almost certainly need different saga classes because their semantics are so wildly different and some of them have time components others don’t. For example, some of them only take daily batches of requests and report results via a callback request some time later, while others only take individual requests and serve them in real time.

What I’m struggling with is a clean way to get the right payments to the right sagas. The obvious approach is to define a separate set of event classes for each saga class, and have the aggregate root send out events of the appropriate types. In other words, rather than directly using a PaymentCreatedEvent, define subclasses like PaymentCreatedForServiceA and PaymentCreatedForServiceB. That would be viable for a small number of services but if I follow the best practice of using event subclasses to represent the intent of an operation, it’s going to result in an explosion of event classes that’ll be tough to manage – I’ll have to have a separate event class for every (intent, service) pair. Worse, it leaks an implementation detail into the domain classes which isn’t good.

Another approach would be to abandon the idea of different saga classes and route everything to a saga that can handle a superset of all the behaviors of all the services. It could delegate most of its work to service-specific helper classes but it would still be a mess structurally with a bunch of workflow logic that would sit unused for any given request.

This would be easier if I could do either of two things. First, the Saga classes themselves could accept or reject the events, either via explicit “do you want this event?” methods or in their @StartSaga handlers:

@StartSaga
@SagaEventHandler(associationProperty = “paymentId”)

public void handlePaymentCreatedEvent(PaymentCreatedEvent event) {

if (event.getService() != whatever) {
… do something to tell Axon the event doesn’t apply here …
} else {
… handle the event …
}
}

I don’t see how to implement this since it looks like @StartSaga causes a bunch of work to be done before my code is ever called. I could call end() to reject the event, but that’d mean Axon was constantly creating and destroying sagas in the saga store. Alternately, filter logic could be specified in metadata:

@StartSaga
@SagaEventHandler(associationProperty = “paymentId”)
@EventFilter(property = “serviceName”, equalTo = “ServiceA”)
public void handlePaymentCreatedEvent(PaymentCreatedEvent event) {

}

It seems like if I extend AnnotatedSagaManager and override the extractAssociationValues() method, I could implement the second option without modifying the core Axon code. But that approach seems likely to be brittle as I upgrade to new Axon versions over time.

What I’d really like to know is, is there already a way to do what I’m trying to do? Or am I thinking about the problem all wrong?

Thanks for reading this longwinded question!

-Steve

Answering my own question here: After tracing through the code, it seems like calling end() in the @StartSaga handler is actually the way to go. Axon does the right thing and skips saving anything to the saga repository if the saga is marked inactive by its initial event handler.

-Steve

Hi Steve,

I didn’t have time to respond fast enough, but the “end()” approach was one that I also though of.

Another solution could be to use different SagaManager instances for the different types of Sagas and filter incoming events based on their properties.

Cheers,

Allard