Arch Day 12: Event Storming高级
Event Storming是一种通过在墙上贴便签来发现领域知识、识别业务流程和设计系统架构的协作式工作坊方法。它的核心价值不是产出文档,而是让业务专家和技术团队在同一面墙前建立共同理解——这是DDD中Ubiquitous Language落地的最有效手段。
日期: 2026-04-11 (Day 12) 阶段: 第一阶段 - 架构基础 标签: #Event-Storming #领域发现 #BigPicture #ProcessLevel #DesignLevel #引导技巧
核心概念
一句话定义
Event Storming是一种通过在墙上贴便签来发现领域知识、识别业务流程和设计系统架构的协作式工作坊方法。它的核心价值不是产出文档,而是让业务专家和技术团队在同一面墙前建立共同理解——这是DDD中Ubiquitous Language落地的最有效手段。
为什么资深架构师仍需关注
做了10年软件的人,参加过无数需求评审会。Event Storming之所以不同,是因为它解决了传统需求分析的三个致命问题:
- 业务专家被边缘化:传统方式是BA写文档→开发看文档。Event Storming让业务专家直接参与建模,不需要理解技术术语
- 知识的隐性传递:很多关键业务规则存在于业务专家的脑子里,不会写在文档中。Event Storming通过互动式讨论"逼"出这些隐性知识
- 全局视图缺失:传统需求文档是按功能/模块切割的,很难看到全局的业务流程。Event Storming把整个流程铺在一面墙上,所有人都能看到全貌
常见误区与反模式
| 误区 | 真相 | 后果 |
|---|---|---|
| "Event Storming = 画流程图" | ES是发现过程,不是记录过程;重要的是讨论 | 变成了一个人在白板上画图,其他人旁观 |
| "只需要技术人员参与" | 没有业务专家参与的ES毫无价值 | 技术人员自己想象的领域模型 |
| "一次做完整个系统" | 应该分多次workshop,每次2-3小时 | 参与者疲劳,后半段质量骤降 |
| "产出必须是完美的" | ES的产出是"足够好"的,用来指导后续讨论 | 追求完美导致进度停滞 |
| "便签颜色不重要" | 颜色是约定,必须全员理解一致 | 混乱,无法解读 |
| "ES之后直接开始编码" | ES到代码之间还需要Design Level细化 | 代码和ES脱节 |
知识点详解
知识点1: Event Storming三层方法论
Alberto Brandolini设计了三个层级的Event Storming,从宏观到微观递进:
Layer 1: Big Picture Event Storming
├── 目的: 理解整个业务域的全貌
├── 参与者: 所有利益相关者(业务+技术+管理+运营)
├── 时间: 2-4小时
├── 产出: 业务全景图 + 关键热点 + Bounded Context候选
└── 粒度: 粗(一个事件可能代表一整个子流程)
Layer 2: Process Level Event Storming
├── 目的: 深入理解一个具体的业务流程
├── 参与者: 该流程的业务专家 + 技术团队
├── 时间: 2-3小时
├── 产出: 详细流程(命令→事件→策略) + 角色识别 + 集成点
└── 粒度: 中(每个步骤一个事件)
Layer 3: Design Level Event Storming
├── 目的: 将流程映射为具体的代码结构
├── 参与者: 技术团队(可选业务专家)
├── 时间: 1-2小时
├── 产出: 聚合根 + 命令 + 事件 + 读模型的直接映射
└── 粒度: 细(每个状态变化一个事件)
知识点2: Big Picture Event Storming
步骤详解:
Phase 1: 混沌阶段 (30min)
├── 每人拿一叠橙色便签
├── 规则: "写下你知道的、在这个业务中发生的事件"
├── 事件格式: 过去式动词 ("订单已创建"、"支付已完成"、"库存已扣减")
├── 不需要顺序,贴在墙上任意位置
└── 引导者不干预,让所有人自由表达
Phase 2: 时间线排序 (30min)
├── 一起把事件按时间线排列(从左到右)
├── 讨论: "这个事件是在那个事件之前还是之后?"
├── 发现冲突: 不同人对同一流程的理解不同
├── 合并重复的事件,澄清歧义
└── 这是最有价值的阶段——分歧暴露隐性知识
Phase 3: 标记热点 (15min)
├── 用粉色/红色便签标记"热点"(Hot Spot)
├── 热点 = 不确定的地方 / 争议 / 复杂度高 / 痛点
├── 不需要立刻解决,只需要标记出来
└── 热点是后续深入讨论的候选
Phase 4: 识别外部系统和用户 (15min)
├── 用黄色便签标记"外部系统"
├── 用小人图标标记"角色/用户"
├── 问: "这个事件是谁触发的?需要哪些外部系统参与?"
└── 这帮助识别系统边界和集成点
Phase 5: 识别Bounded Context (30min)
├── 在时间线上画竖线,分割不同的"语境"
├── 同一个词在竖线两侧含义不同 → 不同的BC
├── 讨论: "这些事件是由同一个团队负责的吗?"
└── 产出: 初步的Bounded Context划分
便签颜色约定(Brandolini标准):
| 颜色 | 含义 | 格式 | 示例 |
|---|---|---|---|
| 橙色 | 领域事件(Domain Event) | 过去式 | "订单已提交" |
| 蓝色 | 命令(Command) | 祈使句 | "提交订单" |
| 黄色 | 角色/外部系统(Actor) | 名词 | "客户"、"银行API" |
| 粉色/红色 | 热点(Hot Spot) | 问题/风险 | "审批超时怎么办?" |
| 紫色 | 策略/规则(Policy) | 条件 | "金额>10万需要二级审批" |
| 绿色 | 读模型(Read Model) | 名词 | "账户余额视图" |
| 淡黄色 | 聚合根(Aggregate) | 名词 | "订单" |
知识点3: Process Level Event Storming
在Big Picture之后,选取一个关键流程深入:
Process Level的核心范式:
[角色] → 看[读模型] → 发出[命令] → [聚合根]处理 → 产生[事件] → 触发[策略] → ...
示例: 贷款审批流程
[客户经理] → 看[客户资质视图] → 发出[提交贷款申请]
→ [贷款申请]处理 → 产生[贷款申请已提交]
→ 触发[自动风控评估策略]
→ [风控引擎]处理 → 产生[风控评估已完成]
→ 触发[审批路由策略: 金额>100万走二级审批]
[审批经理] → 看[贷款申请详情+风控报告] → 发出[审批通过/拒绝]
→ [贷款申请]处理 → 产生[贷款申请已审批]
→ 触发[放款准备策略]
Process Level的关键产出:
- 完整的命令-事件链:每个步骤的输入和输出
- 策略(Policy)的识别:哪些步骤是自动触发的、哪些需要人工
- 读模型需求:每个角色在每个步骤需要看到什么信息
- 异常路径:超时、拒绝、取消、回退等异常场景
知识点4: Design Level Event Storming——从便签到代码
这是最关键的一步:如何把墙上的便签直接映射为代码结构。
便签 → 代码的映射:
橙色(事件) → 领域事件类
蓝色(命令) → 命令类 + 命令处理器
黄色(角色) → API端点的认证/授权
紫色(策略) → 领域服务 / 事件处理器
绿色(读模型) → 查询服务 / DTO
淡黄色(聚合根) → 聚合根类
直接映射示例:
便签:
[客户经理] → [提交贷款申请] → [贷款申请] → [贷款申请已提交] → [风控评估策略]
代码:
// 命令(蓝色便签)
class SubmitLoanApplicationCommand {
constructor(
readonly applicantId: string,
readonly amount: Money,
readonly term: number, // 贷款期限(月)
readonly purpose: string,
readonly submittedBy: string // 客户经理ID
) {}
}
// 聚合根(淡黄色便签)
class LoanApplication {
private _id: string;
private _status: LoanApplicationStatus;
private _applicantId: string;
private _amount: Money;
private _term: number;
private _riskScore: number | null;
private _events: DomainEvent[] = [];
static submit(command: SubmitLoanApplicationCommand): LoanApplication {
const app = new LoanApplication();
app._id = generateId();
app._status = LoanApplicationStatus.SUBMITTED;
app._applicantId = command.applicantId;
app._amount = command.amount;
app._term = command.term;
app._riskScore = null;
// 领域事件(橙色便签)
app._events.push(new LoanApplicationSubmitted({
applicationId: app._id,
applicantId: command.applicantId,
amount: command.amount,
submittedBy: command.submittedBy,
submittedAt: new Date()
}));
return app;
}
recordRiskAssessment(score: number, result: RiskResult): void {
if (this._status !== LoanApplicationStatus.SUBMITTED) {
throw new Error("Can only assess submitted applications");
}
this._riskScore = score;
this._status = LoanApplicationStatus.RISK_ASSESSED;
this._events.push(new RiskAssessmentCompleted({
applicationId: this._id,
score: score,
result: result
}));
}
approve(approvedBy: string, conditions: string[]): void {
if (this._status !== LoanApplicationStatus.RISK_ASSESSED) {
throw new Error("Can only approve risk-assessed applications");
}
this._status = LoanApplicationStatus.APPROVED;
this._events.push(new LoanApplicationApproved({
applicationId: this._id,
approvedBy: approvedBy,
conditions: conditions,
approvedAt: new Date()
}));
}
reject(rejectedBy: string, reason: string): void {
if (this._status !== LoanApplicationStatus.RISK_ASSESSED) {
throw new Error("Can only reject risk-assessed applications");
}
this._status = LoanApplicationStatus.REJECTED;
this._events.push(new LoanApplicationRejected({
applicationId: this._id,
rejectedBy: rejectedBy,
reason: reason
}));
}
}
// 策略(紫色便签) → 事件处理器
class AutoRiskAssessmentPolicy {
constructor(private riskEngine: RiskEngine) {}
// 当贷款申请提交后,自动触发风控评估
async handle(event: LoanApplicationSubmitted): Promise<void> {
const score = await this.riskEngine.assess({
applicantId: event.applicantId,
amount: event.amount
});
// 发出命令给LoanApplication聚合根
await this.commandBus.send(new RecordRiskAssessmentCommand({
applicationId: event.applicationId,
score: score.value,
result: score.result
}));
}
}
// 策略(紫色便签): 审批路由
class ApprovalRoutingPolicy {
async handle(event: RiskAssessmentCompleted): Promise<void> {
if (event.score >= 80) {
// 高评分 → 自动审批
await this.commandBus.send(new AutoApproveCommand(event.applicationId));
} else if (event.score >= 60) {
// 中等评分 → 一级审批
await this.notificationService.notifyApprover(
ApprovalLevel.LEVEL_1, event.applicationId
);
} else {
// 低评分 → 二级审批
await this.notificationService.notifyApprover(
ApprovalLevel.LEVEL_2, event.applicationId
);
}
}
}
// 读模型(绿色便签)
class LoanApplicationView {
applicationId: string;
applicantName: string;
amount: string;
term: number;
status: string;
riskScore: number | null;
submittedAt: Date;
approverName: string | null;
}
知识点5: 引导(Facilitation)技巧
这是Event Storming成败的关键——如何引导一屋子人有效地协作。
引导者的角色:
引导者 ≠ 主讲人
引导者 ≠ 决策者
引导者 = 过程的管理者
职责:
├── 设置规则和节奏
├── 鼓励沉默的参与者发言
├── 打断跑题的讨论("这是个好问题,我们先贴个热点便签")
├── 识别关键争议并引导讨论
├── 控制时间
└── 确保所有声音都被听到
常见场景及应对:
| 场景 | 应对策略 |
|---|---|
| 业务专家沉默不语 | 直接提问: "在你的工作中,XX之后通常会发生什么?" |
| 技术人员过早讨论实现 | "这个实现细节我们后面再讨论,先关注业务流程" |
| 两人争论不休 | "你们说的可能都对,我们贴两个不同颜色的便签表示两种观点" |
| 某人主导发言 | "谢谢XX的观点,其他人怎么看?" |
| 讨论跑题 | "很好的观点,但它属于另一个流程,我们先贴个热点" |
| 参与者疲劳 | 休息10分钟,回来后从热点开始讨论(热点通常更有趣) |
房间和物资准备:
必须:
├── 大面白墙(或白板墙/大卷纸) - 至少5米宽
├── 多色便签(至少橙/蓝/黄/粉/紫/绿)
├── 粗马克笔(细笔写的字远处看不清)
├── 站立工作(不要坐着 - 坐着的人会变成旁观者)
└── 2-4小时的连续时间块
禁止:
├── 笔记本电脑(除非用于查资料)
├── 投影仪(PPT会让人变成被动听众)
└── 超过15人(超过则分组并行)
知识点6: 热点(Hot Spot)和策略(Policy)的识别
热点是Event Storming最独特的产出之一:
热点的类型:
├── 不确定性: "这个流程到底是先审批还是先风控?"
├── 争议: "业务说A,运营说B,谁是对的?"
├── 复杂性: "这里有10种异常情况需要处理"
├── 痛点: "这个步骤经常出错/很慢/被用户投诉"
├── 知识缺口: "没人知道这个流程的细节"
└── 技术债: "这里是遗留系统的接口,很脆弱"
热点的价值:
├── 是后续深入讨论的优先级列表
├── 是产品backlog的输入
├── 是架构风险的早期信号
└── 比任何需求文档都更真实地反映了系统的痛点
策略(Policy)是连接事件和后续行为的规则:
策略的识别方法:
在每个事件后面问: "这个事件发生后,会自动触发什么?"
如果答案是"有人需要手动操作" → 不是策略,是人工步骤
如果答案是"系统自动做XX" → 这是策略(自动化规则)
如果答案是"当XX条件满足时才做" → 这是条件策略
示例:
[支付已完成] → 策略: "自动发送收据邮件"
[订单已逾期] → 策略: "超过30天自动取消"
[风控评分<60] → 策略: "自动路由到人工审核"
对比分析
Event Storming vs 传统需求分析方法
| 维度 | Event Storming | 用户故事地图 | 传统需求文档 | UML用例图 |
|---|---|---|---|---|
| 参与者 | 所有角色 | PM+设计+开发 | BA单独编写 | 架构师 |
| 时间投入 | 2-4小时/次 | 半天-1天 | 数周 | 数天 |
| 知识传递 | 实时面对面 | 半实时 | 文档异步 | 文档异步 |
| 全局视图 | 强(整面墙) | 中(有层级) | 弱(按模块割裂) | 弱(按用例割裂) |
| 发现隐性知识 | 强(讨论驱动) | 中 | 弱 | 弱 |
| 到代码的距离 | 近(Design Level直接映射) | 中 | 远 | 远 |
| 学习曲线 | 低(贴便签) | 中 | 低(写文档) | 高(UML语法) |
| 适合阶段 | 项目初期/重构前 | 产品规划 | 需求确认 | 系统设计 |
三层Event Storming对比
| 维度 | Big Picture | Process Level | Design Level |
|---|---|---|---|
| 目标 | 全貌理解 | 流程细化 | 代码映射 |
| 粒度 | 粗 | 中 | 细 |
| 参与者 | 10-15人(全角色) | 5-8人(领域+技术) | 3-5人(技术为主) |
| 时间 | 2-4小时 | 2-3小时 | 1-2小时 |
| 产出 | BC划分+热点 | 命令-事件链+策略 | 聚合根+读模型 |
| 频率 | 项目初期1-2次 | 每个核心流程1次 | 每个BC内多次 |
架构设计实操
实操: "贷款申请→审批→放款→还款→逾期" 完整三层Event Storming
Layer 1: Big Picture
事件时间线(从左到右):
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 申请阶段 │ │ 审批阶段 │ │ 放款阶段 │
│ │ │ │ │ │
│ 客户已注册 │ │ 风控评估已完成 │ │ 放款已执行 │
│ 实名认证已通过 │ │ 审批已通过/拒绝 │ │ 合同已签署 │
│ 贷款申请已提交 │ │ 条件已补充 │ │ 资金已到账 │
│ │ │ 复审已完成 │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 还款阶段 │ │ 逾期阶段 │ │ 结清阶段 │
│ │ │ │ │ │
│ 还款计划已生成 │ │ 还款已逾期 │ │ 贷款已结清 │
│ 还款已执行 │ │ 催收通知已发送 │ │ 征信报告已更新 │
│ 提前还款已申请 │ │ 催收任务已分配 │ │ │
│ 部分还款已确认 │ │ 诉讼已发起 │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
热点标记:
🔴 热点1: "风控评估需要多长时间?实时还是异步?" (性能关注)
🔴 热点2: "审批通过后客户不签合同怎么办?有效期多长?" (业务规则不明)
🔴 热点3: "提前还款的违约金计算规则是什么?" (业务复杂度)
🔴 热点4: "逾期催收的升级规则是什么?" (策略复杂度)
🔴 热点5: "多个贷款同时还款时的优先级" (并发复杂度)
初步BC划分:
BC1: 客户管理 (注册/认证)
BC2: 贷款申请 (申请/审批)
BC3: 风控评估 (独立的风控引擎)
BC4: 合同管理 (签约/合同条款)
BC5: 放款执行 (资金划转)
BC6: 还款管理 (还款计划/执行/提前还款)
BC7: 催收管理 (逾期/催收/诉讼)
BC8: 征信报告 (外部系统集成)
Layer 2: Process Level (聚焦"贷款申请→审批"流程)
[客户经理] → 看[客户资质概览] → [提交贷款申请]
│
▼
[贷款申请] 聚合根
│
▼
[贷款申请已提交] 事件
│
┌──────────┤
│ │
[自动风控策略] [补充材料策略]
│ │
▼ ▼
[风控引擎评估] [通知客户补充]
│
▼
[风控评估已完成] 事件
│
[审批路由策略]
├── 评分≥80: 自动审批
├── 评分60-79: 一级审批
└── 评分<60: 二级审批
│
▼
[审批经理] → 看[申请详情+风控报告+历史贷款] → [审批通过/拒绝/要求补充]
│
▼
[贷款申请已审批/已拒绝] 事件
│
┌────────────────┤
│ │
[审批通过策略] [拒绝通知策略]
│ │
▼ ▼
[生成合同草案] [发送拒绝通知]
[通知客户签约] [记录征信]
Layer 3: Design Level (聚焦"贷款申请"聚合根)
// 直接从便签映射的代码结构
// ===== 命令 (蓝色便签) =====
interface SubmitLoanApplication {
applicantId: string;
amount: Money;
termMonths: number;
purpose: LoanPurpose;
collateral?: CollateralInfo;
}
interface RecordRiskAssessment {
applicationId: string;
riskScore: number;
riskLevel: RiskLevel;
factors: RiskFactor[];
}
interface ApproveLoanApplication {
applicationId: string;
approvedBy: string;
approvedAmount: Money; // 可能不是申请全额
conditions: string[];
interestRate: number;
}
interface RejectLoanApplication {
applicationId: string;
rejectedBy: string;
reason: string;
}
// ===== 事件 (橙色便签) =====
interface LoanApplicationSubmitted {
applicationId: string;
applicantId: string;
amount: Money;
termMonths: number;
submittedAt: Date;
}
interface RiskAssessmentCompleted {
applicationId: string;
riskScore: number;
riskLevel: RiskLevel;
assessedAt: Date;
}
interface LoanApplicationApproved {
applicationId: string;
approvedAmount: Money;
interestRate: number;
conditions: string[];
approvedAt: Date;
}
interface LoanApplicationRejected {
applicationId: string;
reason: string;
rejectedAt: Date;
}
// ===== 聚合根 (淡黄色便签) =====
class LoanApplication {
// 状态机(从ES中识别):
// DRAFT → SUBMITTED → RISK_ASSESSED → APPROVED/REJECTED
// → PENDING_SUPPLEMENTARY
// 每个命令对应一个方法
// 每个方法产生一个事件
// 每个方法检查状态前置条件
}
// ===== 策略 (紫色便签) =====
// AutoRiskAssessmentPolicy: 申请提交后自动评估
// ApprovalRoutingPolicy: 风控完成后路由到合适的审批人
// ContractGenerationPolicy: 审批通过后自动生成合同
// ===== 读模型 (绿色便签) =====
// LoanApplicationListView: 审批人看的待审批列表
// LoanApplicationDetailView: 审批人看的详情(含风控报告)
// ApplicantHistoryView: 客户的历史贷款记录
AI增强实践
AI在Event Storming中的应用
1. 事前准备——领域知识预研
Prompt: "我们要对 [业务领域] 做Event Storming。请帮我:
1. 列出这个领域中最常见的30个领域事件
2. 识别关键角色(Actor)
3. 列出可能的外部系统集成点
4. 预测可能的热点(复杂/争议/不确定的地方)
这些不是最终答案,只是帮我准备引导问题的参考。"
2. 事后整理——便签到文档
Prompt: "以下是我们Event Storming的照片/记录:
[描述或列出便签内容]
请帮我:
1. 整理为结构化的事件时间线
2. 标注命令-事件-策略的关系
3. 识别潜在的Bounded Context边界
4. 生成Design Level的代码框架"
3. 代码生成——Design Level到代码
Design Level的产出可以直接让AI生成代码框架:
- 聚合根的状态机
- 命令和事件的类型定义
- 策略的事件处理器骨架
- 读模型的DTO定义
AI vs 人工边界:
| 任务 | AI能力 | 人工不可替代 |
|---|---|---|
| 预研领域事件列表 | 优秀(泛化知识) | 特定业务的独特规则 |
| 整理ES产出 | 优秀 | - |
| 生成代码框架 | 优秀 | - |
| 引导ES工作坊 | 不适用 | 现场氛围和人际互动 |
| 识别热点 | 有限 | 需要业务上下文和直觉 |
| 解决争议 | 不适用 | 需要组织政治判断 |
与Web3/DeFi的关联
| 传统ES概念 | Web3对应 | 关键差异 |
|---|---|---|
| 领域事件 | 链上Event Log | 链上事件不可篡改,天然的事件存储 |
| 命令 | 交易(Transaction) | 链上命令需要Gas费和签名 |
| 策略(Policy) | 智能合约逻辑/链下Bot | 链上策略=合约代码,链下策略=Keeper/Bot |
| 聚合根 | 智能合约 | 合约=状态+行为,类似充血聚合根 |
| 读模型 | Subgraph/Indexer | The Graph等索引器构建读模型 |
| 热点 | Gas优化热点 | 链上的"热点"往往与Gas成本相关 |
用Event Storming分析DeFi协议:
以Aave借贷协议为例:
事件时间线:
[用户已存款] → [利息已累计] → [借款已发起] → [健康因子已更新]
→ [健康因子低于阈值] → [清算已触发] → [清算已执行]
策略:
- 利率计算策略: 根据利用率自动调整利率
- 清算触发策略: 健康因子<1时自动触发
- 预言机更新策略: Chainlink价格更新时重新计算健康因子
热点:
🔴 价格闪崩时的级联清算风险
🔴 预言机延迟导致的错误清算
🔴 Gas价格飙升时清算bot是否能及时执行
今日思考
-
回想你参加过的最好的和最差的需求讨论会。 最好的那次,是什么让它有效?最差的那次,问题出在哪里?Event Storming的哪些元素能解决你经历过的问题?
-
在远程工作时代,如何做Event Storming? 用Miro/FigJam等工具能替代物理便签吗?远程ES损失了什么(物理走动、面对面互动)?如何弥补?
-
DeFi协议的Event Storming有什么不同? 传统系统的事件是"可能发生也可能不发生的",但链上事件是"一旦发生就不可逆的"。这种不可逆性如何影响ES中"异常路径"的设计?
面试题准备
Q1: Event Storming和传统需求分析有什么区别?
30秒版本: 三个核心区别:第一,参与者不同——ES让所有角色(业务+技术+运营)同时参与,而不是BA独自写文档;第二,知识传递方式不同——ES是实时面对面讨论,能暴露隐性知识;第三,产出形式不同——ES的产出可以直接映射为代码结构(事件→命令→聚合根),比需求文档到代码的距离更短。
2分钟版本:
Event Storming的核心价值在于三个方面。
第一,打破信息孤岛。传统需求分析中,业务专家告诉BA,BA写文档,开发读文档。信息在每次传递中都会丢失和扭曲。ES让所有人在同一面墙前讨论,分歧当场暴露和解决。
第二,发现隐性知识。业务专家脑中有大量"理所当然"的规则,不会主动告诉你。ES通过事件时间线的排序过程,强迫所有人检查"这个事件之后会发生什么",从而"逼"出这些隐性规则。
第三,到代码的距离短。ES的Design Level直接产出命令、事件、聚合根、策略——这就是代码的骨架。传统需求文档到代码之间还需要大量的翻译工作。
在我的实践中,一次2-3小时的ES工作坊产出的领域知识,相当于2-3周的传统需求调研。关键成功因素是引导者的水平——好的引导者能让沉默的业务专家开口,能在争论中识别真正的问题。
追问准备:
追问: "如何处理业务专家不配合的情况?" 回答: 三种策略。第一,不要叫它"Event Storming"或任何技术名词,就说"我想请你帮忙梳理一下XX流程"。第二,从具体场景开始,"上周那个XX客户的案例,流程是怎样的?"比抽象讨论有效得多。第三,如果业务专家实在没时间全程参与,至少请他们参加30分钟的"验证环节"——看看我们理解的对不对。
Q2: 从Event Storming到代码,中间还需要什么?
30秒版本: 需要三步:第一,细化Design Level——将Process Level的事件进一步细化为聚合根的状态转换;第二,确定持久化策略——事件要存在哪里、读模型怎么构建;第三,定义API契约——外部如何触发命令、如何查询读模型。ES到代码不是一步到位的,但ES大幅缩短了这个距离。
2分钟版本:
ES的Design Level已经非常接近代码了,但还需要补充三个方面。
第一,聚合根的不变量细化。ES告诉你有哪些命令和事件,但具体的业务规则(如"贷款金额不能超过年收入的30倍")需要在Design Level之后进一步明确。
第二,技术决策。ES不涉及技术选型——数据库、消息队列、缓存策略等。这些需要在ES之后,结合架构特征需求单独决策。
第三,非功能需求。ES关注的是业务流程,但性能SLA("风控评估必须在3秒内完成")、安全要求("敏感数据必须加密")等需要额外补充。
我的做法是在ES之后立即产出三个文档:一是事件目录(所有事件的schema定义),二是聚合根设计(状态机+不变量),三是ADR(关键技术决策)。这三个文档加上ES的墙面照片,就足以开始编码了。
学习资源
- Alberto Brandolini - "Introducing Event Storming" - 官方书(在线连载): https://www.eventstorming.com/
- Alberto Brandolini - EventStorming Workshop视频 - YouTube上有多个演讲
- Nick Tune - "Domain-Driven Design Starter Modelling Process" - 将ES融入完整的建模流程
- Miro Event Storming模板 - 远程协作工具
- EventStorming Glossary Cheat Sheet - DDD Crew: https://github.com/ddd-crew/eventstorming-glossary-cheat-sheet
- 《Domain-Driven Design Distilled》 - Vaughn Vernon - Chapter 7: Event Storming
明日预告
Day 13: 微服务治理(高级) —— 从"拆出微服务"到"管好微服务"。我们将深入服务网格(Istio/Linkerd)、分布式追踪(OpenTelemetry)、熔断限流降级(Resilience4j)、Saga模式(编排vs协调)、API版本演进策略。实操:设计一个完整的"支付平台"微服务方案,包含服务通信矩阵、Saga设计和API演进计划。