Class PaymentRole

java.lang.Object
com.tnsai.roles.Role
com.tnsai.integration.scop.examples.PaymentRole

@RoleSpec(name="PaymentAgent", description="Charges accounts via a flaky external API; falls back to queueing on failure.", version="0.1.0", llm=@LLMSpec(provider=OPENAI,model="gpt-4o-mini",temperature=0.0f)) public class PaymentRole extends com.tnsai.roles.Role
Reference example: declarative observability + recovery decorator stack — @Traced + @Metered + @Fallback (issue #173, Phase 1).

Demonstrates the canonical resilience pattern from the issue: a flaky external call (here simulated with a random failure) wrapped in three decorators that the framework's ActionExecutor applies automatically — no manual try/catch in the action body.

Decorator stack (outer → inner)

  1. @Traced — opens a span, pushes a trace id into MDC, emits span.start / span.end log lines with duration + outcome. Optional args/result inclusion.
  2. @Metered — records call count, success/failure counters, and a latency histogram in ResilienceMetrics. Tests can snapshot the registry to assert metrics fired correctly.
  3. @Fallback — when the primary action throws, retries up to maxRetries times, then invokes the matching fallback method on this role with the original args + the captured exception. Suppresses the original failure if the fallback returns normally.

The fallback method convention

Fallback methods live on the same role class as the action they cover, marked with @Fallback(forAction = "primaryActionName"). They take the primary action's parameters in the same declaration order, with a trailing Throwable (or Exception) parameter that receives the captured failure. The method's return value becomes the action's return value when the framework invokes the fallback.

Why this example is "flaky" by design

The charge method has a 50% random failure rate. This isn't a bug — it's the cookbook's whole point: a real consumer code calling charge once gets either the tool's result or the fallback's recovery value, transparently. The integration test (PaymentRoleResilienceIntegrationTest) seeds the JVM's ThreadLocalRandom via repeated invocations to assert both branches fire.
Since:
0.5.4 (issue #173, Phase 1 — resilience cookbook)
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    charge(String requestId, int amount)
    Primary action — calls the (simulated) external payment API.
    chargeFallback(String requestId, int amount, Throwable failure)
    Recovery handler for charge(java.lang.String, int).
    com.tnsai.models.role.RoleIdentity
     

    Methods inherited from class com.tnsai.roles.Role

    create, getAction, getActionCount, getActions, getDomain, getGoal, getInputGuardrailConfig, getLLM, getMinimalPrompt, getName, getOutputGuardrailConfig, getResilienceConfig, getSpec, getSystemPrompt, hasAction, hasLLM, identity, setLLM, toString

    Methods inherited from class java.lang.Object

    equals, getClass, hashCode, notify, notifyAll, wait, wait, wait
  • Constructor Details

    • PaymentRole

      public PaymentRole()
  • Method Details

    • getIdentity

      public com.tnsai.models.role.RoleIdentity getIdentity()
      Specified by:
      getIdentity in class com.tnsai.roles.Role
    • charge

      @ActionSpec(type=LOCAL, description="Charge a customer\'s account.") @Traced(operationName="payment.charge", includeArgs=true) @Metered(name="payment.charge", histogram=true) public String charge(String requestId, int amount)
      Primary action — calls the (simulated) external payment API. Fails ~50% of the time to demonstrate the fallback path.

      Three decorators stack here:

      • @Traced(operationName = "payment.charge", includeArgs = true) — every call gets a trace id; args (requestId, amount) appear in the span.start log line.
      • @Metered(name = "payment.charge", histogram = true) — every call ticks a counter and records latency. The integration test snapshots the registry to assert both success and failure paths increment the right counters.
      • @Fallback declared on chargeFallback(java.lang.String, int, java.lang.Throwable) below — handles failures via the queue path.
    • chargeFallback

      @Fallback(forAction="charge", maxRetries=2) public String chargeFallback(String requestId, int amount, Throwable failure)
      Recovery handler for charge(java.lang.String, int). Marked with @Fallback(forAction = "charge", maxRetries = 2) — the framework will:
      1. Catch any exception thrown by charge
      2. Re-invoke charge up to maxRetries = 2 additional times
      3. If all retries also fail, invoke this method with the original (requestId, amount) + the final exception
      4. Return this method's value as if charge had returned it normally

      Method name doesn't matter for resolution — the forAction value is the binding key. Multiple fallbacks with different forAction values can coexist on the same role class.