你每天都在for-each循环里与迭代器擦肩而过,但你可能从未真正挖掘过它那冰山之下的惊人潜力。当被问及“如何实现一个可暂停、可恢复、失败后还能自动回滚的复杂业务流程”时,你会想到什么?状态机?工作流引擎?

不,答案可能简单到让你惊讶——就是这个你最熟悉的**“迭代器”**。

本文将摒弃高深的比喻,从一个新人最容易写出的“过程式”烂代码开始,带你亲手构建一个支持“断点续传”和“失败回滚”功能的轻量级业务工作流,让你彻底领悟,最基础的工具,往往蕴含着最深刻的架构智慧。

梦魇的开端:那个“一根筋”走到底的下单流程

想象一下,你刚入职,接到一个需求:实现一个电商下单的后端流程。这个流程包含四个步骤:

  1. 校验订单参数
  2. 预留商品库存
  3. 扣减用户余额
  4. 发送成功通知

你大笔一挥,很快写出了下面这段“一根筋”走到底的代码:

public class BadOrderService {
    public void placeOrder(OrderRequest req) {
        // 为了回滚,我们得用一堆布尔“膏药”来标记进度
        boolean inventoryReserved = false;
        boolean balanceCharged = false;

        try {
            System.out.println("--- 开始处理订单 ---");
            // 步骤1:校验参数
            if (req.getQuantity() <= 0) {
                throw new Exception("商品数量非法!");
            }
            System.out.println("✅ 1. 参数校验通过");

            // 步骤2:预留库存
            reserveInventory(req);
            inventoryReserved = true; // 贴上膏药1
            System.out.println("✅ 2. 库存预留成功");

            // 步骤3:扣减余额 (我们在这里故意让它失败)
            if (req.isForcePayFail()) {
                throw new Exception("支付网关超时!");
            }
            chargeBalance(req);
            balanceCharged = true; // 贴上膏药2
            System.out.println("✅ 3. 扣款成功");

            // 步骤4:发送通知
            sendNotification(req);
            System.out.println("✅ 4. 通知发送成功");

            System.out.println("🎉 订单处理完成!");

        } catch (Exception e) {
            System.out.println("❌ 订单处理失败: " + e.getMessage());
            System.out.println("--- 开始回滚 ---");
            // 噩梦般的回滚逻辑,完全靠“膏药”判断
            if (balanceCharged) {
                System.out.println("↩️ 正在退款...");
                refund(req);
            }
            if (inventoryReserved) {
                System.out.println("↩️ 正在释放库存...");
                releaseInventory(req);
            }
            System.out.println("--- 回滚结束 ---");
        }
    }
}

这段代码,丑陋但真实。它的问题是致命的:

  1. 逻辑像面条:所有的步骤和回滚逻辑都搅在一起,难以阅读。
  2. 状态管理混乱:靠一堆布尔“膏药”(inventoryReserved)来跟踪进度,每增加一个可回滚的步骤,就要多加一张“膏药”和一块if补丁。
  3. 扩展性极差:想在“扣款”和“发通知”之间加一个“发券”步骤?你得小心翼翼地修改这个巨大的try-catch块,极易出错。

思想的转变:从“一堆代码”到“一串积木”

现在,我们换个思路。这个下单流程,不就是**一连串有序的“步骤”**吗?

就像玩乐高,我们可以把每个步骤,都做成一块独立的、可复用的“积木”

1.步骤接口

这是所有积木的标准,它规定了每块积木都必须有一个名字,并且能被“执行”(apply)。

public interface Step {
    
    /**
     * 返回步骤名称,用于日志记录与可视化展示。
     */
    String name();

    /**
     * 执行当前步骤的业务逻辑。
     *
     * @param ctx 流转上下文(承载共享数据与流程日志)
     * @return 步骤执行结果;当 success=false 时,工作流引擎将触发补偿回滚
     * @throws Exception 允许抛出异常,由工作流引擎统一捕获处理
     */
    StepResult apply(FlowContext ctx) throws Exception;
}

2.可回滚的步骤

有些积木(比如“扣款”)装错了,是可以“拆掉”的(回滚)。我们给这类积木贴个特殊标签Compensable

public interface Compensable {
    
    /**
     * 当工作流中后续步骤失败时,用于对本步骤进行补偿/回滚的逻辑。
     * 仅对已成功执行过的步骤生效,由引擎以“反向遍历”顺序调用。
     *
     * @param ctx 流转上下文(可根据之前放入的数据执行补偿)
     */
    void compensate(FlowContext ctx);
}

3.配套工具:FlowContext (上下文)

这是贯穿整个流程的“工具箱”,所有步骤都可以从里面拿数据、放结果,实现信息共享。

// 流转上下文,携带数据和日志
@Slf4j
@Getter
@Component
public final class FlowContext {
    private final Map<String, Object> data = new HashMap<>();
    private final List<String> logs = new ArrayList<>();

    public Map<String, Object> data() {
        return data;
    }

    public List<String> logs() {
        return logs;
    }

    public void log(String s) {
        logs.add(s);
        log.info(s);
    }
}

// 步骤执行结果
public record StepResult(boolean success, String message) {
    public static StepResult ok() {
        return new StepResult(true, "OK");
    }

    public static StepResult fail(String msg) {
        return new StepResult(false, msg);
    }
}

4.改造成独立业务块

现在,我们可以把原来的业务逻辑,改造成一块块独立的积木:

校验参数步骤
@Slf4j
@Component
public class ValidateInputStep implements Step {
    @Override
    public String name() {
        return "校验参数";
    }

    @Override
    public StepResult apply(FlowContext ctx) {
        Long userId = (Long) ctx.data().get("userId");
        Long productId = (Long) ctx.data().get("productId");
        Integer qty = (Integer) ctx.data().get("quantity");

        if (userId == null || userId <= 0) {
            return StepResult.fail("用户ID非法");
        }
        if (productId == null || productId <= 0) {
            return StepResult.fail("商品ID非法");
        }
        if (qty == null || qty <= 0) {
            return StepResult.fail("数量必须大于0");
        }

        ctx.log("✅ 1. 参数校验通过");
        return StepResult.ok();
    }
}
预留库存(可回滚)
@Slf4j
@Component
@RequiredArgsConstructor
public class ReserveInventoryStep implements Step, Compensable {

    private final InventoryRepository inventoryRepository;

    @Override
    public String name() {
        return "预留库存";
    }

    @Override
    public StepResult apply(FlowContext ctx) {
        Long productId = (Long) ctx.data().get("productId");
        Integer qty = (Integer) ctx.data().get("quantity");
        int before = inventoryRepository.getStock(productId);
        if (before < qty) {
            return StepResult.fail("库存不足");
        }
        boolean ok = inventoryRepository.reserve(productId, qty);
        if (!ok) {
            return StepResult.fail("库存预留失败");
        }
        ctx.data().put("inventoryReserved", true);
        ctx.log("✅ 2. 库存预留成功,商品" + productId + ",数量" + qty);
        return StepResult.ok();
    }

    @Override
    public void compensate(FlowContext ctx) {
        if (Boolean.TRUE.equals(ctx.data().get("inventoryReserved"))) {
            Long productId = (Long) ctx.data().get("productId");
            Integer qty = (Integer) ctx.data().get("quantity");
            inventoryRepository.release(productId, qty);
            ctx.log("释放已预留库存 (补偿)... 商品" + productId + " 返还数量 " + qty);
        }
    }
}
扣减余额(可回滚)
@Slf4j
@Component
@RequiredArgsConstructor
public class ChargeBalanceStep implements Step, Compensable {

    private final AccountRepository accountRepository;

    @Override
    public String name() {
        return "扣减余额";
    }

    @Override
    public StepResult apply(FlowContext ctx) {
        Long userId = (Long) ctx.data().get("userId");
        Integer qty = (Integer) ctx.data().get("quantity");
        boolean forcePayFail = Boolean.TRUE.equals(ctx.data().get("forcePayFail"));

        // 简化:假设单价固定 9.90
        BigDecimal amount = new BigDecimal("9.90").multiply(BigDecimal.valueOf(qty));

        if (forcePayFail) {
            return StepResult.fail("支付网关超时");
        }

        boolean ok = accountRepository.charge(userId, amount);
        if (!ok) {
            return StepResult.fail("余额不足");
        }
        ctx.data().put("balanceCharged", true);
        ctx.data().put("chargedAmount", amount);
        ctx.log("✅ 3. 扣款成功,金额 " + amount);
        return StepResult.ok();
    }

    @Override
    public void compensate(FlowContext ctx) {
        if (Boolean.TRUE.equals(ctx.data().get("balanceCharged"))) {
            Long userId = (Long) ctx.data().get("userId");
            BigDecimal amount = (BigDecimal) ctx.data().get("chargedAmount");
            accountRepository.refund(userId, amount);
            ctx.log("退款完成 (补偿)... 金额 " + amount);
        }
    }
}
发送通知(不可回滚)
@Slf4j
@Component
@RequiredArgsConstructor
public class NotifyUserStep implements Step {

    private final NotificationGateway notificationGateway;

    @Override
    public String name() {
        return "发送通知";
    }

    @Override
    public StepResult apply(FlowContext ctx) {
        Long userId = (Long) ctx.data().get("userId");
        notificationGateway.send(userId, "下单成功通知");
        ctx.log("✅ 4. 通知发送成功");
        return StepResult.ok();
    }
}

至此,我们已经把混乱的过程,拆分成了清晰的对象。但问题来了:谁来按照正确的顺序,把这些积木搭起来呢?

迭代器,流程的“主宰者”!

我们需要的,是一个能**“遍历”**这些积木的工具。这,正是迭代器模式的用武之地!

我们将创建一个Workflow类,它像一个**“积木托盘”,负责按顺序装载我们的Step积木。但它自己不负责搭建,而是提供多种“搭建机器人”(迭代器)**。

@Getter
@Component
public final class Workflow {

    private final List<Step> steps = new ArrayList<>();

    public Workflow add(Step s) {
        steps.add(s);
        return this;
    }

    public WFIterator iterator() {
        return new WFIterator(0);
    }

    public ReverseIterator reverseIterator(int startIndex) {
        return new ReverseIterator(startIndex);
    }

    // 正向迭代器
    public final class WFIterator implements Iterator<Step> {
        private int cursor;

        public WFIterator(int start) {
            this.cursor = start;
        }

        @Override
        public boolean hasNext() {
            return cursor < steps.size();
        }

        @Override
        public Step next() {
            return steps.get(cursor++);
        }

        public int cursor() {
            return this.cursor;
        }
    }

    // 反向迭代器
    public final class ReverseIterator implements Iterator<Step> {
        private int cursor;

        public ReverseIterator(int start) {
            this.cursor = start;
        }

        @Override
        public boolean hasNext() {
            return cursor >= 0;
        }

        @Override
        public Step next() {
            return steps.get(cursor--);
        }
    }
}

现在,我们有了“积木”和“搭建机器人”,最后就差一个**“总控室”**来指挥机器人干活了。

@Slf4j
@Component
public final class WorkflowEngine {
    public void run(Workflow wf, FlowContext ctx) {
        var forwardIterator = wf.iterator();
        int lastSuccessStepIndex = -1;

        try {
            while (forwardIterator.hasNext()) {
                Step currentStep = forwardIterator.next();
                ctx.log("▶️ 执行步骤: " + currentStep.name());
                StepResult result = currentStep.apply(ctx);
                if (!result.success()) {
                    throw new Exception("步骤 " + currentStep.name() + " 失败: " + result.message());
                }
                lastSuccessStepIndex = forwardIterator.cursor() - 1;
            }
            ctx.log("🎉 工作流全部成功!");
        } catch (Exception e) {
            ctx.log("❌ 工作流异常: " + e.getMessage());
            ctx.log("--- 开始回滚 ---");
            var reverseIterator = wf.reverseIterator(lastSuccessStepIndex);
            while (reverseIterator.hasNext()) {
                Step stepToUndo = reverseIterator.next();
                if (stepToUndo instanceof Compensable c) {
                    ctx.log("⏪ 补偿步骤: " + stepToUndo.name());
                    try {
                        c.compensate(ctx);
                    } catch (Exception ex) {
                        log.warn("补偿步骤异常: {}", ex.getMessage());
                    }
                }
            }
            ctx.log("--- 回滚结束 ---");
        }
    }
}

那段丑陋的、靠布尔膏药和if判断来做的回滚,被一段**通用的“反向遍历”**完美取代!

无论你的流程有多少步,run方法的核心逻辑永远不变。它只关心指挥“正向机器人”前进,和在失败时指挥“反向机器人”后退。**“怎么走”(迭代器)“走哪里”(工作流引擎)**被完美地分离开来。

订单服务实现(基于通用工作流引擎)

下面是所有拼图组合在一起,可以直接运行的完整示例:

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final WorkflowEngine engine;
    private final ValidateInputStep validateInputStep;
    private final ReserveInventoryStep reserveInventoryStep;
    private final ChargeBalanceStep chargeBalanceStep;
    private final NotifyUserStep notifyUserStep;
    // 使用独立的上下文实例,避免并发污染

    @Override
    public OrderResultDTO placeOrder(OrderRequest request) {
        // 准备上下文(每次创建新实例)
        FlowContext ctx = new FlowContext();
        ctx.data().put("userId", request.getUserId());
        ctx.data().put("productId", request.getProductId());
        ctx.data().put("quantity", request.getQuantity());
        ctx.data().put("forcePayFail", Boolean.TRUE.equals(request.getForcePayFail()));

        // 组装工作流
        Workflow wf = new Workflow()
                .add(validateInputStep)
                .add(reserveInventoryStep)
                .add(chargeBalanceStep)
                .add(notifyUserStep);

        engine.run(wf, ctx);

        boolean success = ctx.logs().stream().anyMatch(s -> s.contains("工作流全部成功"));
        String message = success ? "下单成功" : "下单失败";
        return new OrderResultDTO(success, message, ctx.logs());
    }
}

对比一下最开始的“面条代码”,再看看现在这个版本,是不是感觉豁然开朗?我们不仅让代码变得清晰、可扩展,更重要的是,我们用一个基础的设计模式,构建起了一个小型、可靠的业务“引擎”

总结

  1. 从“过程”到“对象”:重构的第一步,是把混乱的过程,拆分成一个个职责单一的Step对象。
  2. 迭代器是“执行官”:不要把迭代器看作只能遍历数据。它是流程的“执行官”,它的next()就是“前进一步”的命令。
  3. 反向迭代是“后悔药”:当你需要为一系列操作提供补偿或回滚逻辑时,反向迭代器提供了一种极其优雅、通用的实现范式。
  4. 游标是“书签”:迭代器内部的游标,是实现暂停、恢复、断点续传等长时间任务的天然“状态记录器”。
  5. 分离“编排”与“执行”WorkflowEngine只负责“编排”(指挥迭代器),Step只负责“执行”(具体业务)。职责清晰,天下太平。

下次当你面对一段复杂的、有顺序、有回滚的业务逻辑时,请暂时忘记那些沉重的工作流框架。静下心来,问问自己:这,是不是只是一场需要一个“正向”和一个“反向”导游的“旅行”?

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐