持久化执行前置课(十):画出最小持久化内核,再开始写执行器
上一篇为事件与逻辑加上版本契约旧运行终于能够跨部署恢复。现在可以收束九篇原则稳定身份标记同一件事追加事件保存事实纯决定产生命令发件箱投递意图幂等回执约束副作用租约与预算控制恢复。一、痛点组件齐全仍可能边界混乱团队很容易先选数据库、队列和编排框架再把业务判断塞进 worker 循环。结果是历史读取、网络调用、状态推进和异常重试相互嵌套任何一步都无法独立重放。最小内核不是功能最少的玩具而是职责最少且边界可验证的结构。二、原理一个确定性核两个持久化方向内核读取事件历史折叠出状态再根据新事件产生新状态和命令。向内只追加事件向外只写命令意图活动执行器把命令变成回执事件。下面用纯内存数据展示一轮循环刻意不在决定函数里执行命令。fromdataclassesimportdataclass,replacedataclass(frozenTrue)classState:phase:strnewtoken:str|NoneNonedataclass(frozenTrue)classMessage:kind:strdata:dictdefdecide(state:State,event:Message)-tuple[State,list[Message]]:ifstate.phasenewandevent.kindtrip_requested:returnreplace(state,phaselocking),[Message(lock_budget,{activity_id:act-1})]ifstate.phaselockingandevent.kindbudget_locked:returnreplace(state,phaseready,tokenevent.data[token]),[]raiseValueError(finvalid:{state.phase}{event.kind})stateState()history[]outbox[]foreventin[Message(trip_requested,{}),Message(budget_locked,{token:B-7}),]:state,commandsdecide(state,event)history.append(event)outbox.extend(commands)print(event.kind,,state.phase,[item.kindforitemincommands])print(history,len(history),outbox,len(outbox),token,state.token)输出trip_requested locking [lock_budget] budget_locked ready [] history 2 outbox 1 token B-7三、实现用不变量先约束存储接口在选择框架前先写存储端口的契约测试序号连续、期望版本冲突、事件与命令同事务、命令 ID 唯一、完成回执引用已有命令。示例验证最核心的守恒关系任何阶段都不能凭空出现。fromdataclassesimportdataclassdataclass(frozenTrue)classRecord:seq:intevent:strcommands:tuple[str,...]defvalidate(records:list[Record],completed:set[str])-None:knownset()forexpected,recordinenumerate(records,start1):ifrecord.seq!expected:raiseValueError(sequence_gap)forcommandinrecord.commands:ifcommandinknown:raiseValueError(duplicate_command)known.add(command)missingcompleted-knownifmissing:raiseValueError(forphan_receipt:{sorted(missing)})records[Record(1,trip_requested,(act-1,)),Record(2,budget_locked,(act-2,)),Record(3,flight_booked,()),]validate(records,{act-1,act-2})print(valid records,len(records))try:validate(records,{act-3})exceptValueErroraserror:print(caught,error)输出valid records 3 caught orphan_receipt:[act-3]四、踩坑一开始就追求分布式规模单机 SQLite 足以验证事务边界、重放和崩溃语义。过早引入多分区队列会把业务错误藏在基础设施噪声里。另一个坑是把快照当事实源快照只是缓存删除后必须能由事件重建。监控指标同样不能代替事件指标可采样、可丢失不适合裁决业务状态。最小内核也要从第一天保存模式版本、逻辑版本、活动 ID 和停止原因否则以后补字段无法解释旧记录。安全边界应默认拒绝未知事件和未知版本。人工操作通过命令进入同一事件链而不是提供一个绕过历史的“强制改状态”按钮。五、验证先证明纯核再连接数据库验收顺序应是状态转移表、历史重放、非法事件、命令稳定性然后才是数据库事务、队列重复和进程崩溃。每加入一种基础设施都保留前一层快速测试。最终从空库执行一条旅行历史关闭进程、重新打开并重放所得状态和命令摘要必须一致。至此090 的证据包不再只是任务终点它启发了持久化执行从一开始就保存可验证事实。这十篇完成了从证据消费到内核蓝图的桥接。下一篇将真正从零实现miniflow先把旅行预订工作流写成无数据库、无网络的纯状态机再逐步为它接上持久化能力。参考来源Temporal核心应用结构Martin FowlerFunctional Core, Imperative ShellSQLite事务 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《持久化执行前置课》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。