Context
I've a scenario - I need to expose a rest endpoint and provide a post method , which will be used by a fronend to receive form values (name, email, address). With these details I need ti call a third party api (that could take upto 10 seconds to respond). Additionally , I need to store the processed application and the response in DB somewhere to keep a track for monitoring purposes.
Problem
I plan to use Spring @Async
functionality for storing details in Queue (the a DB) so that I dont keep the user waiting for response while I do this storing. The @Async
seems to be creating new Thread , I can see from the logs but Controller doesn’t send the response back to client (Which is contrary to actual @Async
knowledge I have which is not alot) ; So, until the async completes user has to wait for the response.
Is there anything wrong here or missing ?
TIA for your help.
here are some snippets of my classes-
Main
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
@SpringBootApplication
public class CardsApplication {
public static void main(String[] args) {
SpringApplication.run(CardsApplication.class, args);
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Controller
@Validated
@RequiredArgsConstructor
public class CardEligibilityController {
private final CardEligibilityService cardEligibilityService;
@PostMapping("/check-eligibility")
@CrossOrigin(origins = "*")
public EligibilityResponse checkEligibility(@RequestBody @Valid Applicant applicant){
return cardEligibilityService.eligibilityService(applicant);
}
}
Service 1
public interface CardEligibilityService {
EligibilityResponse eligibilityService(Applicant applicant);
}
@Slf4j
@Service
@RequiredArgsConstructor
public class CardEligibilityServiceImpl implements CardEligibilityService {
private final ThirdPartyEligibilityAdapter thirdPartyEligibilityAdapter;
private final QueueService queueService;
private final QueueMessageResponseService queueMessageResponseService;
@Override
public EligibilityResponse eligibilityService(Applicant applicant){
EligibilityResponse eligibilityResponse = checkEligibility(applicant);
queueService.pushMessage(queueMessageResponseService.createQueueResponse(applicant,eligibilityResponse));
return eligibilityResponse;
}
private EligibilityResponse checkEligibility(Applicant applicant) {
return thirdPartyEligibilityAdapter.getEligibility(applicant);
}
}
Service 2
public interface QueueService {
void pushMessage(QueueMessage queueMessage);
void retry();
}
@Service
@RequiredArgsConstructor
@Slf4j
public class QueueServiceImpl implements QueueService{
private final List<QueueMessage> deadQueue = new LinkedList<>();
//TODO check why async gets response stuck
@Override
@Async
public void pushMessage(QueueMessage queueMessage){
try {
//Push message to a queue - Queue settings Rabbit/Kafka - then this could be
//used by listeners to persist the data into DB
log.info("message queued {} ", queueMessage);
} catch (Exception e) {
log.error("Error {} , queueMessage {} ", e, queueMessage);
deadQueue.add(queueMessage);
}
}
**This method is a fault tolerance mechanism in case push to queue had any issues, The Local Method call to pushMessage isn’t the problem I also tried this by deleting retry method method**
@Override
@Scheduled(fixedDelay = 300000)
public void retry() {
log.info("Retrying Message push if there are any failure in enqueueing ");
final List<QueueMessage> temp = new LinkedList<>(deadQueue);
deadQueue.clear();
Collections.reverse(temp);
temp.forEach(this::pushMessage);
}
}
Service 3
public interface QueueMessageResponseService {
QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse);
}
@Service
public class QueueMessageResponseServiceServiceImpl implements QueueMessageResponseService {
@Override
public QueueMessage createQueueResponse(Applicant applicant, EligibilityResponse eligibilityResponse) {
return new QueueMessage(applicant,eligibilityResponse);
}
}
EDIT 2
THE MOST STRANGE BEHAVIOUR
If I add Thread.sleep(20);
in my async method, This works as expected , the user gets a response back without waiting for async
to complete. But Still unable to understand the cause.
@Async
public void pushMessage(QueueMessage queueMessage) {
try {
//Push message to a queue - Queue settings Rabbit/Kafka - then this could be
//used by listeners to persist the data into DB
Thread.sleep(20);
log.info("message queued {} ", queueMessage);
} catch (Exception e) {
log.error("Error {} , queueMessage {} ", e, queueMessage);
deadQueue.add(queueMessage);
}
}
The call to pushMessage
in retry
is a LOCAL call. So the proxy is not involved an the method is executed synchronously.
You have to move the async method to it's own class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With