-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
Description
Spring Framework already supports Vavr's Try type for transaction rollback decisions in TransactionAspectSupport. This proposal requests adding equivalent support for Vavr's Either type, which is widely used for railway-oriented programming and functional error handling.
Motivation
Either<L, R> is a common pattern for representing success (Right) or failure (Left) without throwing exceptions. It's the preferred approach for:
- Railway-oriented programming
- Explicit error handling in functional pipelines
- Domain-driven design where errors are domain concepts, not exceptions
Currently, a method like this will commit even when returning a failure:
@Transactional
public Either<PaymentError, Receipt> processPayment(PaymentRequest request) {
return validateRequest(request)
.flatMap(this::checkFunds)
.flatMap(this::executeTransfer);
// Returns Either.Left(InsufficientFunds) → transaction COMMITS (unexpected!)
}Users expect Either.Left to trigger a rollback, similar to how Try.Failure does.
Proposed Change
Add Either support in TransactionAspectSupport.VavrDelegate, mirroring the existing Try implementation:
In invokeWithinTransaction() (~line 380):
// Existing Try support
else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
// Proposed Either support
else if (vavrPresent && VavrDelegate.isVavrEither(retVal)) {
retVal = VavrDelegate.evaluateEitherLeft(retVal, txAttr, status);
}In VavrDelegate inner class:
public static boolean isVavrEither(@Nullable Object retVal) {
return (retVal instanceof Either<?, ?> either && either.isLeft());
}
public static Object evaluateEitherLeft(Object retVal,
@Nullable TransactionAttribute txAttr, TransactionStatus status) {
return ((Either<?, ?>) retVal).peekLeft(left -> {
if (left instanceof Throwable throwable) {
if (txAttr != null && txAttr.rollbackOn(throwable)) {
status.setRollbackOnly();
}
} else {
// Non-Throwable Left: rollback by default
status.setRollbackOnly();
}
});
}Behavior
| Return Value | Transaction Outcome |
|---|---|
Either.Right(value) |
Commit |
Either.Left(throwable) |
Rollback if txAttr.rollbackOn(throwable) matches |
Either.Left(nonThrowable) |
Rollback (default) |
Scope
This proposal (Phase 1):
- Basic Either.Left → rollback support
- Consistent with existing Try.Failure behavior
- Minimal change (~30-50 lines)
Future enhancement (Phase 2):
- Custom rollback rules for non-Throwable types (
rollbackForWithEitherattribute) - Fine-grained control over which Left values trigger rollback
Impact
- No breaking changes - additive only
- No new dependencies - Vavr is already optional
- Consistent - matches existing Try support pattern
- Low risk - isolated to VavrDelegate
Alternatives Considered
- Do nothing - Users must wrap Either in Try or throw exceptions (breaks functional style)
- Custom TransactionInterceptor - Requires significant boilerplate for each project
- AOP around advice - Happens after commit, too late for rollback
References
- Existing Try support:
TransactionAspectSupport.VavrDelegate.evaluateTryFailure() - Vavr Either documentation: https://docs.vavr.io/#_either
- Railway-oriented programming: https://fsharpforfunandprofit.com/rop/