Axon 4 @SagaEventHandler not fired when using mongo extension as event store

Hi Dears,
i am a new to axon and want to apply CQRS using AXON in our project and just created a poc to get full understand.
at first i tried axon-server as event store and i faced an issue for replying all events whenver i restart any microservice after searching i found @Steven_van_Beelen pointed to using persistence unit so i tried monogo-extension as event store but after that my OrderSagaManager which has this annotation [@SagaEventHandler] not get notified although the event firing happen please need ur help and clarification thanks i will share my code and configuration below:

    @Saga(sagaStore = "sagaStore")
    public class OrderSagaManager {

	private static final Logger log = LoggerFactory.getLogger(OrderSagaManager.class);

	@Autowired
	OrderSummaryEntityRepository entityRepository; 
	
	@Autowired
	private CommandGateway commandGateway;

	@StartSaga
	@SagaEventHandler(associationProperty = "orderId")
	public void handleCreate(OrderCreatedEvent orderCreatedEvent) {
		try {
			log.info("handleCreate >>>> orderId: {} , productId: {}",orderCreatedEvent.getOrderId(),
					orderCreatedEvent.getProductId());
			
			OrderSummaryEntity orderSummaryEntity = OrderSummaryEntity.builder().orderId(orderCreatedEvent.getOrderId())
					.productId(orderCreatedEvent.getProductId()).productName(orderCreatedEvent.getProductName())
					.price(orderCreatedEvent.getPrice()).orderStatus(orderCreatedEvent.getOrderStatus())
					.creationDate(orderCreatedEvent.getCreationDate()).quantity(orderCreatedEvent.getQuantity())
					.build();
			
			entityRepository.save(orderSummaryEntity);
			
			commandGateway.send(PayOrderCommand.builder().orderId(orderCreatedEvent.getOrderId())
					.productId(orderCreatedEvent.getProductId()).productName(orderCreatedEvent.getProductName())
					.orderStatus(orderCreatedEvent.getOrderStatus()).build());			
		} catch (Exception e) {
			log.error("Error {}",e);
		}

	}

	@EndSaga 
	@SagaEventHandler(associationProperty = "orderId")
	public void hnaldeShippment(OrderShippedWellEvent orderShippedWellEvent) {
		log.info("hnaldeShippment >>>> {}",orderShippedWellEvent);
		commandGateway.send(OrderUpdatedCommand.builder().orderId(orderShippedWellEvent.getOrderId())
				.productId(orderShippedWellEvent.getProductId()).productName(orderShippedWellEvent.getProductName())
				.orderStatus(orderShippedWellEvent.getOrderStatus()).build());

	}
}

and my aggregate class belwo:

    @Aggregate
    public class OrderAggregator {

	private static final Logger log = LoggerFactory.getLogger(OrderAggregator.class);

	@AggregateIdentifier
	private String orderId;
	private String productId;
	private String productName;
	private Integer price;
	private Integer quantity;
	private Integer totoalPrice;
	private Date creationDate;
	private OrderStatus orderStatus;
	
	// first command will get handled will get placed in constructor
	@CommandHandler
	public OrderAggregator(CreateOrderCommand createOrderCommand) {
		try {
			if (createOrderCommand.getProductId().isEmpty())
				throw new IllegalArgumentException("Product Id is empty");
			
			if (createOrderCommand.getProductName().isEmpty())
				throw new IllegalArgumentException("Product Name is empty");
			
			OrderCreatedEvent orderCreatedEvent = OrderCreatedEvent.builder().orderId(createOrderCommand.getOrderId())
					.productId(createOrderCommand.getProductId()).productName(createOrderCommand.getProductName())
					.price(createOrderCommand.getPrice()).quantity(createOrderCommand.getQuantity())
					.creationDate(createOrderCommand.getCreationDate()).orderStatus(OrderStatus.NEW)
					.makeOrderResult("not called").build();
			
			log.info("OrderCreatedEvent Fired : {}",orderCreatedEvent);
			AggregateLifecycle.apply(orderCreatedEvent);
		}catch (Exception e) {
			log.error("Error: {}",e);
		}
	}

	@EventSourcingHandler()
	public void on(OrderCreatedEvent orderCreatedEvent) {
		try {
			this.orderId = orderCreatedEvent.getOrderId();//this aggregate id must be set 
			this.productId = orderCreatedEvent.getProductId();
			this.productName = orderCreatedEvent.getProductName();
			this.price = orderCreatedEvent.getPrice();
			this.quantity = orderCreatedEvent.getQuantity();
			this.creationDate = orderCreatedEvent.getCreationDate();
			this.totoalPrice = this.price * this.quantity; 
			this.orderStatus = orderCreatedEvent.getOrderStatus();
			
			log.info("calling >>>>> on(OrderCreatedEvent)");
			
		}catch (Exception e) {
			log.error("Error: {}",e);
		}
	}
}

and my AXON Configuration below:

    @Configuration
    public class AxonConfig {

	@Value("${spring.data.mongodb.host:127.0.0.1}")
	private String mongoHost;

	@Value("${spring.data.mongodb.port:27017}")
	private int mongoPort;

	@Value("${spring.data.mongodb.database:testEvent}")
	private String mongoDatabase;

	@Bean
	public MongoSagaStore sagaStore() {
		return MongoSagaStore.builder().mongoTemplate(axonMongoTemplate()).build();
	}

	@Bean
	public TokenStore tokenStore() {
		return MongoTokenStore.builder().mongoTemplate(axonMongoTemplate())
				.serializer(JacksonSerializer.defaultSerializer()).build();
	}

    @Bean
    public EventStorageEngine eventStorageEngine() {
    	return MongoEventStorageEngine.builder().mongoTemplate(axonMongoTemplate()).build(); 
    }

	@Bean
	public MongoTemplate axonMongoTemplate() {
		return DefaultMongoTemplate.builder().mongoDatabase(mongo()).build();
	}

	@Bean
	public MongoClient mongo() {
		final ConnectionString connectionString = new ConnectionString(
				"mongodb://" + mongoHost + ":" + mongoPort +"/"+ mongoDatabase);
		final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
				.applyConnectionString(connectionString).build();
		return MongoClients.create(mongoClientSettings);
	}

}

and my pom.xml has the below:

        <dependency>
			<groupId>org.axonframework</groupId>
			<artifactId>axon-spring-boot-starter</artifactId>
			<version>4.4</version>
		</dependency>
		<dependency>
			<groupId>org.axonframework.extensions.mongo</groupId>
			<artifactId>axon-mongo</artifactId>
			<version>4.4</version>
		</dependency>

when i send the command it get handled but firing the event not handled although the OrderSagaManager in the same project
i have created 4 microservice linked using eureka and found in this Link
really thanks in advance for this great framework and appreciate any advice.
Note: i forget to add when starting the microservices i start also the axon-server as a way of communication between the microservices

Hi @mostafa_mohamed,

In actuality, using Axon Server should be very (very) easy compared to setting up Mongo as a storage solution and/or a Discovery Service to route commands. Axon Server simply takes the role of a dedicated Event Store and a consolidated routing solution for Commands, Events and Queries. So, wherever you’ve found an argument of mine to use a persistence unit: Axon Server is just as well a persistence unit. Furthermore, it simplifies your work as you no longer have to deal with four distinct infrastructure components (four because of (1.) event storage, (2.) event routing, (3.) command routing and (4.) query routing).

On top of this, using either solution should be interchangeable when it comes to routing events to your Saga instances. Having said that, I’d be hardpressed to “find” the issue in either Axon Server or Mongo.
Added, there is (sadly) nothing in the shared code which leads to any specifics why your set up isn’t routing messages as desired. Having said that, I feel it’s best to lean more towards the defaults for the time being.

What I mean with that is to use Axon Server (standard) and start op your Axon application with the Spring Boot Starter dependency. Then in your Aggregate/Saga classes, stick to the basics mostly (which would be the case if you are not using Mongo for example). What you do need, is a persistence unit for your Tracking Tokens (these store the progress of moving through the event stream) and Saga instances. When you add for example Spring Data JPA (starter) to your dependency set, in combination with a storage solution (h2 for PoC’s is typically fine if you ask me) you should be all set to actually store Tokens/Sagas.

Last note, I see your Saga example wires in the CommandGateway and a OrderSummaryEntityRepository. As just stated, the Saga is stored, thus serialized as is. This means in your current set up that the CommandGateway and OrderSummaryEntityRepository are serialized and stored with it, which is undesirable. It is thus recommended to make any services transient which you use in a Saga. The Reference Guide has this to say about resource injection into Sagas.

Hope this gives you some guidance @mostafa_mohamed! If you need any further info on setting up Axon projects, you can always drop questions here. Do know though that AxonIQ also provides online trainings, which you can see on the events page.

Little to add to that. You might want to take a look at how I did it in a demo project. Using Axon Server makes it very easy.

1 Like

really thanks @Steven_van_Beelen and @gklijs for your help, i appreciate this
but my problem in not connecting the microservices together as i mentioned above before putting the mongo extension everything works like a charm and the events moved between the microservices as expected but after adding the mongo db as store for events the problem appeared

That is why we are essentially suggesting to not use the Mongo Extension as your Event Store.
Added, I tried to point out I would personally not recommend the Mongo Extension for event storage any how. I thas certain missers when it comes to the requirements of an Event Store. Allard explains these (and short comings of other storage solutions when it comes to being an Event Store) nicely in this presentation.

Furthermore, if we trail back to your original point for moving away from Axon Server:

at first i tried axon-server as event store and i faced an issue for replying all events whenver i restart any microservice

That is likely not an Event Store problem, but an issue with not storing your TrackingTokens in a storage solution. Hence why you would find posts of me recommending to use a persistence solution to begin with. The TrackingTokens however have nothing to do with Axon Server, thus using it should be fine.

Hence I’d like to ask you to give it a go with Axon Server again. Check any sample projects (like AxonIQ’s Hotel demo) or the project Gerard is referring to. Both work as designed and should show how to set up a project using Framework and Server.

The spot where you could use Mongo in your set up, is as the TokenStore if you will. When picking a TokenStore solution I would select the storage solution wherein you are going to store your Query Models as well. The reasoning for this is to not span distinct databases whenever a transaction is updating a query model.

Hope this sheds some light on our angle and your options Mostafa. Feel free to keep replying if you require further helps.

2 Likes