重放一张旧单的判定

建单时冻结输入、规则行和判据版本,规则与程序都变了,旧单仍能原样重放

一张单事后要能原样重放,SZV 的做法是建单时把单据输入、命中的规则行和判据版本冻结在单上,规则表与程序之后怎么变,都不影响它。判定本身不调用模型;模型起草的单据走的是同一套规则,没有另开的通道。判定依赖汇率、库存、风控分这类实时外部量,而这些量又写不进单据时,重放只能回答「现在会怎么判」,这套做法不适用。

重放要钉住什么

「判定是纯函数」是必要条件。纯函数指同样的输入得到同样的输出,不读外部状态。但一张旧单重放,输入的不只是这张单。

变量会因什么变怎样钉住
单据输入单据只追加,输入不变单据本身
规则行规则表追加新行审批单建单时,把命中的金额档行存在单据上
判据版本解释器随发布变化单据记下签法版本;签核位序列按签法版本分组,版本只追加,单据按冻结的版本走

签法版本,指一类单据的签核位序列的版本号。第三行最容易漏。只快照规则行,而判定函数在发版里改了含义,同一份快照也会得出不同的结果。Temporal 把同类问题放在工作流代码上:它靠重跑代码、重放事件历史恢复状态,所以代码必须对同一段历史做出同样的决定,不能读时钟、随机数和工作流之外的网络调用(Temporal 文档)。

重放只读单据上冻结的各格

图里,建单之后规则表与程序都变了,重放仍只用单据卡里的这些格,所以结果应与当时一致。重放结果与当时记下的结果逐项比对,不一致时,要查的就是存下的快照与解释器。

审计时重放一张旧单,不必还原当天的规则表和程序版本。

一张旧单怎样重放

示例:一张采购申请在金额线调整之前建单,命中旧的档位行,签核位序列是三位。调整之后,同样金额的新单命中新的档位行,只要两位。建单日的那张旧单今天重放,仍是三位,不会因为新规则「翻案」。重放不查规则表,只用单据上存下的档位行与签法版本。

重放的保证是:同一张单加同一份冻结,得到同一个结果。它不保证结果不随时间变化:同样金额的单今天新建,按新规则就得到另一个结果。

时钟与模型调用放在判定之外

判定的设计要求是:不读时钟,不读随机数,不读实时表,也不调模型;需要「现在几点」,由调用方当输入传进来。模型调用在判定之外,输出当作不可信的输入,只产出待审批的草案,见模型能碰什么 不能碰什么。

这个分法在软件设计里叫 Functional Core, Imperative Shell:核心是无副作用的判定,外壳做有副作用的事。SZV 的外壳就是唯一的写入入口:提议、检查、审批、追加、留痕,见修改规则参数的唯一入口。

两份解释器靠对拍

同一条判据在数据库和应用里各有一份解释器时,两份会分叉。SZV 的做法是用例逐键对拍:同一组输入分别交给两侧,结果必须相同。例如审批状态转移,判定写在 PostgreSQL 里,守卫触发器调用它,所以应用之外的连接写库也要过这道判定,见审批流转。

另有代码扫描,拦截业务代码里的第二份判断。扫描是启发式的,拦得住照抄的判断,证明不了语义上只剩一份。对拍也只覆盖取样的键,不能证明所有输入一致。所以「判据只有一份」说得过满,准确的说法是:重复的判据靠对拍与扫描看住。

表驱动的适用范围

同类单据共用一条处理管线,差异放在数据里。一类单据在单型注册表里占一行:单据号前缀、签核位序列、金额线引用、时限、批准后执行写入的那一步。每条规则写「何时」「位序列」「是否先审」,按序首条命中,最后一条必须是无条件默认,所以每张单总能找到一条规则可走。这相当于 DMN 决策表的 FIRST 命中策略(Camunda 文档)。

状态也是注册表里的行:加一种状态是加一行,全表恰有一个处于可裁决阶段的状态。

这是把差异放进数据、由一个解释器按名字取行的做法,不是函数式意义上的函数组合。它成立的前提是:

  • 新情况与旧情况同形。差旅报销若要按城市分级,规则行的形状要多一列,这是改代码,要发版。
  • 判据与签核位的种类已有。新种类要给解释器加解释,随版本发布,见规则写成带版本的数据。

核对一类新单据时,先看它的差异能不能写成已有判据与已有位种类的参数。能,核对新加的行即可。

限制与不适用

  • 每张单带一份快照,旧版本的判据要一直保留。
  • 不表达追溯更正:旧行保留,追加新行。
  • 判定依赖实时外部量(汇率、库存、风控分)时,这些量必须当输入并入快照;并不进去,重放只能回答「现在会怎么判」。
  • 新判据种类与新位种类要发版。

与事件溯源、工作流引擎对照

Temporal事件溯源SZV
重放什么工作流代码,对着事件历史重跑事件序列,重放得到状态一张单的一次判定
靠什么保证一致代码必须是确定的事件不可改冻结的输入、规则行与判据版本
追溯更正—要引入双时间不表达;旧行保留,追加新行

SZV 把等待状态与判定依据存成单据上的数据,和规则表放在同一个 PostgreSQL 库里,不重放事件流来恢复状态。

其他做法

做法它解决什么为什么不用
重放时查当前规则表不用在单据上存快照只能回答「现在会怎么判」,答不了「当时为什么这么判」
只存规则行,不钉判据版本快照小判据随发布变化,同一份快照得出不同结果

验收时可以问的问题

  • 请演示:重放一张规则调整之前建的旧单,把结果与当时记下的结果逐项比对。
  • 请现场查这张旧单上冻结的金额档行与签法版本。
  • 请给出库内与应用内两份解释器的对拍用例清单。

相关页

这页有帮助吗谢谢,我们会据此改进这一页。

需要帮助?联系我们

想先知道我们怎么想?关于 SZV

联系我们

企业人员规模(人)

试用工作台