Saga synchronous event handling

Hello there. The question is about how to handle Axon Events in a saga.

My case is about deleting nested entities with foreign keys.

Imagine I have a menu with menu sections. Each menu section has its menu item and each menu section might have nested menu section with depth level up to 10, and all of the nested menu section might have own menu items.

So, to delete the whole menu I have to:

  1. Delete all menu items
  2. Delete all menu sections
  3. Delete menu

And besides all of that it has to be done with sending commands to delete items, sections to have the events about it.
So, before I delete menu section I have to wait until ALL menu items have been deleted. I do it with CompletableFuture.allOf()

@StartSaga
@SagaEventHandler(associationProperty = "transactionalId")
public void on(MenuTransactionalDeletedEvent event) {
Iterable<MenuItem> menuItemsList = menuItemRepository.findAll() // here are the items
Iterable<MenuItem> menuSectionList = menuSectionRepository.findAll() // here are the sections

List<CompletableFuture<Object>> deleteMenuItemsFutures = new ArrayList<>();
menuItemList.forEach(menuItem -> {
            CompletableFuture<Object> send = commandGateway.send(
                    new DeleteMenuItemTransactionalCommand(
                            menuItem.getId(),
                            event.getTransactionalId(),
                    ));
            deleteMenuItemsFutures.add(send);
        });

CompletableFuture.allOf(deleteMenuItemsFutures.toArray(new CompletableFuture[0]))
                .thenApply(deleteMenuSection -> {
                    List<CompletableFuture<Object>> deleteMenuSectionsFutures = new ArrayList<>();
                    sections.forEach(menuSection -> {
                        CompletableFuture<Object> send = commandGateway.send(
                                new DeleteMenuSectionCommand(
                                        menuSection.getId(),
                                        event.getTransactionalId()
                                ));
                        deleteMenuSectionsFutures.add(send);
                    });

                    return CompletableFuture.allOf(deleteMenuSectionsFutures.toArray(new CompletableFuture[0]))
                            .thenApply(deleteMenu -> commandGateway.send(new DeleteMenuCommand(
                                    event.getId(),
                                    event.getTransactionalId(),
                            )));
                });

@SagaEventHandler(associationProperty = "transactionalId")
    public void on(MenuItemDeletedTransactionalEvent event) throws InterruptedException {
        menuItemRepository.deleteById(event.getId());
    }

@SagaEventHandler(associationProperty = "transactionalId")
public void on(MenuSectionDeletedByRangeEvent event) {
    menuSectionRepository.deleteRange(event.getLeftMargin(), event.getRightMargin());
}

@EndSaga
@SagaEventHandler(associationProperty = "transactionalId")
public void on(MenuDeletedEvent event) {
    menuRepository.deleteById(event.getId());
}

Is it the best way to do it ? Or I might somehow configure Axon Saga to handle all events sequentially ?
Wait before some commands are done and only then go further ?

The main problem was when all events have been dispatched - sometimes I handled events to delete menu section before all of the items have been deleted - and there was an error, because that menu section still had menu items in it

Hi Vladimir,
reading your message I was asking myself if this Saga is the right approach to use in your use case.
So let me ask you one main question: are all the events related to the deletion of menu items vital and important for your domain? Were they discovered with an event storming session? Do you need to audit or replay over them? Or were they be introduced because of trying to solve everything with CQRS/ES approach?

If the latter applies to you, I suggest changing your perspective and go the easy way: handle the command and move the complexity of foreign key constraint to the part of the software that is better fitted to face this challenge, meaning the DB (Repository).

Hope that I was able to guide you a bit.

Hello, Corrado! Thank you for the answer.

Actually I need events of updating&deleting everything in my system. This is needed for backup.
And deleting all entities needs to be done with CQRS/ES approach.

As I wrote before when I dispatch events of deleting sections, I need to wait while all items in sections have been deleted. If axon provided some setting for sequentially handling events in order they were dispatched I would handle my challenge.

I see. How about using a sendAndWait?
https://docs.axoniq.io/reference-guide/axon-framework/axon-framework-commands/command-dispatchers#the-command-gateway

I’m still a bit skeptical of a saga approach. I tend to use Sagas when I need to cooperate with external entities or systems. A “classical” event-based approach will fit better.