Bond Polymorphic Orchestration 模块深度解析
1. 问题空间与设计意图
想象一下,你正在管理一个复杂的软件项目,需要将不同的工作单元组合起来:有时你想把两个预定义的工作流程模板合并成一个新模板;有时你需要将一个模板实例化并附加到正在进行的工作流中;有时你只是想把两个正在进行的任务连接起来。这就是 bond_polymorphic_orchestration 模块要解决的核心问题。
为什么需要这个模块?
在项目管理和工作流自动化中,我们经常遇到以下场景:
- 模板组合:将两个工作流模板(protos)合并成一个更复杂的模板
- 模板实例化:将一个模板实例化并附加到现有的工作流(molecule)中
- 工作流连接:将两个正在进行的工作流连接起来,形成一个更大的工作流
一个简单的解决方案是为每种情况编写单独的命令,但这样会导致代码重复和用户界面不一致。bond_polymorphic_orchestration 模块通过多态设计,用一个统一的 bond 命令处理所有这些场景,同时保持了灵活性和表达力。
2. 核心心智模型
关键抽象
这个模块的核心是多态调度的概念,我们可以将其想象为一个"智能连接器":
- 操作数类型检测:首先确定两个操作数的类型(proto 或 molecule)
- 策略选择:根据操作数类型组合选择相应的 bonding 策略
- 执行与适配:执行选定的策略,并根据用户提供的选项进行适配
类比:乐高积木拼接
你可以把这个模块想象成一个智能的乐高积木拼接器:
- Protos 是预定义的积木套装(模板)
- Molecules 是已经拼好的积木结构(实例化的工作流)
- Bonding 是将这些积木拼接在一起的过程
拼接器会根据你提供的积木类型(是套装还是已拼好的结构)自动选择最合适的拼接方式,同时还允许你指定拼接的类型(顺序、并行、条件)和其他属性。
3. 架构与数据流
核心架构
┌─────────────────────────────────────────────────────────────┐
│ molBondCmd │
│ (Cobra Command) │
└────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ runMolBond │
│ (多态调度入口函数) │
└─────────────┬─────────────────────────┬──────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ resolveOrCookToSubgraph│ │ 操作数类型检测与调度 │
└──────────┬───────────┘ └───────────┬──────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ 公式解析与模板加载 │ │ bondProtoProto │
│ │ │ bondProtoMol │
│ │ │ bondMolProto │
│ │ │ bondMolMol │
└──────────────────────┘ └───────────┬──────────────┘
│
▼
┌──────────────────┐
│ BondResult │
└──────────────────┘
数据流程详解
让我们通过一个典型的使用场景来追踪数据流:
- 命令解析:用户执行
bd mol bond protoA molB --type parallel - 参数验证:
runMolBond验证参数和标志的有效性 - 操作数解析:
- 对
protoA:调用resolveOrCookToSubgraph加载模板子图 - 对
molB:调用resolveOrCookToSubgraph加载现有分子
- 对
- 类型检测:确定
protoA是 proto,molB是 molecule - 策略调度:选择
bondProtoMol策略 - 执行策略:
- 加载 proto 的完整子图
- 检查必要的变量
- 确定 ephemeral 标志
- 构建克隆选项
- 调用
spawnMoleculeWithOptions实例化并附加
- 结果返回:返回
BondResult结构,包含操作结果信息
4. 核心组件深度解析
4.1 BondResult 结构
type BondResult struct {
ResultID string `json:"result_id"`
ResultType string `json:"result_type"` // "compound_proto" 或 "compound_molecule"
BondType string `json:"bond_type"`
Spawned int `json:"spawned,omitempty"` // 生成的问题数量
IDMapping map[string]string `json:"id_mapping,omitempty"` // 旧ID到新ID的映射
}
设计意图:
- 提供统一的结果格式,无论使用哪种 bonding 策略
- 包含足够的信息供机器解析(JSON 输出)和人类阅读
IDMapping允许调用者跟踪从模板到实例的 ID 转换
4.2 runMolBond 函数
这是模块的核心调度函数,负责:
- 参数解析和验证
- 操作数解析(通过
resolveOrCookToSubgraph) - 操作数类型检测
- 根据类型组合调度到相应的 bonding 函数
关键设计决策:
- 使用
resolveOrCookToSubgraph统一处理 issue ID 和公式名称 - 通过类型检测实现多态调度,而不是使用接口或继承
- 支持 dry-run 模式,允许用户预览操作结果
4.3 四种 Bonding 策略
bondProtoProto:proto + proto → compound proto
用途:将两个模板合并成一个新的复合模板
实现细节:
- 创建一个新的 root issue 作为复合模板
- 添加两个原 proto 作为子项
- 根据 bond type 添加适当的依赖关系
- 始终创建持久化的模板(Ephemeral=false)
设计权衡:
- 选择创建新的 root issue 而不是修改现有 proto,保持了原 proto 的不变性
- 复合 proto 作为一个整体,可以像其他 proto 一样被实例化
bondProtoMol:proto + molecule → spawn + attach
用途:实例化一个模板并将其附加到现有工作流
实现细节:
- 加载 proto 的完整子图
- 检查必要的变量
- 根据目标 molecule 或用户标志确定 ephemeral 状态
- 使用
spawnMoleculeWithOptions原子性地实例化和附加 - 支持动态 ID 生成(通过
--ref标志)
设计亮点:
- 通过
AttachToID选项确保实例化和附加在单个事务中完成 - 支持变量替换和动态 ID 生成,实现"圣诞装饰"模式
bondMolProto:molecule + proto → spawn + attach(对称)
用途:与 bondProtoMol 相同,但操作数顺序相反
实现细节:
- 简单地调用
bondProtoMol,交换参数顺序 - 保持 API 的对称性,提高用户体验
bondMolMol:molecule + molecule → compound molecule
用途:将两个现有工作流连接起来
实现细节:
- 在两个 molecule 之间创建依赖关系
- 根据 bond type 选择适当的依赖类型
- 不创建新的 issue,只是修改现有 issue 之间的关系
设计权衡:
- 选择修改依赖关系而不是创建新的 root issue,保持了原 molecule 的身份
- 依赖于存储层的约束(每个 (issue_id, depends_on_id) 对只能有一个依赖)
4.4 resolveOrCookToSubgraph 函数
用途:统一处理 issue ID 和公式名称,将它们解析为子图
实现细节:
- 首先尝试解析为 issue ID
- 如果失败,检查是否看起来像公式名称
- 如果是公式,将其内联烹饪为内存子图(不存储在数据库中)
- 返回子图和一个标志,表示是否是从公式烹饪而来
设计亮点:
- 实现了 gt-4v1eo 需求:公式被烹饪为内存子图,不污染数据库
- 支持步骤条件过滤(通过 vars 参数)
- 统一了 issue 和公式的处理,简化了上层逻辑
5. 依赖关系分析
5.1 核心依赖
这个模块依赖于几个关键组件:
- formula 包:用于解析和烹饪公式
- storage 包:特别是
dolt.DoltStore,用于数据持久化 - types 包:提供核心数据类型,如
Issue、Dependency等 - utils 包:提供
ResolvePartialID等工具函数 - cobra 包:用于命令行界面
5.2 被依赖情况
这个模块主要被 CLI 层调用,是用户通过 bd mol bond 命令直接交互的接口。
5.3 数据契约
输入契约:
- 两个操作数,可以是 issue ID 或公式名称
- 各种选项标志,如
--type、--ephemeral、--ref等
输出契约:
BondResult结构,包含操作结果信息- 或者在错误情况下,返回相应的错误信息
6. 设计决策与权衡
6.1 多态调度 vs 接口继承
选择:使用类型检测和函数调度,而不是接口继承
原因:
- 操作数类型组合有限(只有 4 种)
- 函数调度更直观,更容易追踪和调试
- 避免了为每种类型创建单独的结构体和方法
权衡:
- 失去了一些面向对象的优雅
- 但获得了更好的可理解性和可维护性
6.2 内存子图 vs 数据库存储
选择:公式被烹饪为内存子图,不存储在数据库中
原因:
- 公式是临时的,不需要持久化
- 避免了数据库污染
- 提高了性能,避免了不必要的数据库操作
权衡:
- 内存子图在操作完成后就消失了
- 但这正是我们想要的行为
6.3 原子性操作 vs 分步操作
选择:使用 AttachToID 选项确保实例化和附加在单个事务中完成
原因:
- 避免了部分完成的状态
- 提高了数据一致性
- 简化了错误处理
权衡:
- 事务可能会更大,锁定时间更长
- 但在这个场景下,这是可以接受的
6.4 显式标志 vs 隐式行为
选择:提供显式标志(如 --ephemeral 和 --pour),同时有合理的默认行为
原因:
- 给用户提供了控制权
- 合理的默认行为使常见情况更简单
- 显式标志使意图更清晰
权衡:
- 增加了 API 的复杂性
- 但提高了灵活性和表达力
7. 使用指南与示例
7.1 基本用法
# 顺序连接两个 proto
bd mol bond protoA protoB
# 并行连接两个 molecule
bd mol bond molA molB --type parallel
# 条件连接 proto 和 molecule
bd mol bond protoA molB --type conditional
7.2 高级选项
# 使用自定义标题创建复合 proto
bd mol bond protoA protoB --as "My Compound Proto"
# 强制生成持久化的实例
bd mol bond protoA molB --pour
# 强制生成临时的实例
bd mol bond protoA molB --ephemeral
# 使用动态 ID 生成
bd mol bond protoA molB --ref "arm-{{name}}" --var name=ace
7.3 常见模式
模式 1:创建复合模板
bd mol bond mol-feature mol-deploy --as "Feature + Deploy"
这会创建一个新的复合模板,可以像其他模板一样被实例化。
模式 2:附加模板到现有工作流
bd mol bond mol-test bd-abc123 --pour
这会实例化 mol-test 模板并将其附加到 bd-abc123 工作流中,强制生成持久化的实例。
模式 3:动态 ID 生成
bd mol bond mol-arm bd-patrol --ref "arm-{{name}}" --var name=ace
这会创建一个 ID 为 bd-patrol.arm-ace 的实例,而不是随机哈希。
8. 边缘情况与注意事项
8.1 变量替换
- 当使用
--ref标志时,确保提供所有必要的变量 - 未提供的变量会导致错误
- 变量名区分大小写
8.2 Ephemeral 标志
--ephemeral和--pour不能同时使用- 如果都不使用,会遵循目标的 ephemeral 状态
- Ephemeral issue 不会被导出到 JSONL
8.3 Bond 类型
- 顺序连接:B 在 A 完成后运行(无论结果如何)
- 条件连接:B 只在 A 失败时运行
- 并行连接:B 和 A 同时运行,没有阻塞关系
8.4 公式名称
- 公式名称通常以 "mol-" 开头
- 也可以是包含 ".formula" 的文件路径
- 公式被烹饪为内存子图,不会存储在数据库中
9. 总结
bond_polymorphic_orchestration 模块是一个优雅的多态设计示例,它通过统一的接口处理多种不同的 bonding 场景。它的核心优势在于:
- 多态调度:根据操作数类型自动选择合适的策略
- 内存子图:公式被烹饪为内存子图,避免数据库污染
- 原子操作:确保实例化和附加在单个事务中完成
- 灵活选项:提供丰富的选项,满足各种需求
这个模块展示了如何在保持简单性的同时实现强大的功能,是项目管理和工作流自动化领域的一个重要组成部分。