别再只用 for-each 了!我用迭代器模式,写了个“带回滚功能”的轻量工作流引擎
你每天都在for-each循环里与迭代器擦肩而过,但很可能错过了它真正的威力。这个看似“基础”的模式,绝不仅是“遍历”那么简单。它是一种关于“解耦”与“控制”的深刻思想。本文将摒弃高深的比喻,从零开始,带你亲手构建一个支持“fail-fast”安全机制、多种遍历方式的自定义集合。然后,我们将以此为基石,一步步揭示如何利用迭代器的核心机制,去解决分页拉取、并发安全,乃至实现一个带“断点续传”和“失败
你每天都在for-each
循环里与迭代器擦肩而过,但你可能从未真正挖掘过它那冰山之下的惊人潜力。当被问及“如何实现一个可暂停、可恢复、失败后还能自动回滚的复杂业务流程”时,你会想到什么?状态机?工作流引擎?
不,答案可能简单到让你惊讶——就是这个你最熟悉的**“迭代器”**。
本文将摒弃高深的比喻,从一个新人最容易写出的“过程式”烂代码开始,带你亲手构建一个支持“断点续传”和“失败回滚”功能的轻量级业务工作流,让你彻底领悟,最基础的工具,往往蕴含着最深刻的架构智慧。
梦魇的开端:那个“一根筋”走到底的下单流程
想象一下,你刚入职,接到一个需求:实现一个电商下单的后端流程。这个流程包含四个步骤:
- 校验订单参数
- 预留商品库存
- 扣减用户余额
- 发送成功通知
你大笔一挥,很快写出了下面这段“一根筋”走到底的代码:
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("--- 回滚结束 ---");
}
}
}
这段代码,丑陋但真实。它的问题是致命的:
- 逻辑像面条:所有的步骤和回滚逻辑都搅在一起,难以阅读。
- 状态管理混乱:靠一堆布尔“膏药”(
inventoryReserved
)来跟踪进度,每增加一个可回滚的步骤,就要多加一张“膏药”和一块if
补丁。 - 扩展性极差:想在“扣款”和“发通知”之间加一个“发券”步骤?你得小心翼翼地修改这个巨大的
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());
}
}
对比一下最开始的“面条代码”,再看看现在这个版本,是不是感觉豁然开朗?我们不仅让代码变得清晰、可扩展,更重要的是,我们用一个基础的设计模式,构建起了一个小型、可靠的业务“引擎”。
总结
- 从“过程”到“对象”:重构的第一步,是把混乱的过程,拆分成一个个职责单一的
Step
对象。 - 迭代器是“执行官”:不要把迭代器看作只能遍历数据。它是流程的“执行官”,它的
next()
就是“前进一步”的命令。 - 反向迭代是“后悔药”:当你需要为一系列操作提供补偿或回滚逻辑时,反向迭代器提供了一种极其优雅、通用的实现范式。
- 游标是“书签”:迭代器内部的游标,是实现暂停、恢复、断点续传等长时间任务的天然“状态记录器”。
- 分离“编排”与“执行”:
WorkflowEngine
只负责“编排”(指挥迭代器),Step
只负责“执行”(具体业务)。职责清晰,天下太平。
下次当你面对一段复杂的、有顺序、有回滚的业务逻辑时,请暂时忘记那些沉重的工作流框架。静下心来,问问自己:这,是不是只是一场需要一个“正向”和一个“反向”导游的“旅行”?
更多推荐
所有评论(0)