Package com.tnsai.resilience
Class FallbackResolver
java.lang.Object
com.tnsai.resilience.FallbackResolver
Resolves the How
Unlike
), followed by the
captured
Phase 1 honours
@Fallback annotation on a Role's methods and
invokes the matching handler when an action's primary execution
fails. Issue #173, Phase 1.
How @Fallback works in this codebase
Unlike @Traced / @Metered (which wrap the action's
own dispatch), @Fallback marks a separate
method on the same Role class as a recovery handler:
@ActionSpec(type = ActionType.WEB_SERVICE, ...)
public PaymentResult charge(String requestId, Money amount) {
return paymentApi.charge(requestId, amount);
}
@Fallback(forAction = "charge", maxRetries = 0)
public PaymentResult chargeFallback(String requestId, Money amount, Exception error) {
return PaymentResult.queued(requestId);
}
Resolution
Per-Role-class scan, cached in aConcurrentHashMap keyed
by Role class so the reflection cost is paid once per JVM. For a
failing action A, the resolver looks for:
- A method with
@Fallback(forAction = "A"), OR - A method with
@Fallback(noforAction) — the global fallback for any unhandled action failure on this role.
Method-named fallbacks win over the global one. Multiple
@Fallback(forAction = "A") on the same class is a config
error logged at first lookup; the resolver picks the first one
encountered for stability.
Argument binding
The fallback method is invoked with the original action's working parameters in declaration order (matched by name viainvalid reference
ActionMetadata#getParameters()
Throwable as the last argument. Phase 1 supports
the two canonical signatures:
fallback(originalArgs..., Exception error)fallback(originalArgs..., Throwable error)
Other parameter shapes are tolerated by best-effort positional
binding (extras get null); fail-fast validation lands in
Phase 2.
maxRetries
Phase 1 honours Fallback.maxRetries() with simple immediate
retries (no exponential backoff — that's Phase 2). Each retry
re-executes the failed primary action via the supplied
retryCallback. The fallback method itself is invoked only
once, after the final retry fails.- Since:
- 0.5.4 (issue #173, Phase 1)
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceCallback for re-invoking the primary action during retry. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final ObjectSentinel returned bytryRecover(com.tnsai.metadata.ActionMetadata, com.tnsai.roles.Role, java.util.Map<java.lang.String, java.lang.Object>, java.lang.Throwable, com.tnsai.resilience.FallbackResolver.RetryCallback)to distinguish "no fallback found, rethrow" from "fallback returned null intentionally". -
Method Summary
Modifier and TypeMethodDescriptionstatic voidevictForTesting(Class<?> roleClass) Test hook — drops the cached fallback table for the given role class.static ObjecttryRecover(ActionMetadata action, Role role, Map<String, Object> params, Throwable failure, FallbackResolver.RetryCallback retryCallback) Attempts to recover from a failed action invocation by invoking the matching@Fallbackhandler.static ObjecttryRecover(ActionMetadata action, Role role, Map<String, Object> params, Throwable failure, FallbackResolver.RetryCallback retryCallback, Consumer<Method> beforeFallback) Managed dispatch supplies the common gate immediately before a fallback body.
-
Field Details
-
FALLBACK_NOT_FOUND
Sentinel returned bytryRecover(com.tnsai.metadata.ActionMetadata, com.tnsai.roles.Role, java.util.Map<java.lang.String, java.lang.Object>, java.lang.Throwable, com.tnsai.resilience.FallbackResolver.RetryCallback)to distinguish "no fallback found, rethrow" from "fallback returned null intentionally". Tests + ActionExecutor compare by reference.
-
-
Method Details
-
tryRecover
public static Object tryRecover(ActionMetadata action, Role role, Map<String, Object> params, Throwable failure, FallbackResolver.RetryCallback retryCallback) Attempts to recover from a failed action invocation by invoking the matching@Fallbackhandler. HonoursFallback.maxRetries()by re-invokingretryCallbackup to that many times before falling back; if every retry also fails, the fallback method is invoked exactly once with the latest exception.- Parameters:
action- the action that failedrole- the role hosting it (and any fallback methods)params- the original working parametersfailure- the exception thrown by the primary actionretryCallback- re-runs the primary action; called up tomaxRetriestimes. May throw.- Returns:
- the fallback's return value, or
FALLBACK_NOT_FOUNDwhen no fallback matches (the caller is then expected to rethrowfailure)
-
tryRecover
public static Object tryRecover(ActionMetadata action, Role role, Map<String, Object> params, Throwable failure, FallbackResolver.RetryCallback retryCallback, Consumer<Method> beforeFallback) Managed dispatch supplies the common gate immediately before a fallback body. -
evictForTesting
Test hook — drops the cached fallback table for the given role class. Production code should never call this; tests use it between cases that mutate role-class state.
-