eventScheduler doesn't work on @Saga

Hello everyone,
I’m with a simple implementation testing a scheduler inside a saga.

`

@Saga
@Slf4j
public class OpenRestaurantSaga implements Serializable {

  private static final Logger logger = LoggerFactory.getLogger(OpenRestaurantSaga.class);
  String restaurantId;

  @Autowired
  @Setter
  private transient CommandGateway commandGateway;

  @Autowired
  @Setter
  private transient EventScheduler eventScheduler;

  @StartSaga
  @SagaEventHandler(associationProperty = "restaurantId")
  public void handle(RestaurantToggledOnlineEvent event) {
    log.info("Handling {} saga: {}", event.getClass().getSimpleName(), event);
    restaurantId = event.getRestaurantId();

//    commandGateway.send(new ToggleRestaurantOfflineCommand(restaurantId));

    eventScheduler.schedule(
        Instant.now().plusSeconds(10),
        new RestaurantToggledOfflineEvent(event.getRestaurantId())
    );
  }

  @SagaEventHandler(associationProperty = "restaurantId")
  public void on(RestaurantToggledOfflineEvent event) {
    log.info("Handling {} scheduled event: {}", event.getClass().getSimpleName(), event);
  }
}

`

My RestaurantToggledOfflineEvent is never called.
Here is the source: https://github.com/lgertel/ifood-backend-connection-test/blob/master/monitor/src/main/java/br/com/grts/commandmodel/OpenRestaurantSaga.java

I’m missing something?
Thanks

Hi Lucas,

I’ve just checked out your sources, but on first glance I do not see a culprit.
Would you mind trying out whether you’re able the handle the RestaurantToggledOfflineEvent in a regular event handler?
If that does work, I think we can eliminate a couple of potential situations.

Cheers,
Steven

Hello Steven! Thanks for your answer.

You mean by removing it from the Saga?
In my RestaurantAggregate I have a EventHandler for this event and it’s working, i’m just setting a value to true.

If I remove the @Saga annotation from this class, it’s not called (maybe because it’s not an aggregate?).

If I call the command directly on the @Saga, the method works.
Not sure what do do!

Thanks!

Hi Lucas,

What I meant was having a separate event handling class with an @EventHandler annotated function for the exact event you’re scheduling.
So just an additional class with an event handler for the ‘RestaurantToggledOfflineEvent’, where you verify that the event you’re handling is the same event that you’ve scheduled in your Saga.
That way we can assert if it’s the scheduler which fails, or the saga event handler which is failing. :slight_smile:

Your last comment makes me doubt a little what the issue is.
Let me explain a couple of concepts, maybe that’ll help:

  • An Aggregate can only handle events which it has published itself. You state that your aggregate can handle the RestaurantToggledOfflineEvent - this means that the given aggregate publishes the event itself. This holds if I look at your repo, because the RestaurantAggregate has a command handler for ‘ToggleRestaurantOfflineCommand’, which publishes the RestaurantToggledOfflineEvent. Note however that the aggregate will never handle outside events - event handlers in aggregates are event sourcing handlers, so to source itself from history

  • The @Saga annotation is a stereotype annotation from the axon-spring module. This annotation is searched for by the framework upon start up, given that your using Spring (Boot) to run your application. If it’s found, the framework knows to create all the necessary building blocks to deal with sagas (e.g. a SagaStore, SagaManager and the Saga instances itself). It is thus a short hand to receive the default configuration for a Saga, which, importantly, will be registered to the EventBus as ‘interested in events’. Hence, without the Saga annotation, the EventBus just doesn’t know that that exact component is interested in events at all.

  • With the following comment:

    If I call the command directly on the @Saga, the method works.

    You mean the commented out line number 41 in your OpenRestaurantSaga, right? When publishing this command, you will effectively move through the Aggregate to publish that event, which in turn will be delivered to the Saga. So I think this is not something to worry about.
    Lastly, I see that you’re using Axon version 3.3.4.

As your project seems to be a PoC, I’d recommend moving to Axon 4 to try that out too.

Cheers,
Steven

Thanks for this fantastic answer. I’m starting with these components so there is always some questions regarding the right architecture to go with.
I have this scenario:

  • The opening hour for the Restaurants are from 10:00 am to 11:00 pm.
  • A Restaurant may be available/unavailable.
  • A Restaurant may scheduled the status unavailable, due to the following reasons:
    • lack of delivery staff
    • connection issues (bad internet)
    • overloaded due to offline orders
    • holidays
  • A Restaurant may also be online/offline:
  • In order to a Restaurant be considered online, it must be inside the opening hour AND have interacted with our platform (aka sent a keep-alive signal) in the last two minutes AND its status should be available. Otherwise, it should be considered offline.
    I was thinking that the keep alive signal could be called from the client to start a saga and then change the value of the aggregate when times out, with the scheduler. Maybe my way of trying to implement this is wrong.

I’ll try to figure it out how to do it! It would be great if you can share with me your thoughts!
Thanks and regards,
Lucas Gertel

Hi Lucas,

when going into this level of designing, there are always a lot of “it depends” factors. We really cannot oversee all these factors without knowing your case in detail, which is unfeasible over a mailinglist. Let’s try to give you some pointers/options, to allow you to make the best decision possible.

You could use a scheduler from the aggregate itself. That aggregate could then cancel the previous schedule and create a new one for the next 2 minutes. When the call triggers, the aggregate can do a last check on the last received call. This does put a lot of effort on aggregate that have lots of activity. Assuming disconnects are more rare than “normal cases”, this might be too heavyweight.

Another solution could be the Saga. Again, Sagas are quite a heavy mechanism to handle cases that are relatively rare. I have seen many places where Sagas are being used, where a much more simple view model could also suffice. The big questions is, do you want to raise an event when a restaurant goes offline or is it just something you want to see in a view model?

The third approach would be to create a view model, that keeps track of the “last interaction” from a restaurant. You could have a process check that view model for restaurants that had no activity for 2 minutes. In this case, activity doesn’t necessarily need to be a command or event being raised. You could also update this model based on incoming queries. Note that, in that case, you’ll not be able to reconstruct the view based on events. But mind you, event sourcing is not always the solution to everything.

Hope this clarifies the options a bit.
Cheers,