Using Result in a Java Spring application is not as straightforward as it seems. In Spring, transactions are controlled by annotations. They are defined at the Bean-level, where they state whether a transaction should start or not. Implementation-wise, this effectively creates a Proxy around the Bean that will start and stop the transaction. A transaction will stop when an exception propagates between these Proxy layers. This effectively means that using a Result to indicate the failure of an operation means it will not be rolled back.
An example of a Result class was introduced in a previous post. It is an Either pattern from functional programming for describing an operation that has either failed or succeeded.
Whether you actually want to roll back the transaction depends entirely on context. If you have a domain class that returns a Result, you might not want to roll back anything, as it is the consumer’s job to handle the error. However, if your application service returns a Result, you would want it to roll back the transaction, as that is part of its normal contract.
Transactional Rollback
One option is to roll back the transaction manually and programmatically:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Beware of this approach, as it does not work well if any code afterwards calls a transactional method, because it will throw a
UnexpectedRollbackException.
The approach that I have found to work is the most annoying one: throwing an exception. Instead of returning a Result at the application layer, it defaults to throwing an Exception. This is fairly straightforward to implement with the Message Bus pattern, as all application-level calls go through a single service.
Conclusion
It seems the only way to make the @Transactional annotation work with Result without triggering UnexpectedRollbackException is to throw an exception. If anyone finds a different solution, please reach out.