软件建模20:EventStorming
“预留已取消”后面能否直接放“退款已完成”?如果预留没有收款,退款对象不存在;即使收过款,应退金额也可能取决于取消条款。这条看似顺畅的事件链,隐藏着至少两个尚未回答的问题。
统一语言与知识消化 检查了词义如何改变模型。本篇将同一租赁材料按事件组织,再补充命令、政策与热点,运行一段可复现的合成演练。它由单人编写,没有真实业务参与者,也没有经过跨职能工作坊确认。方法的重点是共同校正理解
EventStorming 官方将它描述为协作探索复杂业务领域的灵活工作坊形式,并区分面向组织、服务和软件等不同用途。原作者 Alberto Brandolini 的书页使用“集体学习”描述其目的。不能把某次练习采用的便签数量、顺序或软件工具当成所有场景的固定流程。官方介绍、作者书页
Brandolini 在2019年 Leanpub 访谈中解释,参与者各自了解流程的一部分,先提供事件片段,再尝试排成一致的时间线;排列过程中,不同人会纠正先后关系并补充例外。他也明确提到早期博客描述的流程已经不同于其当时做法。因此,方法会演进,不能仅据早期介绍宣布一套永恒步骤。原作者访谈逐字稿
这次演练保留“用具体事件暴露解释缺口”的做法,但所有输入和规则均为教学构造。两张图使用文字标明类别,没有自创原方法的颜色规范;程序检查的是本例约定,而不是对一场 EventStorming 工作坊打分。
先写发生了什么
材料固定在2026年10月3日 UTC。请求 A 希望租 E1 的 [10,12),获得确认并登记一百元收款。请求 B 希望租同一设备的 [11,13),因重叠被拒绝。随后取消 A,释放资源,再用新请求 F 申请 [11,13)。
这些叙述要拆成能追问的事实。“客户操作”“处理订单”还没有说发生了什么;“确认预留”表达动作意图,“预留已确认”才表达处理结果。同一个命令可能因条件不同被接受或拒绝,因此事件不能只是把命令名机械改成过去式。
flowchart LR
A[09:00 A预留已确认] -->|后续记录| P[09:01 A收款已登记]
P --> B[09:02 B预留已拒绝]
B --> C[09:03 A预留已取消]
C --> R[09:03 A退款核查已开启]
R --> N[09:06 F预留已确认]
图中箭头表示这段夹具的后续记录顺序,不表示前一个事件直接导致所有后继事件。例如 B 被拒绝并不是 A 取消的充分原因。因果依据另存为命令引用,避免从图形相邻关系推断业务必然性。
时间线还省略了两次没有新状态变化的调用:九点零四分重放 A,返回原确认;九点零五分重放 B,返回原拒绝。取消保留回执,所以旧 A 不会重新占用,旧 B 也不会因为资源空闲而改为成功。两次调用存在,但没有制造第二个确认或拒绝事件。
失败分支也应留在材料中。若只保留 A、F 两次成功,读者看不到 B 为什么失败,更无法解释旧 B 在取消之后仍被拒绝。新的预留意图需要新请求编号,这项身份约定影响调用结果,却不等于设备永久不可租。区分资源条件与请求历史,可以防止把两类拒绝原因混写成一个“不可用”。
命令、政策与结果各有位置
实验输入保留命令编号、请求编号、UTC 时刻和负载。Reserve 决定预留结果,RecordPayment 登记夹具提供的收款数值,Cancel 释放活跃预留。命令编号表示本次调用,请求编号用于预留重试,两者不可合并成一个键。
| 类别 | 本例表达 | 可回答的问题 |
|---|---|---|
| 事件 | ReservationCancelled |
哪个请求已经取消 |
| 命令 | OpenRefundReview |
接下来要求系统做什么 |
| 政策 | 取消且已有收款时开启核查 | 什么条件触发后续动作 |
| 热点 | 取消是否收费、应退多少 | 哪些解释仍需确认 |
政策是本例明确采用的条件规则:观察到预留取消,若已经登记收款,就发出开启退款核查命令,成功后记录核查已开启。它既不是退款金额计算,也不是支付渠道操作。程序采用同步调用,让条件、命令和结果都能在一次运行中检查。
flowchart LR
E[事件 预留已取消] --> Q{政策 已登记收款?}
Q -->|是| C[命令 开启退款核查]
C --> R[事件 退款核查已开启]
Q -->|否| N[不自动开启核查]
H[热点 退款金额与取消费用待确认] -.-> R
X[退款完成 不能由取消推断] -.-> H
同样的取消动作在无收款夹具中只产生取消事件,不开启核查。这个反例使政策里的条件具有实际作用;如果两种输入都无条件生成退款完成,时间线即使排得整齐,也已经把未确认规则伪装成事实。
RecordPayment 也只是教学输入。实验没有连接支付系统,事件名称刻意使用“收款已登记”,而不是宣称外部资金已经到账。真实来源是否可靠、重复回调怎样去重,应成为另一组明确场景,不能靠事件名称完成验证。
热点需要问题和确认依据
本例留下两个热点:H1 询问取消费用与应退金额;H2 询问已经交付设备后是否还能直接取消。它们分别关联退款核查和取消事件。记录还包含待确认角色、需要取得的业务材料和 OPEN 状态。
| 热点 | 本次演练能证明什么 | 仍缺什么 |
|---|---|---|
| H1:退款金额 | 有收款的取消会开启核查,没有生成退款完成 | 合同条款、适用版本及金额规则确认 |
| H2:交付后取消 | 当前模型没有交付状态和取消授权 | 撤销、归还、部分履约场景确认 |
表中角色只是应参与确认的职责描述,不表示某个人已接受任务。原始材料没有提供答案时,实验继续保留问题。尤其不能因为测试全部通过,就把 OPEN 批量改成“已解决”。
前文第14篇曾采用“登记实际动作后禁止取消”的教学候选,本次复用的是共享基线的预留取消行为,没有接入实际交付。这种差异本身就是需要记录的范围问题:两份模型服务的判断不同,不能把其中一项规则未经确认地套进另一份实现。
热点也不是任意备注的存放处。若只写“退款有问题”,后续无法知道应查合同、账目还是接口。本例要求问题、确认角色与所需证据均非空;这只能检查记录可追踪,不能自动判断问题提得是否充分。
用反例检查整理后的时间线
演练器执行七条输入命令,另由政策生成一条开启核查命令,最终产生六个事件。每个事件记录自己的编号、所属请求、发生时刻和原因命令。校验器核对支持的类型、因果引用、命令允许的结果、时间顺序及先确认后取消等局部条件。
为了检查政策没有只停留在图上,实验删去“核查已开启”事件,再次校验时得到同步政策义务未完成。调换两个事件使时间倒退,会被时间顺序检查拒绝;把核查结果改成“退款已完成”,会因命令与事件不匹配而失败。
另一反例将收款事件的原因改为取消命令。即使时间线中确实存在这两个对象,类型关系仍不成立。检查引用存在和检查引用含义是两回事,单纯确认所有编号都能找到还不够。
这些校验不是通用流程正确性证明。程序只支持本例四种命令和五种事件,不检查现实时间线是否遗漏某个部门,也不从事件自动推出上下文边界。同步政策校验更不代表异步消息一定送达;本篇没有消息队列、重试投递或事件存储恢复实验。
本例所有时刻由同一夹具显式给定,排序只在这段记录内成立。多个业务同时发生时,可能只有局部先后关系,不能强迫每个事件共享一条全局顺序。实际材料若出现互相矛盾的先后描述,应先保留分歧和来源,再判断是并行流程、时间口径不同还是记录错误,而不是仅按便签位置消除矛盾。
复跑与带走未决事项
第20篇实验包 提供 `board.json`、两张图、演练器和原始输出。从解压后包含 `examples` 的目录执行:1 | |
实际环境为 Python 3.14.4,无第三方依赖。十八项检查通过时退出码为零;预期反例未被拒绝或结果不符,则非零退出。输出逐条保留 EVENT 和 HOTSPOT,可按命令编号回查输入。
1 | |
输入在 examples/software-modeling/models/20/board.json,范围在同目录 semantics.md,完整结果在 examples/software-modeling/evidence/20/output.txt。读者可以改写某个命令或删去政策结果,重新观察错误位置;修改业务假设时,也应同步修改预期和热点说明。
真正进入多人讨论时,这份时间线只能作为可质疑的草稿。需要让了解签约、预留、交付与款项的人补充缺失事实,检查哪些条件仅在特定情形成立,并为未决问题找到确认依据。当前产物证明的是这份合成模型如何执行和暴露矛盾,尚未证明真实业务已经被完整理解。






