Can a Saga be triggered by a command? (and potential workarounds)

See subject. I’d like to create multiple aggregates from a single command. They are somewhat related but I believe deserve to be independent objects, as the aggregates could have a large memory footprint. Would this be the same as having an external command handler trigger multiple “aggregate created” events?

Two other questions:

  1. An event does not have an @TargetAggregateIdentifier notation. How does the framework know which aggregate should handle an event? I see most are triggered using AggregateLifecyle.apply. Does the aggregate only handle events that it applies?

  2. How does one route a command to a nested aggregate entity?

Thanks,
Joel

Hi Joel,

If I understand your questions correctly:

To create an aggregate, you normally mark the constructor with @CommandHandler, and have it handle whatever your create command is. You could have as many aggregates as you want handle the same create command. You will have to define some strategy for ID generation for the aggregate. A normal approach is to use UUID.randomUUID().

Events do not use the @TargetAggregateIdentifier annotation. The commands use that annotation. Axon routes the command to the correct aggregate based on its ID. Once the command has been routed to the aggregate, the event gets applied using the .apply() method. In the @EventSourcingHandler method you would perform your mutations on the aggregate. The only command that does not use the @TargetAggregateIdentifier annotation is the create command (since the aggregate has not been created yet, an ID does not necessarily exist yet). Search for @TargetAggregateIdentifier on this page for more info: https://docs.axoniq.io/reference-guide/implementing-domain-logic/command-handling/aggregate

When applying events, the event should carry the ID of the aggregate it applies to (however, not the @TargetAggregateIdentifier anymore. Axon will know which field is the ID at that point, and route the event accordingly).

You can create a nested aggregate using the @AggregateMember annotation on one of the properties of your @Aggregate. Axon will then look inside that property for any @CommandHandler’s, just like it would a normal @Aggregate. See this page for details: https://docs.axoniq.io/reference-guide/implementing-domain-logic/command-handling/multi-entity-aggregates

Saga’s are created by annotating a method with the @StartSaga annotation, and the @SagaEventHandler annotation. Saga creation is normally in reaction to an event, so for example if my DomainAggregate emits an ImportThingsHappenedEvent, then in my @Saga class I would say:

@StartSaga
@SagaEventHandler(associationProperty = “domainAggregateId”)
public void on(final ImporantThingsHappenedEvent event) {
// event handling logic
}

Hope this helps,

David

What should the ID be on a command to route to a nested aggregate? Do the nested aggregates also require globally unique IDs?

Hi Joel,

first a little naming clarification: aggregate’s aren’t nested. An Aggregate consists of a number of entities. The “main” entity of the aggregate, being the entry point for all interaction, is the aggregate root.

A command would always identify the aggregate root’s identifier. That identifies the aggregate that Axon will need to load and will pass that command to the aggregate root. In some cases, the @CommandHandler is not on the root itself. In that case, other properties on the command are used to find the entity to forward it to, in case of a collection of child entities.

In the reference guide (https://docs.axoniq.io/reference-guide/implementing-domain-logic/command-handling/multi-entity-aggregates), under the first code sample, item 2 explains how the mapping is done. Basically, the routing key of the entity id (which defaults to the field name of the @EntityId annotated field, but can be overridden at different levels).

So basically the @TargetAggregateIdentifier routes to the aggregate as a whole. The routing keys defined by the entities (which are mapped to properties on the command object) then define routing within the aggregate’s entities.

Hope this helps.
Cheers,

twitter-icon_128x128.png

Thanks Allard, that is exactly what I needed to know. This also helps resolve the difference between using parent-child aggregates and nesting. Thanks for clearing this up!

Can a saga be started by publishing an event outside of an aggregate?
Examples: from a service, a rest controller (spring), etc

AFAIK the source of the event is irrelevant, but the saga must be triggered on an event. The example I’ve seen uses events such as “BeginSagaXyz”. This seems very command-like to me, which is why I was wondering if I could begin a saga using a CommandHandler rather than an event. The event kicking off the saga would serve no purpose by being persisted and replayed.

Hi Joel,

the idea has crossed our minds quite a few times. In a few cases, we’ve seen that Sagas live alongside somewhat synthetic Aggregates which translate an incoming command into an event, which is then picked up by the Saga.

We haven’t been able to give this enough priority to really make it onto the backlog, yet. We’d have to find the way in which this would fit into existing components, while still assuring the reliability of the processing (especially around concurrency and sequential processing) that is currently in place.

Cheers,

twitter-icon_128x128.png