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.
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.