Every stage declares its compensator

public interface CompensatingStage extends PipelineStage {
  void compensate(PipelineContext ctx);
}

public class CreateOrderStage implements CompensatingStage {
  public void run(PipelineContext ctx) {
    long orderId = orderService.create(ctx.get("order"));
    ctx.put("orderId", orderId);
  }
  public void compensate(PipelineContext ctx) {
    ctx.get("orderId", Long.class)
       .ifPresent(orderService::cancel);
  }
}
Advertisement

The saga runner

Runs stages forward, tracks which succeeded. On failure, runs compensators in reverse order.

Advertisement

Idempotent compensators

Compensators MUST be idempotent — they might be retried if the compensation itself fails. Cancelling an already-cancelled order should be a no-op, not an error.