Skip to main content

Step Types

Sagaweaw automatically infers the type of each step based on its definition. There are three types:


COMPENSABLE

A COMPENSABLE step has a compensate() method defined. If a subsequent step fails, Sagaweaw will execute the compensation automatically.

.step("block-balance")
.invoke(this::blockBalance)
.compensate(this::unblockBalance) // ← Defines compensation

Characteristics

AspectBehavior
CompensationExecuted in reverse order
DataContext available for compensation
RetryConfigurable

Full Example

// Invoke: Block the balance
private PixContext blockBalance(PixContext ctx) {
String blockId = balanceService.block(ctx.payerId(), ctx.amount());
return ctx.withBlockId(blockId); // Save the ID for compensation
}

// Compensate: Unblock using the saved blockId
private void unblockBalance(PixContext ctx) {
balanceService.unblock(ctx.blockId()); // Uses the saved ID
}
Tip

Always save in the context any data needed for compensation (IDs, tokens, etc).


PIVOT

A PIVOT step is the "point of no return". It has no compensation defined. After a PIVOT executes successfully, previous steps will not be compensated even if subsequent steps fail.

.step("transmit-to-bacen")
.invoke(this::transmitToBacen)
// No .compensate() = PIVOT

Characteristics

AspectBehavior
CompensationDoes not exist
Point of no returnYes
RetryGenerally configured

When to Use

  • Irreversible operations: Sending email, SMS, push notification
  • External integrations without rollback: Third-party APIs, BACEN, gateways
  • Final confirmations: External transaction commit

Example

.step("transmit-to-bacen") // PIVOT
.invoke(this::transmitToBacen)
.retry(exponential(3, Duration.ofSeconds(1))) // Retry before giving up
Note

If a PIVOT fails after all retry attempts, the saga enters FAILED state and previous COMPENSABLE steps are compensated.


RETRIABLE

A RETRIABLE step has infinite retry and no compensation. It must eventually always succeed. Use for idempotent operations that may fail temporarily.

.step("send-notification")
.invoke(this::sendNotification)
.retry(infinite()) // ← Infinite retry without compensate = RETRIABLE

Characteristics

AspectBehavior
CompensationDoes not exist
RetryInfinite
ExpectationWill always succeed

When to Use

  • Notifications: Email, SMS, push (idempotent)
  • Cache updates: Eventual consistency
  • Webhooks: Notifying external systems

Example

.step("send-confirmation-email")
.invoke(this::sendConfirmationEmail)
.retry(infinite(Duration.ofMinutes(5), Duration.ofHours(1))) // Min 5min, max 1h between attempts
Warning

Only use RETRIABLE if the operation is idempotent (executing multiple times produces the same result).


Comparison Table

Typecompensate()retry()When to Use
COMPENSABLE DefinedOptionalReversible operations
PIVOT NoneRecommendedPoint of no return
RETRIABLE Noneinfinite()Must always succeed

Inference Flow

Sagaweaw determines the type automatically:

┌─────────────────────────────────────────┐
│ Step defined │
└─────────────────┬───────────────────────┘


┌─────────────────┐
│ Has compensate? │
└────────┬────────┘

┌───────┴───────┐
│ │
YES NO
│ │
▼ ▼
COMPENSABLE ┌─────────────────┐
│ Infinite retry? │
└────────┬────────┘

┌───────┴───────┐
│ │
YES NO
│ │
▼ ▼
RETRIABLE PIVOT

Next Steps