feat(s1): saga compensation chain — wire releaseLock on cancel (STA-112)#111
Conversation
Replace placeholder logging in handleCancellation() with actual activity execution. Compensation steps are parsed from the LIFO stack and dispatched to the appropriate activity (RELEASE_FX_LOCK → fxLockActivity.releaseLock). Each compensation is wrapped in try-catch so failures are logged but never block remaining compensations. Tests verify: releaseLock called after FX lock cancel, releaseLock NOT called when cancel arrives before FX lock, and compensation failure still produces FAILED result. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds concrete compensation execution to the payment cancellation flow: parses compensation step strings, invokes the FX lock release activity for matching steps, logs successes and failures, and expands tests to cover pre-lock cancellation and exception handling during compensation. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
…es activity (STA-112) Temporal retries the activity 3 times (maxAttempts=3) before the failure propagates to the workflow's catch block, so exact count verification fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`:
- Around line 233-246: The compensation logic in executeCompensationStep
currently uses the fxLockActivity stub which was configured with
setMaximumAttempts(3), causing retries before the catch; change this by creating
a dedicated compensation activity stub (e.g., fxLockCompensationActivity) with
RetryOptions tuned for fail-fast behavior (single attempt/short timeout) and use
that stub inside executeCompensationStep when handling RELEASE_FX_LOCK_PREFIX,
leaving the original fxLockActivity unchanged for normal workflow calls.
In
`@payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowTest.java`:
- Around line 248-249: The test currently asserts a single call to
fxLockActivity.releaseLock but FxLockActivity is configured with
setMaximumAttempts(3) so Temporal will retry; update the assertion in
PaymentWorkflowTest to expect three invocations by using
then(fxLockActivity).should(times(3)).releaseLock(new FxReleaseRequest(LOCK_ID,
PAYMENT_ID, "Timeout")), and add the static import for Mockito.times;
alternatively replace times(3) with atLeastOnce() if you prefer to treat retry
count as an implementation detail.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b0d1bade-70fa-4cdd-8e8d-96652a005ff6
📒 Files selected for processing (2)
payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.javapayment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowTest.java
| private void executeCompensationStep(String step, UUID paymentId, String reason) { | ||
| try { | ||
| if (step.startsWith(RELEASE_FX_LOCK_PREFIX)) { | ||
| var lockId = UUID.fromString(step.substring(RELEASE_FX_LOCK_PREFIX.length())); | ||
| fxLockActivity.releaseLock(new FxReleaseRequest(lockId, paymentId, reason)); | ||
| log.info("Successfully released FX lock {} for paymentId={}", lockId, paymentId); | ||
| } else { | ||
| log.warn("Unknown compensation step: {} for paymentId={}", step, paymentId); | ||
| } | ||
| } catch (Exception e) { | ||
| log.error("Compensation step failed: {} for paymentId={}, error={}", | ||
| step, paymentId, e.getMessage(), e); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Compensation retry semantics — architectural consideration.
The fxLockActivity stub used here has setMaximumAttempts(3) configured at Lines 63-64. This means compensation retries exhaustively before the catch block is reached, which may be intentional for resilience.
If compensation should fail fast (e.g., single attempt with immediate fallback), consider creating a separate activity stub with different RetryOptions for compensation scenarios. This also aligns with saga patterns where compensations often have distinct retry/timeout policies.
Not blocking — current behavior is functionally correct.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`
around lines 233 - 246, The compensation logic in executeCompensationStep
currently uses the fxLockActivity stub which was configured with
setMaximumAttempts(3), causing retries before the catch; change this by creating
a dedicated compensation activity stub (e.g., fxLockCompensationActivity) with
RetryOptions tuned for fail-fast behavior (single attempt/short timeout) and use
that stub inside executeCompensationStep when handling RELEASE_FX_LOCK_PREFIX,
leaving the original fxLockActivity unchanged for normal workflow calls.
Summary
fxLockActivity.releaseLock()call intohandleCancellation()— compensations now execute instead of just loggingRELEASE_FX_LOCK:<lockId>) and dispatched to the appropriate activityTest plan
shouldReleaseFxLockOnCancelAfterFxLock— verifiesreleaseLockcalled with exactFxReleaseRequest(LOCK_ID, PAYMENT_ID, reason)shouldNotReleaseLockWhenCancelBeforeFxLock— cancel during compliance, neitherlockFxRatenorreleaseLockcalledshouldReturnFailedWhenCompensationThrows—releaseLockthrows RuntimeException, workflow still returns FAILEDCloses STA-112
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests