I have a Spring Boot application that relies on 2 data sources: an third-party HTTP API and a PostgresSQL db. The database is queried using Spring Data JPA, I have set up all the classes for the entities and corresponding repositories.
There is a service with a transactional method that looks something like this:
@Service
public class MyService {
@Autowired
private EntityRepository entityRepository;
@Autowired
private ApiService apiService;
@Transactional
public Entity createEntity(EntityInputData data) {
this.apiService.createEntity(data); // HTTP call
Entity entity = new Entity(data);
return this.entityRepository.save(entity);
}
}
From what I understand, if any exception were to be thrown during the execution of createEntity
, the Entity
object would not be persisted in the database thanks to the @Transactional
annotation. However, there is nothing preventing or reverting the API call if the entity ends up not being created.
I tried adding a try/catch within createEntity
, but I noticed that exceptions thrown while committing the transaction would not be catched (ie because of a PostgresSQL error), since they are thrown after the method has actually been executed:
@Transactional
public Entity createEntity(EntityInputData data) {
string apiId = this.apiService.createEntity(entity); // HTTP call
try {
Entity entity = new Entity();
return this.entityRepository.save(entity);
} catch (Exception e) {
// Not called if exception is thrown while committing transaction
this.apiService.deleteEntity(apiId);
throw e;
}
}
If I move the try/catch outside around the call to the method, then I can catch these exception. The problem is that I have no context information for handling the rollback in the third-party API.
try {
myService.createEntity(data);
} catch (Exception e) {
// How do I call my API to tell it to rollback whatever has been created?
}
I could not find a method to reflect a DB rollback to an external service that matches my use case, any help?
2
Answers
I found this solution that seems to work ok using a TransactionalEventListener. Simply publish an event after calling the API service, then catch the event only if the transaction is aborted. Since I can pass whatever data I need to the event, I can easily call my service again to revert the changes:
MyService:
ApiService:
Then declare a Component for handling events:
The idea is that the event will only be treated if there is a rollback. Plus I no longer have to deal with a try/catch, everything works as if the method in my ApiService was transactional as well.
Search on: saga design pattern
In this case, saga orchestration seems the simplest approach. Basic idea is to split
MyService
into separate classes, something likeand
then
MyService
becomes an orchestrator for each step of the process:and calls the remote API to delete the entity on an exception.