spring-bootmicroservicesaxonsaga

Why does OrderInfoDto become null in the event handler of CanceledPreemptProductEvent in Axon Saga?


I am implementing a microservices architecture (MSA) using Axon and Saga in Spring Boot. While setting data in OrderInfoDto when the StartedOrderSagaEvent event handler is executed, I encountered an issue: the OrderInfoDto becomes null when the CanceledPreemptProductEvent event handler is executed.

Here is the relevant code:

OrderSaga.java

@Saga
@ProcessingGroup("orderSagaProcessor")
@Slf4j
public class OrderSaga {
    @Transient
    private transient CommandGateway commandGateway;

    private OrderInfoDto orderInfoDto;

    @Autowired
    public void setCommandGateway(CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    @StartSaga
    @SagaEventHandler(associationProperty = "orderUuid")
    public void handle(StartedOrderSagaEvent event) {
        log.info("[OrderSaga] Order Saga started for orderUuid: {}", event.getOrderUuid());
        this.orderInfoDto = new OrderInfoDto(event.getOrderUuid(), event.getName(), event.getPhoneNumber(), 0, OrderState.PROCESS, null, null);
    }

    @SagaEventHandler(associationProperty = "orderUuid")
    public void handle(CanceledPreemptProductEvent event) {
        log.error("[OrderSaga] Failed preempt product for orderUuid: {}", event.getOrderUuid());
        
        // NullPointerException occurs here because orderInfoDto is null
        this.orderInfoDto.getProductItems().forEach(productItem -> {
            commandGateway.send(new CancelPreemptionCommand(productItem.getProductUuid(), productItem.getCount()));
        });
    }
}

OrderInfoDto.java

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class OrderInfoDto implements Serializable {
    private String orderUuid;
    private String name;
    private String phoneNumber;
    private int total;
    private OrderState state;
    private List<ProductItem> productItems;
    private String paymentUuid;

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ProductItem implements Serializable {
        private String productUuid;
        private int price;
        private int count;
    }
}

application.yml

axon:
  axonserver:
    servers: localhost:8124
  eventhandling:
    processors:
      orderSagaProcessor:
        mode: tracking
        source: eventStore
      orderAggregateProcessor: # Processor 이름
        mode: tracking
        source: eventStore
  saga:
    store: jpa
  serializer:
    general: jackson
    events: jackson
    messages: jackson

Since I configured the Saga to use JPA (axon.saga.store: jpa) and expected the OrderInfoDto to persist in the serialized_saga column in the saga_entry table, I assumed it would be reloaded when the saga continued. However, it seems that the data is not persisted.

Questions:

Is it incorrect to store data as a variable (OrderInfoDto) inside the Saga?

If this approach is wrong, what is the best practice for persisting stateful data within an Axon Saga?

Could the issue be related to the serialization settings (axon.serializer.general: jackson)?

Any insights would be greatly appreciated! Thank you.


Solution

  • Using Jackson as a Serializer is currently the superior option, but it does require that your saga can be serialized and deserialized. Your OrderInfoDto meets all of Jackson's requirements, but your saga does not since orderInfoDto is a private field without any getters and setters. As such, the ObjectMapper does not serialize this. There are verious ways to correct this. You could add the required methods to the saga, or configure your ObjectMapper to be able to hande private fields through the objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); method.