I have a spring RetryTemplate wrapping a set of http calls to a DB wrapping service, which when combined have all a transactional nature, meaning that if the last one fails, I need to rollback all previously executed operations, and start the processing all over.
The problem is that the recover is only executed after the last fail attempt, which make the problem bigger than I tried to solve:
I have a DB state that is inconsistent between each attempt.
How can I achieve a recover execution on every failed attempt?
I tried writing the boilerplate code myself. Apart from the very ugly look of it, I prefer a well tested and designed method.
//I'm only experimenting now, when I'm done the template and policy will be injected.
RetryTemplate relshpProcessRetry = new RetryTemplate();
SimpleRetryPolicy retryPolicy=new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
relshpProcessRetry.setRetryPolicy(retryPolicy);
try {
relshpProcessRetry
.execute((RetryCallback<Void, StorageAccessorException>) context -> {
final var decisions = decideRelationships(relationshipRequest).getDecisions();
context
.setAttribute(String.valueOf(relationshipRequest.getParentListingId()), decisions);
decisions
.forEach(relationshipDecision -> relationshipDecision.apply(storageAccessor));
return null;
}, context -> {
//noinspection unchecked
List<RelationshipDecision> decisions = (List<RelationshipDecision>) context
.getAttribute(String.valueOf(relationshipRequest.getParentListingId()));
//noinspection ConstantConditions
Validate.notEmpty(decisions, "Decisions to rollback must not be null");
ReverseListIterator<RelationshipDecision> decisionReverseIterator = new ReverseListIterator<>(
decisions);
while (decisionReverseIterator.hasNext()) {
final var decision = decisionReverseIterator.next();
if (decision.isApplied()) {
decision.rollback(storageAccessor);
}
}
return null;
});
} catch (ExhaustedRetryException e) {
//if we are here, all attempts ended in optimistic lock exceptions
log.debug(
"Failed to process relationship(s) for request {} , after all retry attempts have been exhausted",
relationshipRequest, e);
throw new IllegalStateException(
"Relationship processing failed - " + relationshipRequest.toString(), e);
}
In my unit tests I see the following being printed prior to the first recovery:
14:24:38.882 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=0
14:24:40.726 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=1
14:24:40.726 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=1
14:24:41.843 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=2
14:24:41.843 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry: count=2
14:24:42.781 [main] DEBUG org.springframework.retry.support.RetryTemplate - Checking for rethrow: count=3
14:24:42.781 [main] DEBUG org.springframework.retry.support.RetryTemplate - Retry failed last attempt: count=3