BLOGE
0.9.9-RC1在线版 · 编制 2026-11-02 · English
附录 J —— BLOGE 编码重构手册
代码可以很短,却把重要决定藏起来:表达式里查了一次远端,fallback 把超时伪装成低分,或者一句「批准」同时发款和通知。本手册从这些具体症状出发,说明应该选择哪种 BLOGE 结构,以及怎样验证改写没有改变业务承诺。
方法与可运行工作坊核对 BLOGE 0.9.9-RC1 候选源码。它们不是引擎强制执行的通用规范,也不代表候选制品已经公开发布。首次阅读按正文前进;遇到设计判断时按 BP 编号查阅;第 34 章把这些选择串成一次完整重构。
先选择表达,再选择维护范围
| 主要责任 | 首选表达 | 需要保留的边界 |
|---|---|---|
| 读取或重命名已有字段 | input binding | 数据来源显式 |
| 通用、轻量的值计算 | 已注册 built-in function | 类型、null、数值与 locale 语义 |
| 多字段命名投影 | transform | 不把外部动作藏进投影 |
| 有界计算需要业务名称 | helper;需要 DSL 复用时注册 typed function | 参数显式、可独立测试 |
| 多个事实共同决定结果 | decision_table 或领域方法 | 重叠、遗漏、优先级 |
| 选择下一条执行路径 | branch | 只消费决定,不重复计算政策 |
| 需要独立调用和控制的能力 | Operator | 输入输出、失败、成本与 effect |
| 复用多步业务片段 | subgraph | 明确输入输出与唯一所有者 |
纯计算不一定便宜。一个耗时较长、需要独立隔离的算法也可以使用 Operator;一个只在内部调用的 helper 不必因为有名字就发布为 DSL function。
| 维护范围 | 合适内容 | 不足以扩大范围的理由 |
|---|---|---|
| 当前应用 | 本企业政策、业务适配 | 「其他团队也许用得上」 |
| 团队或领域库 | 多个消费者共享同一契约 | 复制次数多,但政策所有者不同 |
| 可选模块 | 协议、SDK、专属接线 | 为安装方便把供应商依赖放入 core |
| BLOGE 核心 | 稳定、跨领域的基础能力 | 仅仅少写一行表达式 |
BP-01
通用值计算先查既有函数
症状: 为 trim、集合去重或字段拼接增加专用 Operator。
改法与原理: 先查实际 runtime 的 registry/catalog,再核对语义。单个字段用 binding;相关投影需要命名时用 transform。已有 trim、distinct 能表达意图时,没有必要增加一个可重试节点。投影仍需求值,不能称为零成本。
验证: 检查 null、空集合、顺序和错误类型。concat 把 null 当空串,不等于满足姓名必填要求;通用 round 不等于货币政策中的舍入。
例外: now()、today()、uuid() 也是内置函数,却与执行环境有关;不能假定函数都纯或跨执行不变。时间和 ID 从执行服务取得,也不能据此假定重试后值不变。
BP-02
为有界计算命名,保留业务参数
症状: 多层嵌套表达式需要解释执行顺序;同一计算的修复要改多个位置。
改法与原理: 先提取普通领域方法。只有 graph 作者需要调用时,再通过 @BlogeFunction、@BlogeFunctionLibrary 注册 typed function。用 JavaDoc 说明输入、结果和错误,用注解声明 effect 与确定性;注解不是运行时行为证明。
工作坊的 loanText.normalizePolicyCode 是项目新增函数,不是 BLOGE 内置 API。它拒绝空代码,以 Locale.ROOT 规范大小写;这份合同不同于依赖默认 locale 的通用转换。
验证: 工作坊包含完整类、注册和 DSL 调用;测试土耳其语默认 locale、null、空白、catalog 名称和投影结果。
例外: 一次 trim 已足够清楚时不抽取函数;只 供 Operator 内部使用的方法不必发布到 DSL。registry 允许同名替换,因此新库应有明确前缀并检查冲突。
BP-03
独立执行责任需要 Operator 边界
症状: customer.currentCredit(id) 在绑定中读远端;一次函数调用既查询又写库;纯算法耗时很长,却与绑定求值混在一起。
改法与原理: 当调用需要独立 timeout、retry、替换、观测或容量策略时,优先使用 Operator。例如显式 CreditScore node,后续政策只读 credit.output.score。一个 Operator 承担一项可说明的能力,不把审批、发款、通知捆成一次失败边界。
验证: 分开观察函数调用、Operator 调用和真正的外部 effect。使用 EffectPort 时经 invoke 调用,并验证实际 REAL/DOUBLE/DENY 接线;单独建一个 node 不会自动截获任意内部 HTTP/JDBC 调用。
例外: 实例函数库支持 repository/client 和 READ_EXTERNAL,所以 I/O 用 Operator 是默认设计建议,不是语法限制。保留外部读取函数时,应说明重复求值、一致性和控制需求。反过来,没有副作用的大计算也可能需要 Operator。
BP-04
公共函数先证明语义缺口
症状: 一个企业规则被建议加入 core;为少写一层组合而新增公共函数。
改法与原理: 项目注册、团队函数模块、默认 built-in 是不同决定。公共提案应包含不同业务语境中的同一语义、现有组合的具体不足、完整类型/null/错误/顺序/数值合同、成本边界、维护者和兼容方案。特定 SDK 或客户凭证通常应进入可选模块。
实例: distinct 已存在;「按业务键保留首项」可以成为项目探索,但 distinctBy 在此只是候选名称。提案必须先说明 null 键、首末项、相等性和顺序,不能先把未实现语法写进生产 DSL。
验证: 至少用真实消费者的不同输入检验同一合同,并回归已有函数调用。修复违反既有合同的实现与改变合同是两回事;修改 null、舍入或返回形状需要明确迁移。
例外: 某银行准入评分有政策所有者与版本,适合领域代码或决策表。它不因被调用很多次就变成通用 built-in。
BP-05
扩展 Operator 先区分配置、适配和新动作
| 变化 | 优先改法 | 订单映射 |
|---|---|---|
| 地址、凭证、接线变化 | 配置或实例装配 | 同一支付服务的不同环境 |
| 供应商协议变化 | 窄 port/adapter、可选依赖 | 保留授权合同,替换提供方 |
| 业务保证或结果变化 | 新能力或显式版本 | 支付授权与扣款分别建模 |
| 多项能力按顺序完成 | graph/subgraph | 扣款、登记、通知 |
| 只改变字段形状 | binding/transform | 映射提供方返回 |
症状: UniversalBusinessOperator(mode, payload) 同时承担查询、审批和扣款。
改法与原理: 让能力名称和合同保持一致。公共 Operator 提案先给出 schema、effect、错误、幂等、维护者及真实依赖合同;供应商连接器可单独发布,不必进入 core。
验证与例外: 供应商 adapter 可复用同一能力,但不能把授权和实际扣款仅作为一个模式参数。默认 UNKNOWN/MIXED 不表示已具备安全重试保证。
BP-06
多事实决策显式列规则,路由只消费结果
症状: 超过 3 个独立判断维度共同决定一个结果;改一条政策需要追踪多层 if。
改法与原理: 先列事实、输出和规则优先级,再评估决策表。branch 消费政策结果,不在分支里重新算政策。「超过 3 个」是审查信号,不是引擎阈值:四个枚举值的路由仍可直接用 branch。
贷款 starter 的拒绝条件是黑名单命中、信用低于 600、欺诈分至少 80 或收入 weak;否则信用至少 740、欺诈低于 20 且收入 strong 时批准,其余人工复核。第 34 章把这个决定迁到 decision_table,保留七个结果字段和三路行为。
验证: 比较固定 CSV 标准答案、重构前后输出、metadata、SKIPPED 和模拟动作计数。短领域方法本来清楚且政策稳定时,保留它也是合格的设计结果。
例外: 复杂算法和递归不适合塞进规则行;无必要的简单 guard 不必各建一张表。