Hello Lucas,
Thank you for answering. As requested, hereby the entire saga code.
@Saga(sagaStore = "defaultSagaStore")
public class BalanceExceededSagaHandler {
private static final Logger LOGGER = LogManager.getLogger(BalanceExceededSagaHandler.class);
private static final String NOTIFICATION_SEND_DEADLINE = "b.t.c.s.notification-send-deadline";
private static final int MAX_RETRIES = 5;
@Autowired
private transient TemplateProcessService templateProcessService;
@Autowired
private transient TemplateManagementService templateManagementService;
@Autowired
private transient MailerService mailer;
@Autowired
private transient WebClient customerQryService;
@Autowired
private transient DeadlineManager deadlineManager;
private BalanceExceededDomainEvent balanceExceededEvent;
private int retryCounter;
private String accountCreateDeadlineId;
//This method will be called as expected with all injected resources set properly.
@StartSaga
@SagaEventHandler(associationProperty = "eventId")
private void onDomainEvent(BalanceExceededDomainEvent balanceExceededEvent) {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Balance exceeded for customer id = {} received. Sending notification...",
balanceExceededEvent.getCustomerId().getId());
}
this.balanceExceededEvent = balanceExceededEvent;
this.retryCounter = 1;
this.accountCreateDeadlineId = deadlineManager.schedule(Duration.ofSeconds(15), NOTIFICATION_SEND_DEADLINE);
}
//This method won't be called, an exception indication unsatisfied resources is thrown
//before the method body is entered.
@DeadlineHandler(deadlineName = NOTIFICATION_SEND_DEADLINE)
private void on() {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Failed to send notification for customer '{}'. Retrying {}/{}",
balanceExceededEvent.getCustomerId().getId(), retryCounter, MAX_RETRIES);
}
if(retryCounter <= MAX_RETRIES){
this.sendNotification(balanceExceededEvent);
}else {
deadlineManager.cancelSchedule(NOTIFICATION_SEND_DEADLINE, accountCreateDeadlineId);
SagaLifecycle.end();
LOGGER.fatal("Tried to send notification for customer '{}' {} times but failed. Stop retrying!",
balanceExceededEvent.getCustomerId().getId(), retryCounter);
}
}
private void sendNotification(BalanceExceededDomainEvent balanceExceededEvent) {
try{
//Retrieve the customer details for the specified customer id.
Customer customer = retrieveCustomer(balanceExceededEvent.getCustomerId().getId());
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Customer details for id = {} successfully retrieved.",
balanceExceededEvent.getCustomerId().getId());
}
//Retrieve the template to use as notification e-mail message.
Template template = templateManagementService.findByEventTypeAndEventVersion(
balanceExceededEvent.getClass().getName(), balanceExceededEvent.getVersion());
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Template for handling events of type {} successfully retrieved.",
balanceExceededEvent.getClass().getName());
}
//Prepare all required template parameters.
Map<String, Object> parameters = new HashMap<>();
//... left out for brevity ...
//Process the template for the given template parameters.
byte[] result = templateProcessService.process(template, parameters);
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Template processed for {}, version={}. Sending email...",
balanceExceededEvent.getClass().getName(), balanceExceededEvent.getVersion());
}
//Prepare the e-mail message based on the processed template.
//... left out for brevity ...
//Send the e-mail message.
mailer.send(destination, subject, body);
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Email message sent for {}, version={}.",
balanceExceededEvent.getClass().getName(), balanceExceededEvent.getVersion());
}
//Notification successfully sent, stop retrying.
deadlineManager.cancelSchedule(NOTIFICATION_SEND_DEADLINE, accountCreateDeadlineId);
SagaLifecycle.end();
}catch(TemplateFailedException ex){
//Template processing failed, try again...
LOGGER.warn("Processing the template for the balance exceeded notification "
+ "failed for customer '{}', cause = {}",
balanceExceededEvent.getCustomerId().getId(), ex.getLocalizedMessage());
retryCounter++;
}catch(MailException ex) {
//E-mail sending failed, try again...
LOGGER.warn("Sending the e-mail message for the balance exceeded notification "
+ "failed for customer '{}', cause = {}",
balanceExceededEvent.getCustomerId().getId(), ex.getLocalizedMessage());
retryCounter++;
}catch(NoCustomerExistsException ex) {
LOGGER.error("A request to send a balance exceeded notification was received "
+ "for customer '{}' but the customer does not exist. Stopped trying!",
balanceExceededEvent.getCustomerId().getId(), ex.getLocalizedMessage());
deadlineManager.cancelSchedule(NOTIFICATION_SEND_DEADLINE, accountCreateDeadlineId);
SagaLifecycle.end();
}catch(NoTemplateExistsException ex) {
LOGGER.error("A request to send a balance exceeded notification was received "
+ "for customer '{}' but the template does not exist. Stopped trying!",
balanceExceededEvent.getCustomerId().getId(), ex.getLocalizedMessage());
deadlineManager.cancelSchedule(NOTIFICATION_SEND_DEADLINE, accountCreateDeadlineId);
SagaLifecycle.end();
}
}
private Customer retrieveCustomer(String customerId) throws NoCustomerExistsException {
Mono<Customer> result = customerQryService
.method(HttpMethod.GET)
.uri(uriBuilder -> uriBuilder.path("/customer/{customerId}").build(customerId))
.retrieve()
.onStatus(
httpStatus -> httpStatus == HttpStatus.NOT_FOUND,
throwable -> Mono.empty()
)
.bodyToMono(Customer.class)
.retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(2)));
try {
Customer customer = result.block();
return customer;
}catch(Exception ex) {
throw new NoCustomerExistsException("id = " + customerId + ", cause = " + ex.getLocalizedMessage());
}
}
}
The saga store is defined as follows in a Spring @Configuration annotated class.
@Bean
public SagaStore<?> defaultSagaStore(EntityManagerProvider provider) {
JpaSagaStore store = JpaSagaStore.builder()
.entityManagerProvider(provider)
.build();
return store;
}
Kind regards,
Kurt