TT Lab
开始
学习 学习路径 课程

不可撤销的变更

完成客户变更的全流程交接

在 TT Lab 中继续学习

一句话总结

客户变更的协调器必须固定已批准的输入,并把执行响应、数据库观测、报告保存和独立的补偿,作为彼此不同的结果来管理。

为什么需要它

这是太空庆典取消作业的最后一天。客户批准了取消,订单也确实变成了 cancelled。但负责人的界面上出现了错误。因为用来保存报告的磁盘出了问题。界面上有错误,是不是应该重新执行取消?是不是应该恢复原样?如果分不清是什么失败了,恢复作业本身就会变成新的事故。

前面的单元分别学习了输入边界、条件变更、审计、补偿、分块恢复、schema 共存、证据报告。这次的目标不是把那些函数再长篇写一遍,而是把已经验证过的功能当作小型库来接收,做出处理一个客户请求的协调器。新的课题是:把什么输入当作谁的批准来接受,以及在什么结果下可以再调用什么。

即使提供的库能把 SQL 处理好,如果协调器用新 ID 不停重试,或者因为报告错误而自动补偿,整个系统也是错的。部件通过测试,与把部件连起来的业务是否正确,是两回事。综合任务检查的就是这两者之间。

工作原理

1. 预览和批准不是同一个文件

预览读取特定客户的明确 ID,展示 revision 和数量。可以根据这次观测构造出执行请求的样子,但 approved 保持为 False。不能仅因为有观测到的值,就认为人批准了对这些值的变更。

只有学习者确认内容并把 approved 改成真正的 True 的文件,才作为执行请求接收。字符串 true 或整数 1 不当作批准。这个标志是这个虚构业务明确的输入契约,不是电子签名或用户认证。在真实的组织里,需要另外的体系来确认谁以什么权限批准了。能够编辑 JSON 文件,并不能证明批准人的身份。

批准之后如果订单的值变了,不会悄悄地把以前的批准更新成新值。客户看了 qty=4、revision=9 就批准,执行前一刻却改成 qty=6、revision=10 来应用,即使是同一个对象,也是不同的请求。为了减少失败而自动重新调用 preview 的代码,反而可能破坏批准的边界。

2. 把执行响应分成四种

提供的 apply 返回 True,表示这次调用确认了新变更。False 表示已经有相同 ID、相同批准内容的记录,所以没有重新应用。仅凭这一点,不能保证当前行也是想要的值。因为之后其他作业者可能改过。

Conflict 是提供的操作拒绝了批准与当前条件的冲突。除此之外的普通异常,归为结果不明。尤其是提交之后响应传递失败,即使收到了异常,数据库中的变更也可能已经存在。协调器不把异常说成失败已确认,也不无条件说成成功。

这就是为什么即使需要再次调用,也要保持同样的批准和同样的 ID。这个协调器不做自动重试,而是返回一次执行的结果和之后的读取观测。它为人或上层系统选择下一步行动提供依据,而不去猜测那个选择本身。

3. 在观测中不仅核对 ID,还核对批准内容

收到执行响应之后,用 observe 确认原批准、审计、当前行。即使保存的 change_id 相同,tenant 或 targets 不同,就是不同的批准。不允许只是个数相同的列表,也不允许同一 ID 的不同 revision 或数量。也要防止把对应于其他批准的报告,当作当前请求的结果来发布。

没有观测,或者数据库查询失败,就是 unknown。核对“是同一个批准”失败,是 mismatch。查询成功并且是同一个批准的话,原样保持前面单元的 complete、incomplete、hold 判定。不能因为执行响应是 replayed,就把 hold 改成 complete。

执行和观测并不是一个事务。这个结构是之后调查已确认作业的流程。阅读 PostgreSQL 隔离级别时,请区分一个事务的一致性与不同调用之间的当前状态。协调器执行完毕这一事实,并不会让后续写入停下来。

4. 报告失败是另一项作业的失败

执行和观测结束之后,保存报告文件。结果另外设为 saved 或 failed。saved 时也留下返回的 SHA-256,但这并不意味着对作者的认证。数据库观测即使是 complete,报告保存也可能是 failed;报告文件保存成功,其中的业务判定也可能是 hold。

只重新生成报告的 report 动作,不应该有 apply 或 undo 调用。没有理由再次修改已经取消的订单,或者把它重新变回 pending。报告错误的消息里出现了“恢复”这个词,也不会因此产生业务补偿的权限。

观测和报告材料的采集也是另外的调用。其间如果发生后续变更,先返回的 observed 与之后装进文件的判定可能不同。observed 是之前观测的结果,report=saved 是文件保存与否。不要把二者合起来主张“现在整个系统正常,而且报告也是同一时点的”。交给客户的最终内容,必须与报告文件的观测时间和范围一起阅读。

5. 补偿不是自动的错误处理器

undo 动作需要另外的 undo_id、原 change_id、不全是空白的理由、明确的批准。校验原审计,只有当前行与原变更之后立即的客户、数量、状态、revision 相符时,才执行条件补偿。如果中间有一行变了,前面行的恢复也一起回滚。

补偿不会把 revision 降回过去的值。即使回到 pending,因为是新的变更,所以版本会增加,并留下另外的审计。不删除原批准和原审计。抹掉错误的痕迹,与恢复错误的影响,是不同的作业。

补偿中的普通异常也是 uncertain。补偿结果不明确时,不要另造 undo_id 再次执行。这个协调器在补偿之后不做自动的后续作业。确认补偿回执和当前状态的流程,接续前面补偿单元的 inspect_undo 和 reconcile_undo。把自动化的范围和需要人确认的边界写进文档,也是交接的一部分。

在现场相遇的样子

错误边界划得太宽时

如果在一个 try 里放进 apply、observe、publish,并在 except 里调用 undo,就看不出是哪个阶段失败了。写入错误、读取故障、文件错误,各自需要的下一步行动不同。本实验用小函数划分边界,再合成各个阶段的结果。并不是因为捕获了异常,就有了稳定性。

psycopg 事务管理区分事务上下文和连接寿命。提供的操作会清理每次调用所拥有的连接,协调器不再用外部事务把整个请求包起来。那样包起来,下层函数的完成究竟是真正的提交,还是释放 savepoint,可能会不同。实验在已安装的 psycopg 3.2.3 上确认实际效果。

把退出码 0 误解成业务完成时

CLI 处理完请求并输出了结构化结果,就返回退出码 0。JSON 里的 execution=conflict、observed=hold、report=failed,也可能是正常的处理结果。只看代码 0 就发送成功通知的上层自动化,可能得出错误的结论。如果请求格式、文件解析本身失败,就返回代码 2 和固定的错误对象。

错误输出中不要原样放入 DSN、密码、原始异常字符串。学习材料虽然是虚构数据库,但真实的生产习惯就是在这里养成的。还要处理错误的 UTF-8、重复键、被截断的 JSON、非有限常量以及大小限制。Python JSON 文档中的 object_pairs_hook 和 parse_constant,就是把这类输入作为数据来检查的工具。不能用 eval 去执行。

让客户的下一步行动明确的 FDE

Palantir FDSE 招聘启事谈到客户定制软件和与利益相关方的协作。这个综合任务把这种要求解释为“说明客户批准了什么变更、现在确认了什么、下一步由谁决定什么的工具”,是自己设计的学习任务。并不是复现某家公司的内部流程或招聘考试。

下一项实验要做什么

把太空庆典的取消请求用 JSON 接收,并连接到预览、执行、观测、报告、独立补偿。在真实的数据库上确认过期批准被拒绝、提交之后响应丢失、报告文件失败、后续变更和补偿。最后调用真实的 CLI,分别读取退出码和业务结果。不只是正确答案,也要拒绝会造成自动重新批准、自动补偿、虚假完成的错误答案。

只使用每位学习者各自的虚构材料和一次性数据库。本课并不是完整讲授客户权限管理、批准签名、外部报告发送、分布式事务、服务器断电耐久性的课程。