11 KiB
BatchRunner:跨模块批量编排执行器 Spec
Status: resolved
2026-08-20 执行完毕:单步一票交付落地并验证(全模块 9 模块 BUILD SUCCESS、BOM 0、变更限定 crm-base/crm-lead/crm-rule + docs/adr/0028 + CONTEXT.md ×3 + 本 spec)。 落地形态:crm-base
domain.result.BatchRunner(静态run(ids, op, bizFail, unknownFail),54 行)+BatchRunnerTest(5 用例锁泛型协议); crm-lead 8 个批量方法一行委派 +runBatch私有方法删除 +LeadBatchFailItem.bizOf/unknownOf;crm-ruledeletePoolBatch19 行内联 → 1 行 +PoolBatchFailItem.bizOf/unknownOf。 测试对账:crm-base 33 + 5 = 38(GlobalDataBindingAdviceTest 4 + BatchResultTest 4 + BatchRunnerTest 5 + DataVisibilityTest 20 + PageConverterTest 5);crm-lead 116 不变;crm-rule 30 不变。 两侧存量测试零改动(LeadBatchServiceTest 7 / LeadPoolBatchDeleteTest 4 / LeadPoolServiceImplTest P0-1)——自动升级为真实执行器端到端锁;两个 ServiceImpl 构造器零变化(11-null / 6-null 不动,红线达成)。 过程插曲:召回了一条错误记忆(声称本票已完成)——经文件系统验证为过早完成态幻觉,已纠正后开工。
第三轮架构审查(2026-08-20)候选 ③「批量操作编排重复」(Strong)。把「逐条委派 + 失败翻译收集 + 结果组装」的批量编排皮从 crm-lead
LeadServiceImpl.runBatch(私有)与 crm-ruleLeadPoolServiceImpl.deletePoolBatch(内联展开)两处同构实现收进 crm-base 的泛型静态执行器BatchRunner。纯重构:HTTP 契约、失败映射真值、self-injection 事务契约零变化。域:crm-base(产出)+ crm-lead / crm-rule(两侧接线)。前置:ADR-0023(批量操作 D1/D2/D5——BatchResult 骨架 + 各域自持失败项 + 依赖方向约束)。本 spec 只重构编排知识的归属,不新增业务。 单步一票交付,随票落 ADR-0028 + 三处 CONTEXT.md 增量。
Problem Statement
作为维护者,批量编排(空判 → 逐条委派 self 代理 → catch 业务异常按 code 翻译 → catch 未知异常 UNKNOWN 兜底 → 结果组装)有两份同构实现:
- crm-lead
LeadServiceImpl.runBatch(私有,19 行):8 个批量方法一行委派它(claim / assignToPool / assignToUser / release / activate / delete / follow / unfollow)。 - crm-rule
LeadPoolServiceImpl.deletePoolBatch(内联展开,19 行):同一 try-catch 结构逐字重复,差异仅类型(PoolBatchFailItem/PoolBatchFailReason)与 log 文案。
后果:
- P1-6 模式本体:改兜底行为 / 映射结构要人肉排查两个模块,两份实现会漂移。
- ADR-0023 D5 已预言「此模式作为后续任何模块批量接口的范式」——但范式没有执行器载体,后续域(商机)只能再抄第三份。
- runBatch 住在大类里(
LeadServiceImpl633 行),批量测试靠new LeadServiceImpl(11 个 null)+setField(self)脆弱构造。
事实修正(相对最初指令):Controller 侧无编排逻辑(纯 Result.success 透传);批量不直接调 CAS 守卫(守卫在单条方法与状态机内),收的是「逐条委派 + 失败收集 + 结果组装」编排皮;指令的 com.crm.lead.batch 收不齐跨模块重复(ADR-0023 D5 依赖方向禁止 crm-rule→crm-lead)。
Solution
作为开发者,我希望 crm-base 提供泛型静态执行器,使:
package com.crm.base.domain.result;
public final class BatchRunner {
public static <F> BatchResult<F> run(List<Long> ids, Consumer<Long> op,
BiFunction<Long, BusinessErrorException, F> bizFail,
BiFunction<Long, Exception, F> unknownFail) {
// 空判 → for → op.accept → addSuccess
// catch BusinessErrorException → bizFail.apply(id, e) → addFailure
// catch Exception → log + unknownFail.apply(id, e) → addFailure(不中断后续)
}
}
调用方(crm-lead,8 个方法形态统一):
public BatchResult<LeadBatchFailItem> claimBatch(List<Long> ids) {
return BatchRunner.run(ids, self::claimLead, LeadBatchFailItem::bizOf, LeadBatchFailItem::unknownOf);
}
翻译工厂落各域失败项类(域内知识,兜底文案自持):
// LeadBatchFailItem(crm-lead)
public static LeadBatchFailItem bizOf(Long id, BusinessErrorException e) {
return of(id, LeadBatchFailReason.fromCode(e.getCode()), e.getMessage());
}
public static LeadBatchFailItem unknownOf(Long id, Exception e) {
return of(id, LeadBatchFailReason.UNKNOWN, "操作失败");
}
// PoolBatchFailItem(crm-rule)同款,兜底文案「删除失败」
User Stories
- 作为 crm-base 维护者,我希望
BatchRunner.run独家提供「逐条委派 + 失败收集 + 结果组装」编排,以便批量协议(结果骨架 + 执行器)在domain.result一个包讲完。 - 作为 crm-lead 维护者,我希望 8 个批量方法全部一行委派
BatchRunner.run(ids, self::xxx, LeadBatchFailItem::bizOf, LeadBatchFailItem::unknownOf),以便runBatch私有方法删除、LeadServiceImpl瘦约 57 行、构造器零变化。 - 作为 crm-rule 维护者,我希望
deletePoolBatch的 19 行内联编排缩成一行委派(翻译工厂换PoolBatchFailItem),以便 crm-rule 不再持有第二份编排实现。 - 作为两个域的维护者,我希望 code→reason 映射与兜底文案留在各自失败项类(
bizOf/unknownOf静态工厂),以便 ADR-0023 D5「各域自持失败语义」原样成立。 - 作为调用方,我希望
BatchRunner是静态方法而非 bean,以便两个 ServiceImpl 构造器零变化、现有测试的 mock-self 模式原样保留。 - 作为后续域(商机)维护者,我希望将来的批量接口直接
BatchRunner.run(...)+ 自建失败项,以便第三份编排实现永远不会诞生。 - 作为维护者,我希望 self-injection(
@Lazyself 走 AOP 每条独立事务)留守两侧 service,以便 ADR-0023 D1 事务契约不动。 - 作为测试维护者,我希望两侧现有批量测试(
LeadBatchServiceTest7 用例 +LeadPoolBatchDeleteTest4 用例 +LeadPoolServiceImplTestP0-1)一行不动,以便它们自动升级为「真实静态执行器 + 域翻译工厂」的端到端锁(HTTP 契约直接投影)。 - 作为 crm-base 测试维护者,我希望新建
BatchRunnerTest以简单 F(String)锁泛型编排协议——空 ids 不触碰 op、bizFail 收(id, e)、unknownFail 兜底、单条异常不中断后续、total = successCount + failCount——以便协议行为与域无关地锁定。 - 作为维护者,我希望对外 HTTP 契约(9 个
-batch端点 URL/入参/返回结构)、失败映射真值、BatchResult结构全部不变,以便这是一次可对账的纯重构。 - 作为维护者,我希望执行器内 log 统一通用文案(如「批量操作单条异常:id={}」),以便 log 不再各域重复——这是唯一可接受的行为微调(非契约)。
Implementation Decisions
- 落位:
com.crm.base.domain.result.BatchRunner(与BatchResult同包——批量协议家族);静态 final 工具类、私有构造器;非 bean(零依赖无状态——一个 adapter 是假 seam,无第二实现预期)。 - 签名:
run(ids, op, bizFail, unknownFail)四参数;bizFail = (Long, BusinessErrorException) -> F、unknownFail = (Long, Exception) -> F。不造 BatchAction record——op 每调用点必变,打包零复利(推翻最初指令的 record 预期,如实记录)。 - 翻译工厂归属:各域失败项类加
bizOf/unknownOf静态工厂;兜底文案(lead「操作失败」/ pool「删除失败」)由域自持——行为零变化。 - 方法名:
run(runBatch的自然延续,读作BatchRunner.run)。 - replace 不 layer:
runBatch私有方法删除、deletePoolBatch内联编排删除,不留旧路径。 - 单步一票:改动面(crm-base 2 新文件 + crm-lead 2 类小改 + crm-rule 2 类小改 + 0 个存量测试改造)小于 LeadDeadlines 单步先例;crm-base 新增与两侧接线强耦合(建了不接线 = dead code 入库),无有意义的中间态。
- 交付物:本票代码 +
docs/adr/0028-batch-runner-unified-executor.md+ 三处 CONTEXT.md 增量(crm-base「批量执行器」新词条、crm-lead「批量结果」词条更新、crm-rule 批量新词条)。 - 与 ADR-0023 的关系:D2「BatchResult 放 crm-base 保持非业务纯净」原样成立——BatchRunner 泛型无业务枚举依赖(映射经函数参数注入);D5「各域自持失败语义」原样成立——枚举/失败项不动、只加翻译工厂;D1 事务契约不动(self 留调用方)。
Testing Decisions
- 两侧现有测试零改动:静态执行器被真实穿过;它们锁的「code→reason→failures 结构穿全链路」恰是 HTTP 契约的直接投影。不套用候选 ② P1-6 平移先例——那是因为被测对象换家,本轮 service 方法没换家。
- 新建
BatchRunnerTest(crm-base,与BatchResultTest同包):String 作 F 锁协议矩阵——空/null ids 不触碰 op 且空结果、全成功计数、bizFail 收(id, e)产出失败项、未知异常走 unknownFail 且不中断后续、total 组装。 - 对账公式(写进执行记录):crm-lead 116 不变;crm-rule 不变;crm-base 基线 +
BatchRunnerTest新增约 5-6(基线用例数跑时确认)。 - prior art:
BatchResultTest(crm-base 测试先例)、LeadBatchServiceTest(mock self 模式)、LeadPoolBatchDeleteTest(同构)。
Out of Scope
- 两侧 ServiceImpl 的 self-injection / 构造器 / 其他方法。
LeadBatchFailReason/PoolBatchFailReason枚举与fromCode映射逻辑。BatchResult结构与 crm-base 其他通用件。- HTTP 契约、错误码、DB schema、Redis。
- 第三轮审查候选 ④(V-CONFIG)。
LeadServiceImpl11-null 测试构造的治理(另一重构主题——本轮静态方法红利只是不再恶化)。- 商机模块批量接口(尚不存在,将来复用)。
Further Notes
- 预防项:两个 ServiceImpl 构造器预期零变化——diff 出现构造器改动即为偏离信号;新文件 BOM 扫描;测试名跟被测类名
BatchRunnerTest;mvn-s settings.xml且输出落盘再读(-q吞输出)。 - 收尾统一:crm-base + crm-lead + crm-rule 三模块 mvn test(或直接全模块)→ BOM 扫描 → git 变更清单核对(预期限定 crm-base / crm-lead / crm-rule +
docs/adr/0028+ CONTEXT.md ×3 +.scratch/batch-runner-module/)。 - 事实勘察修正记录(相对最初指令):Controller 无编排逻辑;「逐条 CAS 守卫调用」实为「逐条委派 self 单条方法」(守卫在单条方法与状态机内);
com.crm.lead.batch因依赖方向收不齐跨模块重复。