非可重试执行器异常 (NonRetryableExecutorException)

如果您在执行器执行过程中遭遇了并非暂时性的物理故障(即无论重试多少次也绝不可能成功执行的错误,如参数非法、用户不存在、账户余额不足、风控拦截等),您应当主动抛出 NonRetryableExecutorException。 收到该异常后,StackSaga 引擎将立即终止正向执行流,并根据工作流配置从当前故障点启动全链路的逆向补偿回滚流程。

如果您未在代码中显式捕获底层异常,业务逻辑抛出的任何未受控 RuntimeException 也会被 StackSaga 引擎捕获并默认视为*非可重试异常*。 但为了实现严谨的生产级错误治理与元数据可观测性,强烈建议开发者显式捕获并结构化包装此类业务异常。

非可重试执行器异常 (NonRetryableExecutorException) 允许在以下执行器的方法中抛出:

执行器类型 doProcess() 方法 doRevert() 方法

Query Executor (查询执行器)

✔

✖

Command Executor (指令执行器)

✔

✖

Revert Before Executor (前置回滚执行器)

✖

Revert After Executor (后置回滚执行器)

✖

如上表所示,所有逆向补偿方法 (doRevert) 均不允许抛出 NonRetryableExecutorException。 其根本原因根植于 StackSaga 的核心架构设计:任何指令执行器 (CommandExecutor) 都有对应的逆向补偿执行;但逆向补偿执行自身只允许抛出可重试异常。 因为正向业务步骤哪怕执行失败,还有逆向补偿机制可以作为兜底挽回数据一致性。 但如果逆向补偿本身被允许永久失败,系统中将再无任何手段可以补偿“补偿本身的失败”。
简而言之:只有正向业务推进流程才允许抛出非可重试异常,逆向补偿必须确保最终必定能够成功执行。
@SagaExecutor(
        executeFor = "user-service",
        liveCheck = true,
        value = "CheckUserExecutor"
)
@AllArgsConstructor
public class CheckUserExecutor implements QueryExecutor<OrderDomainEntity> {

    private final UserService userService;

    @Override
    public ProcessStepManager<OrderDomainEntity> doProcess(
            ProcessStack processStack,
            OrderDomainEntity domainEntity,
            ProcessStepManagerUtil<OrderDomainEntity> stepManagerUtil
    ) throws RetryableExecutorException, NonRetryableExecutorException {

        try {

            ResponseEntity<UserDetailDto> userDetail = this.userService.getUserDetail(domainEntity.getUsername());
            ...
        } catch (FeignException.ServiceUnavailable unavailableException) {
            throw RetryableExecutorException.of(unavailableException);
        } catch (FeignException.BadRequest badRequestException) {
            (1)
            throw NonRetryableExecutorException
                    .buildWith(badRequestException)
                    .put("time", LocalDateTime.now())  (2)
                    .put("reason", "BadRequest") (2)
                    ...
                    .build();
        }
    }

}
1 遇到 BadRequest 业务入参错误时抛出 NonRetryableExecutorException。该类提供了便捷的链式调用,允许向异常中挂载丰富的诊断元数据。
2 通过 Key-Value 键值对的形式将上下文元数据附加到异常中。
在标准 Java 开发中,我们习惯于在自定义 Exception 对象中挂载复杂的业务实体。 但在 StackSaga 中,我们极力不推荐这种做法。 因为在分布式环境中,事务发生的任何上下文和异常随时可能被序列化持久化至事件存储 (Event Store) 中。 对复杂的异常对象进行 JSON 或 XML 序列化不仅代价高昂,而且经常会因循环引用引发新的运行时异常。 因此,StackSaga 引擎仅将底层异常以纯字符串摘要的形式持久化至事件存储(供 Trace Window 运维看板查看)。 如果您希望向逆向补偿流程或审计日志透传结构化元数据,请使用 NonRetryableExecutorException.put(key, value) 方法,挂载轻量级的键值对。
抛出非可重试异常不仅限于底层接口报错。 如果您在校验当前业务实体时,发现前置业务条件不满足(例如库存数小于购买数),并决定该分布式长事务不应再继续向下推进时,同样可以直接主动构造并抛出 NonRetryableExecutorException 以触发逆向回滚。