惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
月光博客
月光博客
雷峰网
雷峰网
WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security @ Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
爱范儿
爱范儿
W
WeLiveSecurity
J
Java Code Geeks
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
T
Threatpost
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
Latest news
Latest news
V2EX - 技术
V2EX - 技术
小众软件
小众软件
T
The Blog of Author Tim Ferriss
A
Arctic Wolf
B
Blog RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
C
Check Point Blog
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
V
V2EX
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
D
DataBreaches.Net
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
C
CXSECURITY Database RSS Feed - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com

土法炼钢兴趣小组的算法知识备份

国密算法与国密 TLS 系列索引 【系统架构设计】架构质量属性:不只是"高可用高性能" 【系统架构设计百科】告警策略:如何避免"狼来了" 【系统架构设计】CQRS:读写分离的架构哲学 【系统架构设计】空间架构:极端扩展场景的解法 【系统架构设计】微服务架构深度审视:优势、代价与适用边界 【系统架构设计】扩展性原理:水平、垂直与对角扩展 【系统架构设计】无状态设计:扩展的第一步也是最难的一步 【系统架构设计】缓存架构:从本地到分布式的多级缓存体系 【系统架构设计】管道与过滤器:Unix 哲学的架构表达 【系统架构设计】复杂性管理:架构的核心战场 【系统架构设计】消息队列架构:异步解耦的设计与陷阱 【系统架构设计】CDN 架构:全球加速的设计原理 【系统架构设计】连接池设计:被忽视的性能杀手 【系统架构设计】弹性设计模式:熔断器、舱壁与超时 【系统架构设计】高可用设计模式:冗余、故障转移与仲裁 【系统架构设计】容量规划:从拍脑袋到数据驱动 【系统架构设计】数据库扩展:分库分表的工程实践与替代方案 【系统架构设计】SLO 工程:可靠性的量化管理 【系统架构设计】性能建模:用数学思维分析系统瓶颈 【系统架构设计】混沌工程:主动验证系统的韧性 【系统架构设计】零拷贝与内存映射:数据搬运的极致优化 【系统架构设计】线程模型:从 thread-per-request 到协程 【系统架构设计】容灾架构:多活与灾备设计 【系统架构设计】数据库性能模式:索引、查询与连接管理 【系统架构设计】数据建模:从关系范式到文档模型的真实权衡 【系统架构设计】吞吐量优化:批处理、流水线与并发模型 【系统架构设计】流处理架构:从批处理到实时的范式迁移 【系统架构设计】搜索引擎架构:倒排索引之上的系统设计 【系统架构设计】时序数据架构:监控与 IoT 的存储设计 【系统架构设计】数据迁移与版本化:在线不停机的数据演进 【系统架构设计】数据湖与数据仓库:分析架构的演进路线 【系统架构设计】API 网关设计:入口层的职责边界 【系统架构设计】应用层数据一致性模式:在正确性与性能之间走钢丝 【系统架构设计】多模数据库选型:Polyglot Persistence 的工程实践 【系统架构设计】服务发现与注册:动态拓扑的基础设施 【系统架构设计】配置管理架构:从配置文件到配置中心 【系统架构设计】全链路压测:大规模系统的性能验证 【系统架构设计】幂等性设计:分布式环境下的安全重试 【系统架构设计】契约测试与 Schema 演进:服务间的信任协议 【系统架构设计】长连接与推送架构:WebSocket、SSE 与 MQTT 【系统架构设计】延迟分析:从 P50 到 P999 的全链路追踪 【系统架构设计百科】DDD 战术模式:聚合、实体与值对象 【系统架构设计百科】防腐层与开放主机服务:系统集成的 DDD 方案 【系统架构设计百科】CQRS + Event Sourcing 完整实战:从领域建模到部署 【系统架构设计百科】DDD 与微服务:用领域模型划分服务边界 【系统架构设计】DDD 战略设计:限界上下文与上下文映射 【系统架构设计百科】认证架构:从 Session 到 JWT 到 OIDC 【系统架构设计】API 设计哲学:REST vs GraphQL vs gRPC 的真实权衡 排序算法专题:从 TimSort 到并行排序 【密码学百科】国密算法体系:SM2/SM3/SM4/SM9 全景解读 【密码学百科】承诺方案:Pedersen 承诺、向量承诺与多项式承诺 【密码学百科】不经意传输与隐私信息检索:OT、OT 扩展与 PIR 【密码学百科】门限密码学:门限签名、门限解密与分布式密钥生成 完美哈希:从理论到 gperf 实践 【密码学百科】安全多方计算:从 Yao 的混淆电路到实用 MPC 【密码学百科】同态加密:从 Paillier 到全同态加密(FHE) 【密码学百科】零知识证明系统:zk-SNARKs、zk-STARKs 与 Bulletproofs 【密码学百科】概率论与密码分析:生日攻击、差分分析与线性分析 【密码学百科】计算复杂性与归约:密码安全性证明的基石 【密码学百科】秘密共享:Shamir 方案、VSS 与安全多方计算入口 【密码学百科】椭圆曲线代数:Weierstrass 方程、点群运算与曲线选择 【密码学百科】离散对数与配对密码学:从 DLP 到 BLS 签名 【密码学百科】格密码数学基础:SVP、LWE 与格基约化 【密码学百科】抽象代数:群、环、域的密码学视角 【密码学百科】有限域算术:GF(2^n) 运算与在 AES/ECC 中的应用 【密码学百科】数论进阶:二次剩余、椭圆曲线上的 Weil 配对 【密码学百科】密码学简史:从凯撒密码到量子时代 【密码学百科】威胁模型与安全目标:CIA 三要素之外 【密码学百科】Kerckhoffs 原则与现代密码设计哲学 【密码学百科】随机性:密码学的基石 【密码学百科】信息论入门:熵、完美保密与 Shannon 定理 【密码学百科】分组密码原理:Feistel 网络与 SPN 结构 【密码学百科】AES 逐步拆解:SubBytes 到 MixColumns 的数学 【密码学百科】分组密码工作模式全览:ECB/CBC/CTR/OFB/CFB 【密码学百科】流密码:RC4 的兴衰与 ChaCha20 的崛起 【密码学百科】密码学哈希函数:MD5→SHA-2→SHA-3 的进化之路 【密码学百科】MAC 与 HMAC:消息认证的正确姿势 【密码学百科】认证加密(AEAD):GCM、ChaCha20-Poly1305 与 OCB 【密码学百科】密钥派生函数:HKDF、PBKDF2、Argon2 与密码存储 【密码学百科】公钥密码的数论基础:模运算、群、原根 【密码学百科】RSA 从原理到攻击:教科书 RSA 为什么不安全 【密码学百科】Diffie-Hellman 密钥交换与离散对数问题 【密码学百科】椭圆曲线密码学(ECC):从几何直觉到点群运算 【密码学百科】数字签名:ECDSA、EdDSA 与 Schnorr 签名 【密码学百科】现代密钥交换:X25519、ECDHE 与前向保密 【密码学百科】混合加密与 KEM/DEM 范式:ECIES 与 HPKE 【密码学百科】填充方案:PKCS#1 v1.5、OAEP 与 PSS 【密码学百科】TLS 协议全解析:从握手到 0-RTT 【密码学百科】PKI 与数字证书:信任链的构建与崩塌 【密码学百科】密码认证协议:从 SRP 到 OPAQUE 【密码学百科】零知识证明入门:如何证明你知道而不泄露 【密码学百科】安全信道构造:Noise 协议框架与 Signal 协议 【密码学百科】密钥管理工程:HSM、KMS 与密钥生命周期 【密码学百科】侧信道攻击:从时序攻击到功耗分析 【密码学百科】密码学实现陷阱:三层漏洞分类、审计工具链与系统性预防 密码敏捷性:如何设计可升级的密码系统 【密码学百科】OpenSSL/BoringSSL 架构剖析:ENGINE、Provider 与 FIPS 模块 【Linux 网络子系统深度拆解】内核网络调优方法论:从基准测试到生产验证 排序基准测试:用数据说话
【系统架构设计百科】领域事件与事件风暴:从业务到架构的桥梁
2026-04-13 · via 土法炼钢兴趣小组的算法知识备份

一个 20 人的开发团队花了两个月画 UML 图和写需求文档,依然对核心业务流程的理解存在分歧。直到有一天,团队把领域专家、开发者、测试人员全部拉进一个大会议室,用橙色便利贴写出了系统中发生的所有事件——两个小时后,所有人第一次在同一幅图上看到了业务全貌。这就是事件风暴(Event Storming)的力量。

事件风暴是意大利软件架构师 Alberto Brandolini 在 2012 年提出的一种协作建模方法。它通过识别领域事件(Domain Event)来探索业务领域,并将发现逐步细化为可落地的软件设计。本文将完整介绍事件风暴的三个层次、领域事件的设计规范,以及从事件风暴到限界上下文和聚合的推导方法。

上一篇:DDD 战术模式 | 下一篇:防腐层与开放主机服务


一、什么是领域事件

1.1 定义

领域事件(Domain Event)是领域中发生的、具有业务意义的事实。它用过去时态命名,描述已经发生的事情。

命名规范:

正确:OrderPlaced, PaymentReceived, ShipmentDelivered
错误:PlaceOrder, ReceivePayment, DeliverShipment  ← 这是命令,不是事件

1.2 领域事件的特征

特征 说明
过去时态 描述已经发生的事实
不可变 事件一旦产生不可修改
有时间戳 记录事件发生的精确时间
携带足够上下文 包含消费者处理所需的数据
业务语义 用业务语言命名,而非技术语言

1.3 领域事件 vs 集成事件

在前一篇 DDD 战术模式 中已经简要提到这两者的区别,这里做进一步展开。

领域事件(Domain Event)是限界上下文内部的建模概念,表达聚合状态变迁的事实。集成事件(Integration Event)是跨限界上下文的通信机制,通常通过消息中间件传递。

graph LR
    subgraph "交易上下文"
        A["Order 聚合"] -->|"产生"| DE["领域事件<br/>OrderConfirmed"]
        DE -->|"转换"| IE["集成事件<br/>OrderConfirmedIntegration"]
    end

    IE -->|"消息队列"| subgraph2

    subgraph subgraph2 ["物流上下文"]
        ACL2["防腐层"] -->|"转换"| DE2["领域事件<br/>ShippingOrderCreated"]
        DE2 -->|"触发"| B["Shipment 聚合"]
    end

关键区别:

维度 领域事件 集成事件
作用范围 限界上下文内部 跨限界上下文
传输载体 进程内内存 消息队列(Kafka、RabbitMQ)
序列化 不需要 需要(JSON、Protobuf)
版本管理 随代码演进 需要严格的版本策略
消费者 同一上下文的事件处理器 其他上下文的适配器
可靠性要求 内存操作,通常可靠 需要保证至少一次投递

1.4 领域事件的代码结构

// 基础事件接口
public interface DomainEvent {
    Instant occurredAt();
    String eventType();
}

// 具体领域事件
public record OrderPlacedEvent(
    OrderId orderId,
    CustomerId customerId,
    List<OrderLineSnapshot> lines,
    Money totalAmount,
    Instant occurredAt
) implements DomainEvent {

    public OrderPlacedEvent {
        Objects.requireNonNull(orderId);
        Objects.requireNonNull(customerId);
        Objects.requireNonNull(lines);
        Objects.requireNonNull(totalAmount);
        if (occurredAt == null) {
            occurredAt = Instant.now();
        }
    }

    @Override
    public String eventType() {
        return "order.placed";
    }
}

// 集成事件——用于跨上下文传播
public record OrderPlacedIntegrationEvent(
    String orderId,
    String customerId,
    List<LineItemDto> items,
    String totalAmount,
    String currency,
    String occurredAt
) {
    // 从领域事件转换
    public static OrderPlacedIntegrationEvent from(OrderPlacedEvent domainEvent) {
        return new OrderPlacedIntegrationEvent(
            domainEvent.orderId().value(),
            domainEvent.customerId().value(),
            domainEvent.lines().stream()
                .map(l -> new LineItemDto(l.productId(), l.quantity(), l.price()))
                .toList(),
            domainEvent.totalAmount().amount().toPlainString(),
            domainEvent.totalAmount().currency().getCurrencyCode(),
            domainEvent.occurredAt().toString()
        );
    }
}

二、事件风暴概述

2.1 什么是事件风暴

事件风暴是一种基于便利贴的协作建模工作坊方法。其核心理念是:

  1. 把所有相关角色(开发者、领域专家、产品经理、测试人员)聚集在一起;
  2. 用不同颜色的便利贴表示不同类型的领域概念;
  3. 在一面大墙上按时间线排列这些概念;
  4. 通过讨论和迭代,逐步揭示业务全貌。

2.2 为什么需要事件风暴

传统的需求分析方法(用例图、流程图、需求文档)有几个共同的问题:

问题 说明
信息不对称 产品经理写文档,开发者读文档,理解必然有偏差
线性思维 流程图强调”正常路径”,异常场景容易遗漏
缺乏全局视角 每个人只了解自己负责的部分
时效性差 文档写完就开始过时

事件风暴通过同步协作解决了这些问题。所有人在同一时间、同一地点,对同一个模型进行讨论。

2.3 三个层次

事件风暴有三个递进的层次,每个层次解决不同的问题:

┌─────────────────────────────────────────────┐
│  层次三:软件设计(Software Design)          │
│  目标:定义聚合、命令处理器、读模型            │
│  参与者:开发者                               │
│  时长:2-4 小时                              │
├─────────────────────────────────────────────┤
│  层次二:流程建模(Process Modeling)          │
│  目标:细化事件流、识别命令和策略              │
│  参与者:开发者 + 领域专家                    │
│  时长:2-3 小时                              │
├─────────────────────────────────────────────┤
│  层次一:大图探索(Big Picture)              │
│  目标:发现所有领域事件,建立全局共识           │
│  参与者:所有相关角色                         │
│  时长:2-4 小时                              │
└─────────────────────────────────────────────┘

三、便利贴颜色与含义

事件风暴使用标准化的颜色来表示不同类型的领域概念。以下是 Brandolini 定义的标准颜色系统:

颜色 概念 说明 示例
橙色 领域事件(Domain Event) 已发生的业务事实 “订单已下达”
蓝色 命令(Command) 触发事件的意图 “下单”
黄色(小) 参与者/角色(Actor) 执行命令的人或系统 “客户”“定时任务”
黄色(大) 聚合(Aggregate) 处理命令、产生事件 “Order”
紫色/玫红 策略(Policy) 事件触发的自动化反应 “当订单确认时,通知仓库”
绿色 读模型(Read Model) 辅助决策的信息视图 “库存仪表盘”
粉红/红色 热点(Hot Spot) 需要进一步讨论的问题 “退款规则未明确”
白色 外部系统(External System) 与当前系统交互的外部系统 “支付网关”“ERP”

3.1 概念之间的关系

graph LR
    Actor["👤 参与者<br/>(黄色小)"] --> Command["📋 命令<br/>(蓝色)"]
    Command --> Aggregate["📦 聚合<br/>(黄色大)"]
    Aggregate --> Event["📌 领域事件<br/>(橙色)"]
    Event --> Policy["📜 策略<br/>(紫色)"]
    Policy --> Command2["📋 命令<br/>(蓝色)"]
    ReadModel["📊 读模型<br/>(绿色)"] -.->|"辅助决策"| Actor
    External["🏢 外部系统<br/>(白色)"] --> Event
    Event --> External

基本叙事结构是:参与者发出命令聚合处理命令并产生领域事件,事件触发策略,策略可能导致新的命令。


四、层次一:大图探索

4.1 目标

在最短时间内建立对整个业务域的共同理解。不追求精确,追求全面。

4.2 步骤

步骤 1:混沌探索(5-10 分钟)

所有参与者同时在橙色便利贴上写下他们能想到的领域事件。每张便利贴写一个事件,用过去时态。不讨论、不争论,只管写。

"订单已下达"  "支付已完成"  "商品已发货"  "退款已申请"
"库存已扣减"  "优惠券已使用"  "用户已注册"  "评价已提交"

步骤 2:时间线排列(15-20 分钟)

把所有便利贴按时间顺序从左到右贴在墙上。这一步会自然引发讨论:

  • “支付完成是在订单确认之前还是之后?”
  • “库存扣减是在下单时还是支付后?”

这些讨论正是事件风暴的核心价值。

步骤 3:标记热点(5 分钟)

用粉红色便利贴标记所有有争议、不确定或需要进一步讨论的地方。

步骤 4:识别关键事件流(15-20 分钟)

从混沌中识别出几条核心事件流(通常是核心业务场景),用分隔线标记。

步骤 5:讨论和完善(30-60 分钟)

针对热点进行深入讨论,补充遗漏的事件,调整顺序。

4.3 大图探索的产出

时间线 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→

┌───────┐  ┌───────┐  ┌───────┐  ┌───────┐
│用户已  │  │商品已  │  │订单已  │  │支付已  │
│注册   │  │加入购  │  │下达   │  │发起   │
│       │  │物车   │  │       │  │       │
└───────┘  └───────┘  └───────┘  └───────┘
                                      │
                        ┌─────────────┘
                        ▼
                   ┌───────┐  ┌───────┐  ┌───────┐
                   │支付已  │  │库存已  │  │商品已  │
                   │完成   │  │扣减   │  │发货   │
                   │       │  │       │  │       │
                   └───────┘  └───────┘  └───────┘
                                              │
                                   ┌──────────┘
                                   ▼
                              ┌───────┐  ┌───────┐
                              │商品已  │  │订单已  │
                              │签收   │  │完成   │
                              │       │  │       │
                              └───────┘  └───────┘

[热点] 库存扣减时机:下单时还是支付后?
[热点] 退款流程:自动审批还是人工审批?

五、层次二:流程建模

5.1 目标

在大图探索的基础上,为每条核心事件流补充命令、参与者和策略,形成完整的业务流程模型。

5.2 补充命令和参与者

对每个领域事件,反向追问:

  • (Actor)触发了这个事件?
  • 通过什么命令(Command)触发的?
  • 做这个决定时需要看到什么信息(Read Model)?
[参与者]     [读模型]        [命令]        [聚合]      [事件]
 客户    →  商品详情页   →   下单     →   Order   →  订单已下达
 客户    →  支付收银台   →   支付     →   Payment →  支付已完成
 系统    →              →   扣减库存  →   Stock   →  库存已扣减
 仓库员  →  拣货清单     →   确认发货  →   Shipment→  商品已发货

5.3 识别策略

策略(Policy)是事件驱动的自动化反应规则。它描述的是”当 X 事件发生时,自动执行 Y 命令”。

策略识别模式:

[事件]               [策略]                    [命令]
订单已下达      →    当订单下达时              →  锁定库存
支付已完成      →    当支付完成时              →  确认订单 + 通知仓库
支付超时        →    当支付超过 30 分钟未完成时  →  取消订单 + 释放库存
商品已签收      →    当签收后 7 天无异议时      →  自动完成订单

5.4 策略的类型

类型 说明 示例
自动化策略 事件发生后自动触发 支付完成 → 扣减库存
定时策略 基于时间条件触发 超时未支付 → 取消订单
人工策略 需要人工介入决策 退款申请 → 人工审核
条件策略 基于业务条件分支 金额 > 10000 → 风控审核

六、层次三:软件设计

6.1 目标

将流程模型转化为可实现的软件设计,识别聚合边界、定义命令处理流程、设计读模型。

6.2 识别聚合

聚合的识别基于以下原则:

  1. 处理一个命令并产生事件的实体集合构成一个聚合;
  2. 必须在同一事务中保持一致的数据属于同一个聚合;
  3. 不同的命令如果操作不同的数据集,通常意味着不同的聚合。
从事件风暴到聚合的推导:

命令 "下单" → 聚合 "Order"
  创建订单,包含行项信息
  产生事件 "OrderPlaced"

命令 "支付" → 聚合 "Payment"
  记录支付信息
  产生事件 "PaymentCompleted"

命令 "扣减库存" → 聚合 "Inventory"
  更新库存数量
  产生事件 "StockReserved"

6.3 从聚合到限界上下文

当多个聚合属于同一个业务领域、使用相同的通用语言时,它们通常属于同一个限界上下文。

graph TB
    subgraph "交易上下文"
        O["Order 聚合"]
        OL["OrderLine"]
        O --- OL
    end

    subgraph "支付上下文"
        P["Payment 聚合"]
        PR["PaymentRecord"]
        P --- PR
    end

    subgraph "库存上下文"
        I["Inventory 聚合"]
        R["Reservation"]
        I --- R
    end

    subgraph "物流上下文"
        S["Shipment 聚合"]
        T["TrackingEvent"]
        S --- T
    end

    O -.->|"OrderPlaced"| P
    O -.->|"OrderPlaced"| I
    P -.->|"PaymentCompleted"| O
    P -.->|"PaymentCompleted"| S
    S -.->|"ShipmentDelivered"| O

6.4 设计读模型

读模型(Read Model)是为查询优化的数据视图。事件风暴中识别的绿色便利贴直接对应系统中的读模型。

// 读模型:订单列表视图
public class OrderListView {
    private String orderId;
    private String customerName;
    private String totalAmount;
    private String status;
    private String paymentStatus;
    private String shippingStatus;
    private LocalDateTime createdAt;
}

// 读模型的投影器:监听事件,更新视图
public class OrderListProjector {

    public void on(OrderPlacedEvent event) {
        OrderListView view = new OrderListView();
        view.setOrderId(event.orderId().value());
        view.setTotalAmount(event.totalAmount().toString());
        view.setStatus("待支付");
        view.setCreatedAt(event.occurredAt());
        viewStore.save(view);
    }

    public void on(PaymentCompletedEvent event) {
        OrderListView view = viewStore.findByOrderId(event.orderId());
        view.setPaymentStatus("已支付");
        view.setStatus("待发货");
        viewStore.save(view);
    }

    public void on(ShipmentDeliveredEvent event) {
        OrderListView view = viewStore.findByOrderId(event.orderId());
        view.setShippingStatus("已签收");
        view.setStatus("已完成");
        viewStore.save(view);
    }
}

七、事件风暴的引导技巧

7.1 准备工作

准备项 说明
场地 需要一面至少 6 米长的墙或白板
便利贴 标准颜色各 200 张以上
参与者 8-15 人,必须包含领域专家
时间 首次工作坊至少预留 3 小时
引导者 1-2 人,熟悉事件风暴方法
无座位 所有人站立,保持活跃

7.2 引导者的关键行为

  1. 打破沉默:如果参与者不知道从哪里开始,先写几个事件做示范;
  2. 鼓励混沌:大图探索阶段不要追求秩序,混沌中会涌现洞察;
  3. 追问”然后呢”:当事件流断裂时,问”这个事件发生之后会怎样?“;
  4. 追问”之前呢”:当起点不清晰时,问”是什么导致了这个事件?“;
  5. 标记热点:有争议的地方立即贴粉红色便利贴,不要当场解决;
  6. 保护少数声音:确保领域专家的声音被听到,开发者不要主导讨论。

7.3 常见陷阱

陷阱 表现 应对
技术过早介入 讨论数据库设计而非业务流程 引导回”业务上发生了什么”
追求完美 反复修改同一区域 设定时间盒,标记热点后继续
人数过少 只有开发者参与 必须邀请领域专家
场地不够 便利贴挤在小白板上 使用至少 6 米的墙面
线性思维 只关注”正常流程” 主动提问异常场景

八、远程事件风暴

8.1 工具选择

工具 优势 劣势
Miro 无限画布、便利贴模板完善 需要付费
FigJam 与 Figma 生态集成 模板较少
MURAL 企业级功能完善 价格较高
Excalidraw 开源免费 协作功能有限

8.2 远程事件风暴的调整

远程环境下,事件风暴需要额外注意以下几点:

  1. 缩短单次时长:线上注意力集中时间更短,每次不超过 90 分钟;
  2. 提前准备模板:在工具中预设好颜色和区域;
  3. 分组讨论:超过 8 人时先分组探索,再合并;
  4. 录制过程:远程更容易遗忘上下文,录制便于回顾;
  5. 投票功能:使用工具的投票功能标记热点。

九、工程案例:在线教育平台的事件风暴

9.1 背景

某在线教育公司计划重构其课程管理和学习系统。原系统是一个单体应用,所有功能耦合在一起。团队希望通过事件风暴重新理解业务,并设计新的架构。

9.2 大图探索结果

工作坊参与者包括 3 名产品经理、2 名课程运营、1 名教研负责人、5 名开发者。

主要事件流(简化版):

课程管理流:
课程已创建 → 课程内容已上传 → 课程已审核通过 → 课程已上架

学员学习流:
学员已注册 → 课程已购买 → 学习进度已更新 → 课程已完成 → 证书已颁发

直播互动流:
直播间已创建 → 直播已开始 → 学员已加入 → 问答已提交 → 直播已结束 → 回放已生成

订单支付流:
订单已创建 → 优惠已应用 → 支付已发起 → 支付已完成 → 退款已申请 → 退款已处理

热点(共识不一致的区域): - “课程已完成”的判断标准是什么?学完所有章节?还是通过考试? - 退款策略:学完 30% 以上是否还能退款? - 直播回放是否需要单独计费?

9.3 流程建模(课程购买流)

sequenceDiagram
    participant Student as 学员
    participant OrderAgg as 订单聚合
    participant PayAgg as 支付聚合
    participant CourseAgg as 课程聚合
    participant EnrollAgg as 选课聚合

    Student->>OrderAgg: 创建订单(courseId, studentId)
    OrderAgg-->>OrderAgg: OrderCreated
    Note over OrderAgg: 策略:计算优惠

    Student->>PayAgg: 发起支付(orderId, amount)
    PayAgg-->>PayAgg: PaymentInitiated

    PayAgg-->>PayAgg: PaymentCompleted
    Note over PayAgg: 策略:支付完成时

    PayAgg->>OrderAgg: 确认订单(orderId)
    OrderAgg-->>OrderAgg: OrderConfirmed
    Note over OrderAgg: 策略:订单确认时

    OrderAgg->>EnrollAgg: 创建选课(studentId, courseId)
    EnrollAgg-->>EnrollAgg: StudentEnrolled
    Note over EnrollAgg: 策略:选课后

    EnrollAgg->>CourseAgg: 更新学员数(courseId)
    CourseAgg-->>CourseAgg: EnrollmentCountUpdated

9.4 识别出的限界上下文

限界上下文 核心聚合 子域类型
课程管理 Course、Chapter、Lesson 核心
学习体验 Enrollment、Progress、Certificate 核心
直播互动 LiveRoom、Session、QA 核心
交易 Order、Coupon 支撑
支付 Payment、Refund 支撑
用户 Student、Instructor 通用
通知 Notification 通用

9.5 效果

  • 2 小时的大图探索让所有人第一次看到了业务全貌;
  • 识别出了 12 个热点,其中 3 个涉及重大业务规则分歧;
  • 最终划分出 7 个限界上下文,对齐了 5 个开发团队;
  • 从工作坊到首个服务上线用了 6 周。

十、从事件风暴到代码

10.1 事件风暴产出与代码的映射

事件风暴概念 代码对应
橙色便利贴(事件) DomainEvent
蓝色便利贴(命令) Command 类或 API 端点
黄色大便利贴(聚合) Aggregate
紫色便利贴(策略) 事件处理器或 Saga
绿色便利贴(读模型) 查询服务或投影
白色便利贴(外部系统) 防腐层或适配器

10.2 Go 语言示例

// 命令
type CreateOrderCommand struct {
    CustomerID string
    CourseID   string
    CouponCode string
}

// 聚合
type Order struct {
    id         OrderID
    customerID CustomerID
    courseID    CourseID
    amount     Money
    discount   Money
    status     OrderStatus
    events     []DomainEvent
}

func NewOrder(cmd CreateOrderCommand, pricing PricingService) (*Order, error) {
    price, err := pricing.Calculate(cmd.CourseID, cmd.CouponCode)
    if err != nil {
        return nil, fmt.Errorf("计算价格失败: %w", err)
    }

    order := &Order{
        id:         NewOrderID(),
        customerID: CustomerID(cmd.CustomerID),
        courseID:    CourseID(cmd.CourseID),
        amount:     price.Original,
        discount:   price.Discount,
        status:     OrderStatusCreated,
    }

    order.events = append(order.events, OrderCreatedEvent{
        OrderID:    order.id,
        CustomerID: order.customerID,
        CourseID:   order.courseID,
        Amount:     order.amount,
        OccurredAt: time.Now(),
    })

    return order, nil
}

// 策略(事件处理器)
type EnrollmentPolicy struct {
    enrollmentRepo EnrollmentRepository
}

func (p *EnrollmentPolicy) OnOrderConfirmed(event OrderConfirmedEvent) error {
    enrollment, err := NewEnrollment(event.CustomerID, event.CourseID)
    if err != nil {
        return err
    }
    return p.enrollmentRepo.Save(enrollment)
}

// 读模型投影
type MyCoursesProjector struct {
    store ReadModelStore
}

func (p *MyCoursesProjector) OnStudentEnrolled(event StudentEnrolledEvent) error {
    view := MyCourseView{
        StudentID: event.StudentID.String(),
        CourseID:  event.CourseID.String(),
        Status:    "学习中",
        EnrolledAt: event.OccurredAt,
        Progress:  0,
    }
    return p.store.Save(view)
}

func (p *MyCoursesProjector) OnProgressUpdated(event ProgressUpdatedEvent) error {
    view, err := p.store.FindByCourseAndStudent(
        event.CourseID.String(), event.StudentID.String())
    if err != nil {
        return err
    }
    view.Progress = event.Percentage
    if event.Percentage >= 100 {
        view.Status = "已完成"
    }
    return p.store.Save(view)
}

十一、事件风暴的综合权衡

维度 事件风暴 传统需求分析 权衡点
参与度 高:所有角色同时参与 低:分角色串行 组织成本 vs 理解深度
时间投入 集中 2-4 小时 分散数周 前置投入 vs 持续投入
输出形式 便利贴墙(非正式) 正式文档 即时性 vs 可追溯性
异常覆盖 高:混沌探索自然发现 低:容易遗漏 发现未知 vs 确认已知
远程友好 中等:需要工具支持 高:文档天然异步 协作效果 vs 灵活性
适用规模 8-15 人 不限 深度讨论 vs 广泛覆盖
领域专家依赖 高:必须参与 中:可通过文档传递 知识质量 vs 可行性
学习曲线 低:便利贴人人会用 中:UML 等需要培训 上手速度 vs 表达精度

十二、下一步

事件风暴揭示了业务领域的事件流和上下文边界。当你需要将一个上下文与外部的遗留系统集成时,防腐层(Anti-Corruption Layer)和开放主机服务(Open Host Service)是两种关键的保护机制。

下一篇:防腐层与开放主机服务:系统集成的 DDD 方案


参考资料

  1. Brandolini, Alberto. Introducing EventStorming. Leanpub, 2021.
  2. Brandolini, Alberto. “Event Storming.” eventstorming.com.
  3. Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003.
  4. Vernon, Vaughn. Implementing Domain-Driven Design. Addison-Wesley, 2013.
  5. Fowler, Martin. “Domain Event.” martinfowler.com.
  6. Avram, Abel; Marinescu, Floyd. Domain-Driven Design Quickly. InfoQ, 2007.
  7. Young, Greg. “CQRS and Event Sourcing.” cqrs.files.wordpress.com, 2010.
  8. Rayner, Paul. “Event Storming Workshop Guide.” Virtual Genius, 2020.

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13 · architecture

【系统架构设计百科】架构质量属性:不只是"高可用高性能"

需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。

2026-04-13 · architecture

【系统架构设计百科】告警策略:如何避免"狼来了"

大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。

2026-04-13 · architecture

【系统架构设计百科】复杂性管理:架构的核心战场

系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略