Arch Day 10: DDD的争议与取舍
DDD不是信仰——它是一套在"领域复杂度高"的场景下降低认知负担的方法论。问题在于,太多团队在领域复杂度不高的场景下强行使用DDD,或者在使用DDD时犯了系统性错误,最终让DDD从"工具"变成了"负担"。
日期: 2026-04-09 (Day 10) 阶段: 第一阶段 - 架构基础 标签: #DDD争议 #过度设计 #Event-Sourcing #CQRS #贫血模型 #反模式
核心概念
一句话定义
DDD不是信仰——它是一套在"领域复杂度高"的场景下降低认知负担的方法论。问题在于,太多团队在领域复杂度不高的场景下强行使用DDD,或者在使用DDD时犯了系统性错误,最终让DDD从"工具"变成了"负担"。
为什么资深架构师仍需关注
做了10年软件的人,对DDD通常有两种态度:要么是"DDD太好了,什么项目都该用"的传教士,要么是"DDD太重了,不实用"的怀疑者。两种都是错的。资深架构师需要的是:
- 知道DDD的边界:它解决什么问题?不解决什么问题?
- 识别过度设计的信号:聚合根太大、Repository膨胀、事件风暴(Event Storm)后没人看的文档
- 理解隐藏成本:Event Sourcing的调试地狱、CQRS的最终一致性噩梦、DDD战术模式的学习曲线
- 从失败中学习:能分析DDD实施失败的根因,并避免重蹈覆辙
常见误区与反模式
| 误区 | 真相 | 后果 |
|---|---|---|
| "所有项目都需要DDD" | DDD只在高领域复杂度场景有ROI | CRUD项目被迫写6层架构 |
| "贫血模型=反模式" | 贫血模型在很多场景比充血模型更实用 | 强行充血导致代码难以理解 |
| "Event Sourcing是最佳实践" | ES在大多数场景是过度设计 | 调试、查询、数据迁移全部复杂化 |
| "聚合根越大越安全" | 大聚合根=高并发锁竞争 | 性能瓶颈+死锁 |
| "DDD=特定的代码结构" | DDD的核心是思维方式,不是目录结构 | 有了Entity/VO/Repository目录但没有领域逻辑 |
知识点详解
知识点1: DDD过度设计的信号
信号1: 聚合根过大 (God Aggregate)
症状:
- 一个聚合根有20+个属性、10+个方法
- 加载一个聚合根需要JOIN 5+张表
- 几乎所有业务操作都要经过同一个聚合根
- 并发冲突频繁(乐观锁retry不断)
例子(错误的):
class Order {
id: OrderId
customer: Customer // 包含完整客户信息
items: OrderItem[] // 包含完整商品信息
payment: Payment // 包含支付详情
shipping: Shipping // 包含物流详情
invoice: Invoice // 包含发票信息
reviews: Review[] // 包含评价
// ... 还有20个属性
}
// 任何操作(改地址、加评论、更新物流)都锁定整个Order
正确做法:
class Order {
id: OrderId
customerId: CustomerId // 只存引用
items: OrderItem[] // 核心不变量
status: OrderStatus
totalAmount: Money
addItem(item: OrderItem): void { ... }
submit(): void { ... }
cancel(): void { ... }
}
// Payment、Shipping、Invoice是独立聚合根,通过OrderId关联
判断标准: 聚合根应该保护的是不变量(Invariant),而不是所有相关数据。
问自己:如果把这个属性移到另一个聚合根,会违反什么业务规则?如果答案是"没有",那它不属于这个聚合根。
信号2: Repository膨胀
症状:
- OrderRepository有30+个查询方法
- findByCustomerIdAndStatusAndDateRange(...)
- findByProductIdWithItemsAndPayment(...)
- 每个新需求都要加一个Repository方法
根因: 把查询逻辑和命令逻辑混在了一起
解决方案: 读写分离
- Repository只负责命令(save/delete/findById)
- 查询走专门的QueryService或ReadModel(CQRS的思想)
信号3: 领域事件泛滥
症状:
- 每个setter都发一个事件
- OrderAddressChangedEvent, OrderPhoneChangedEvent, OrderNoteChangedEvent...
- 事件消费者很难跟踪到底有多少种事件
- 事件处理的顺序依赖变成了隐式的业务逻辑
正确做法:
- 领域事件应该代表有业务意义的状态变化
- "订单已提交"、"订单已支付"、"订单已发货" ✓
- "订单地址已变更" → 除非有业务流程关心地址变更,否则不需要事件
信号4: "架构宇航员"综合症
症状:
- 一个CRUD页面被实现为: Controller → Application Service → Domain Service
→ Repository → Domain Event → Event Handler → Read Model → Query Service
- 团队花80%时间在"架构层"上,20%在业务逻辑上
- 新人入职3个月还搞不清楚代码在哪里
根因: 把DDD当成了"必须的分层模板",而不是"在复杂度需要时才使用的工具"
知识点2: 贫血模型争议——到底该不该用?
争议核心:
Eric Evans和Martin Fowler都批评贫血模型(Anemic Domain Model)是反模式。但实际上:
贫血模型:
// 贫血模型 - 实体只有数据,没有行为
class Account {
id: string;
balance: number;
status: string;
}
// 业务逻辑在Service中
class AccountService {
transfer(from: Account, to: Account, amount: number): void {
if (from.balance < amount) throw new Error('余额不足');
if (from.status !== 'ACTIVE') throw new Error('账户不活跃');
from.balance -= amount;
to.balance += amount;
}
}
充血模型(Rich Domain Model):
// 充血模型 - 实体包含行为
class Account {
private _balance: Money;
private _status: AccountStatus;
withdraw(amount: Money): void {
this.ensureActive();
this.ensureEnoughBalance(amount);
this._balance = this._balance.subtract(amount);
this.addDomainEvent(new MoneyWithdrawn(this.id, amount));
}
deposit(amount: Money): void {
this.ensureActive();
this._balance = this._balance.add(amount);
this.addDomainEvent(new MoneyDeposited(this.id, amount));
}
private ensureActive(): void {
if (this._status !== AccountStatus.Active) {
throw new AccountNotActiveError(this.id);
}
}
private ensureEnoughBalance(amount: Money): void {
if (this._balance.lessThan(amount)) {
throw new InsufficientBalanceError(this.id, this._balance, amount);
}
}
}
客观对比:
| 维度 | 贫血模型 | 充血模型 |
|---|---|---|
| 学习曲线 | 低(大多数开发者熟悉) | 高(需要DDD知识) |
| 框架兼容 | 高(ORM/序列化友好) | 低(需要处理private字段、构造器) |
| 业务规则位置 | 分散在Service中 | 集中在实体中 |
| 可测试性 | Service需要mock依赖 | 实体可以纯单元测试 |
| 并发安全 | 需要Service层处理 | 聚合根自然形成事务边界 |
| 领域复杂度低时 | 完全够用,甚至更简单 | 过度设计 |
| 领域复杂度高时 | Service变成"上帝类" | 逻辑内聚,容易理解 |
我的观点(基于10年经验):
判断标准:
领域复杂度 = 低 (CRUD为主)
→ 用贫血模型。不要为了"DDD正确"而增加复杂度。
领域复杂度 = 中 (有业务规则但不复杂)
→ 用"战术DDD精简版": 值对象+领域服务,实体可以是半充血的
领域复杂度 = 高 (金融、保险、供应链等)
→ 用充血模型 + 完整DDD战术模式
知识点3: Event Sourcing什么时候是灾难?
Event Sourcing的诱人承诺:
- 完整的审计日志
- 可以重建任意时间点的状态
- 天然支持CQRS
- 领域事件驱动的架构
Event Sourcing的隐藏成本:
成本1: 查询复杂度
传统模型: SELECT * FROM accounts WHERE balance > 10000
ES模型: 需要重建所有账户的当前余额,然后过滤
解决方案: 必须配合CQRS的Read Model
代价: 维护两套数据模型(事件流+读视图),还要处理一致性问题
成本2: 事件版本演进
V1事件: { type: "OrderCreated", amount: 100 }
V2事件: { type: "OrderCreated", amount: { value: 100, currency: "USD" } }
问题: 旧事件怎么处理?
选项A: 写Upcaster(事件升级器) → 每次模型变更都要写
选项B: 快照+新事件 → 丢失历史细节
选项C: 保持向后兼容 → 模型越来越臃肿
成本3: 调试地狱
传统模型: 直接查数据库看当前状态
ES模型: 需要replay事件流才能理解为什么是当前状态
如果有500个事件,你需要跟踪每个事件的处理逻辑
更糟糕的: 如果事件处理器有bug,已经处理错了100万个事件
回滚?重新replay?停机维护?
成本4: 性能
加载一个实体:
传统模型: 1次数据库查询
ES模型: 加载N个事件 → replay → 得到当前状态
(有快照可以优化,但快照本身也需要维护)
何时Event Sourcing是合理的:
| 场景 | ES是否合理 | 理由 |
|---|---|---|
| 金融交易审计 | 是 | 监管要求完整的变更历史 |
| 协作编辑(如Google Docs) | 是 | 天然的事件流模型 |
| 游戏状态(如棋类) | 是 | 需要回放和分析 |
| 电商订单 | 可能 | 订单状态机适合事件驱动,但不一定需要ES |
| 用户管理CRUD | 否 | 过度设计 |
| CMS内容管理 | 否 | 过度设计 |
我的经验法则:
如果你不确定是否需要Event Sourcing,那你就不需要。
ES应该是"没有ES就无法满足需求"时才用,而不是"ES很酷所以用"。
知识点4: CQRS的隐藏成本
CQRS(Command Query Responsibility Segregation)看起来很简单:
命令(写)走一条路径
查询(读)走另一条路径
隐藏成本:
成本1: 最终一致性
用户提交了订单(Write) → 立刻查看订单列表(Read)
如果Read Model还没更新完 → 用户看不到自己刚下的订单
→ 投诉:"我的订单丢了!"
解决方案:
A) 写后读走Write Model(违背了CQRS初衷)
B) UI显示"订单处理中"(用户体验差)
C) 用WebSocket推送更新(增加复杂度)
D) Read Model的更新延迟控制在<100ms(需要强大的基础设施)
成本2: 两套模型的维护
每次领域模型变更:
1. 修改Write Model
2. 修改Read Model
3. 修改事件/投影逻辑
4. 写迁移脚本
5. 测试一致性
对比单模型:
1. 修改模型
2. 写迁移脚本
成本3: 基础设施复杂度
单模型: App → Database
CQRS: App → Command Handler → Event Store → Event Bus → Projector → Read Database → Query Handler
何时CQRS值得:
读写比例极其不均 (读:写 > 100:1) → 值得
读写模型差异大 (写是事务化的,读是聚合统计的) → 值得
写的一致性要求高,读的延迟容忍度高 → 值得
其他场景 → 三思
知识点5: DDD与微服务的关系——不是1:1映射
常见错误:
"每个Bounded Context = 一个微服务"
这导致:
- BC太细时: 微服务太多(运维灾难)
- BC太粗时: 微服务太大(失去微服务意义)
- BC边界调整时: 需要合并/拆分微服务(大规模重构)
正确理解:
Bounded Context 是逻辑边界(领域模型的适用范围)
微服务 是部署边界(独立部署和运行的单元)
关系:
- 一个BC可以包含多个微服务(按技术关注点拆分)
- 一个微服务可以包含多个小BC(当BC太小不值得独立部署时)
- BC边界是微服务拆分的参考,但不是唯一决定因素
其他决定微服务边界的因素:
- 团队所有权(谁维护)
- 部署频率(需要独立部署的频率)
- 扩展需求(需要独立扩展的维度)
- 技术栈差异(需要不同语言/框架)
知识点6: 3个DDD实施失败案例分析
案例1: "DDD驱动的CRUD"
公司: 某中型SaaS公司
项目: 内部运营管理系统
问题:
- 95%的功能是CRUD(用户管理/权限/配置)
- 团队学了DDD后,为每个实体都建了聚合根、Repository、领域事件
- 一个简单的"修改用户邮箱"需要经过:
UserCommandHandler → UserAggregateRoot.changeEmail()
→ UserEmailChangedEvent → EventHandler → UserReadModel更新
- 原本1天能做完的CRUD页面需要3天
教训: DDD的ROI与领域复杂度成正比。对于CRUD为主的系统,
事务脚本(Transaction Script)或活动记录(Active Record)更合适。
案例2: "聚合根边界之争"
公司: 某金融科技公司
项目: 支付平台
问题:
- 团队A认为"订单和支付应该是同一个聚合根"(强一致性)
- 团队B认为"订单和支付应该是不同聚合根"(独立部署)
- 争论了2个月没有结论
- 最终CEO拍板"用团队A的方案"(政治决策)
- 6个月后发现大聚合根导致并发瓶颈,不得不重构
教训:
1. 聚合根边界不是理论争论,而是可以用数据验证的
(并发量是多少?一致性SLA是什么?)
2. 如果不确定,先从小聚合根开始(合并比拆分容易)
3. 用ADR记录决策理由和预期验证方式
案例3: "Event Sourcing全面铺开"
公司: 某电商公司
项目: 全平台重构
问题:
- CTO在技术大会上被ES的演讲打动
- 决定整个平台用Event Sourcing重构
- 包括: 商品管理(CRUD)、用户管理(CRUD)、订单(适合ES)、库存(适合ES)
- 1年后:
- 商品管理团队花了50%时间处理事件版本演进
- 用户管理团队的读写延迟从5ms增加到200ms(replay开销)
- 只有订单和库存团队真正受益于ES
- 最终商品和用户管理回滚到传统模型
教训:
1. ES应该逐个模块评估,而不是"全面铺开"
2. 只在真正需要完整历史记录的领域使用ES
3. 技术选型不应该由"大会上听到的分享"驱动
对比分析
DDD适用性评估框架
| 维度 | 得分标准 | 权重 |
|---|---|---|
| 领域复杂度 | 高=3, 中=2, 低=1 | ×3 |
| 团队DDD经验 | 丰富=3, 一般=2, 无=1 | ×2 |
| 领域专家可用性 | 随时=3, 偶尔=2, 无=1 | ×2 |
| 项目生命周期 | >2年=3, 1-2年=2, <1年=1 | ×1 |
| 变更频率 | 高=3, 中=2, 低=1 | ×1 |
评分解读:
- 21-27分: 强烈推荐DDD (完整战略+战术)
- 15-20分: 推荐DDD精简版 (战略设计+部分战术)
- 9-14分: 不推荐DDD (事务脚本/活动记录更合适)
DDD战术模式选择矩阵
| 场景 | 聚合根/实体 | 值对象 | 领域事件 | Repository | Domain Service | Event Sourcing | CQRS |
|---|---|---|---|---|---|---|---|
| CRUD管理系统 | 不需要 | 可选 | 不需要 | 不需要(用ORM) | 不需要 | 不需要 | 不需要 |
| 简单业务逻辑 | 可选 | 推荐 | 可选 | 可选 | 推荐 | 不需要 | 不需要 |
| 复杂业务规则 | 推荐 | 推荐 | 推荐 | 推荐 | 推荐 | 可选 | 可选 |
| 金融/记账 | 必要 | 必要 | 必要 | 必要 | 必要 | 推荐 | 推荐 |
| 协作/实时系统 | 推荐 | 推荐 | 必要 | 推荐 | 推荐 | 推荐 | 推荐 |
架构设计实操
实操: "何时不该用DDD"指南 + DDD适用性评估框架
指南:何时不该用DDD
## 不该用DDD的10个信号
### 信号1: 项目90%是CRUD
如果大部分功能是数据的增删改查,没有复杂的业务规则和状态转换,
DDD的投入产出比很低。用Active Record或Transaction Script更高效。
### 信号2: 团队没有DDD经验且无学习时间
DDD的学习曲线约3-6个月。如果项目deadline很紧且团队没有DDD基础,
强行引入会导致误用和过度设计。
### 信号3: 没有领域专家参与
DDD的核心是与领域专家建立Ubiquitous Language。如果业务方不参与
或不配合,DDD就变成了"开发者自己想象的领域模型"。
### 信号4: 项目寿命短(<6个月)
DDD的前期投入较大(建模、讨论、重构)。如果项目是短期的概念验证
或一次性工具,这些投入无法回收。
### 信号5: 领域模型频繁完全推翻
如果业务方向每个月都在变,领域模型还没稳定就被推翻,
花大量时间建模反而是浪费。不如用更轻量的方式快速迭代。
### 信号6: 团队规模太小(<3人)且全栈
如果2个全栈开发者能在脑子里装下整个系统的复杂度,
DDD的"显式化"建模反而增加了不必要的形式化开销。
### 信号7: 技术复杂度 >> 领域复杂度
如果项目的挑战是高并发/高性能/分布式,而不是业务规则复杂,
那应该投入在技术架构上而非领域建模上。
### 信号8: 开发团队把DDD当成"目录结构模板"
如果团队的DDD实践只是创建了entities/、valueObjects/、repositories/
目录,里面的代码和以前一样,那就是形式主义。
### 信号9: Event Sourcing被当成默认选项
如果团队默认用ES实现所有聚合根,而不是在需要时才用,
那很快会被ES的复杂度淹没。
### 信号10: 聚合根边界争论超过1周没结论
如果团队在聚合根边界上争论不休,可能说明领域理解还不够深,
或者这个区域的复杂度不足以需要DDD级别的精细建模。
DDD适用性评估问卷:
## DDD适用性快速评估(5分钟)
请对以下问题打分(1-5分):
□ Q1: 你的领域有多少条核心业务规则(不是CRUD)?
1=几乎没有 3=中等(20-50条) 5=非常多(100+条)
□ Q2: 同一个业务概念在不同场景下含义是否不同?
1=基本一致 3=有些差异 5=差异很大
□ Q3: 业务专家是否愿意且有时间参与设计讨论?
1=完全不可能 3=偶尔可以 5=随时配合
□ Q4: 团队中有多少人了解DDD?
1=没人 3=1-2人 5=大部分人
□ Q5: 这个系统预期维护多长时间?
1=<6个月 3=1-3年 5=5年以上
□ Q6: 变更的主要驱动力是什么?
1=UI变化 3=混合 5=业务规则变化
评分:
24-30: 强烈推荐DDD
16-23: 选择性使用DDD(战略设计+部分战术)
6-15: 不推荐DDD,使用更轻量的方法
AI增强实践
AI在DDD争议中的应用
1. 聚合根边界决策辅助
Prompt: "我有以下实体关系:
- Order包含OrderItems(1-50个)
- Order关联Payment(1-N,可能多次支付)
- Order关联Shipping(1个)
- 业务规则:
1. 所有OrderItems的总价=Order的totalAmount(不变量)
2. Payment的总额≥totalAmount时订单状态变为'已支付'
3. Shipping创建时需要Order地址
请分析:
1. 应该如何划分聚合根边界?
2. 哪些是同一个聚合根的不变量?
3. 哪些可以是独立聚合根通过事件关联?"
2. Event Sourcing适用性评估
Prompt: "我的系统有以下特征:
- [描述读写比例]
- [描述审计需求]
- [描述查询模式]
- [描述团队经验]
请评估Event Sourcing是否适合,如果不适合,推荐替代方案。"
3. 用AI审查DDD实现
Prompt: "以下是我的聚合根代码:
[粘贴代码]
请检查:
1. 聚合根是否保护了不变量?
2. 是否有过大或过小的问题?
3. 值对象是否正确使用?
4. 是否有应该是领域事件但缺失的状态变化?"
AI vs 人工边界:
| 任务 | AI能力 | 人工不可替代 |
|---|---|---|
| 检查聚合根不变量完整性 | 优秀 | - |
| 评估DDD模式的适用性 | 良好 | 对项目上下文的深度理解 |
| 生成领域模型草案 | 良好(需要人工验证) | 业务专家的隐性知识 |
| 识别过度设计 | 中等 | 需要对团队能力和项目节奏的判断 |
| 分析失败案例 | 良好 | 亲身经历的失败教训更深刻 |
与Web3/DeFi的关联
| 传统DDD争议 | Web3对应 | 关键差异 |
|---|---|---|
| 聚合根大小争议 | 智能合约的功能范围争议 | 合约越大Gas越高,天然倒逼"小聚合" |
| 贫血模型vs充血模型 | 合约内逻辑vs链下逻辑 | Solidity合约天然是充血模型(数据+行为) |
| Event Sourcing | 区块链本身就是Event Sourcing | 链上天然事件溯源,不需要额外实现 |
| CQRS | 链上写/Indexer读 | Web3天然是CQRS(写上链,读从Indexer) |
| DDD过度设计 | 合约过度设计=Gas浪费 | Gas成本迫使Web3开发者保持简洁 |
深度洞察:区块链是天然的Event Sourcing + CQRS
区块链 = Event Store
├── 每个区块 = 一批事件(交易)
├── 状态 = 从创世区块replay所有事件得到
├── 不可变 = 事件一旦写入不可修改
└── 完整历史 = 可以重建任意时间点的状态
The Graph/Indexer = Read Model
├── 索引链上事件 → 构建查询友好的数据结构
├── 最终一致性 = 索引有延迟
└── 可以有多个Read Model(不同的Subgraph)
写路径: 用户 → 钱包签名 → 交易上链 → 区块确认
读路径: 用户 → The Graph/RPC → 查询索引数据
这就是CQRS!Web3天然走这条路,而传统系统需要刻意设计。
今日思考
-
你在过去的项目中是否遇到过DDD过度设计的情况? 回想一下,有没有哪些模块用了DDD战术模式(聚合根、领域事件等),但其实简单的Service+DTO就能搞定?当时为什么选择了DDD?是技术判断还是团队氛围驱动的?
-
贫血模型真的那么差吗? 在你的10年经验中,大部分项目的大部分模块是不是用贫血模型就足够了?只有核心领域(比如金融记账、风控规则)才真正需要充血模型?如何在一个项目中混用两种模型?
-
区块链强制的Event Sourcing + CQRS模式,对DApp开发者意味着什么? 传统开发者可以选择用不用ES/CQRS,但Web3开发者没有选择。这种"强制约束"反而可能让Web3系统在审计和追溯方面天然优于传统系统?
面试题准备
Q1: DDD最容易犯的错误是什么?
30秒版本: 三个最常见的错误:一是在领域复杂度不高的项目强行使用DDD导致过度设计;二是聚合根画得太大导致并发瓶颈和性能问题;三是把DDD当成代码模板而非思维方式,有了Entity/VO/Repository目录但没有真正的领域建模。
2分钟版本:
第一个错误是适用性判断失误。DDD的ROI与领域复杂度成正比。对于CRUD系统、短生命周期项目、或者领域变化太快的探索期产品,DDD的前期投入无法回收。我见过的最典型案例是一个内部管理系统,95%是CRUD,但团队强行用了完整的DDD战术模式,一个简单的"修改用户信息"功能被实现为7层调用链。
第二个错误是聚合根边界错误。最常见的是聚合根画得太大——把所有相关实体都塞进一个聚合根。这会导致加载慢、并发锁竞争高、事务范围太大。正确做法是聚合根只保护不变量,其他相关数据通过ID引用。如果不确定边界,先从小聚合根开始,因为合并比拆分容易。
第三个错误是忽视战略设计直接跳到战术设计。团队一上来就定义Entity、Value Object、Repository,但没有做Context Mapping和Bounded Context识别。结果是在错误的边界内做了精细的建模,最后发现整个模型需要推翻。
追问准备:
追问: "聚合根边界画错了怎么办?" 回答: 分两种情况。如果是画得太大,拆分相对容易——将不需要强一致性保护的部分独立为新的聚合根,通过领域事件保持最终一致性。如果是画得太小,需要合并,这通常涉及数据迁移和事务范围的调整,复杂度更高。所以我的建议是"宁小勿大"——从最小的不变量集合开始。
Q2: Event Sourcing什么时候不该用?
30秒版本: 三种不该用的场景:一是领域主要是CRUD、没有复杂状态转换时,ES是过度设计;二是查询模式复杂且延迟敏感时,ES的replay开销和最终一致性是问题;三是团队没有ES经验且项目有时间压力时,学习成本太高。
2分钟版本:
Event Sourcing有四个隐藏成本经常被低估。
第一,查询成本。ES的写模型是事件流,查询需要先replay或维护Read Model。如果你的系统是读多写少(比如90%是查询),那维护Read Model的成本远超传统模型。
第二,事件演进成本。当领域模型变化时,旧事件需要升级(Upcasting)。每次模型变更都要写升级逻辑,还要保证升级不破坏已有数据。在业务快速变化的阶段,这个成本是灾难性的。
第三,调试成本。传统模型直接查数据库看当前状态。ES需要replay事件流来理解"为什么是这个状态"。当事件流很长或有并发事件时,调试极其困难。
第四,团队认知成本。ES是一种非常不同的思维模式。大多数开发者习惯的是"查询-修改-保存",而ES是"发出事件-replay得到状态"。团队需要3-6个月才能熟练掌握。
我的判断标准:只在必须有完整审计历史(如金融交易)或天然事件驱动(如协作编辑)的场景使用ES。其他场景,用传统模型+审计日志表就足够了。
学习资源
- Martin Fowler - "AnemicDomainModel" - https://martinfowler.com/bliki/AnemicDomainModel.html
- Greg Young - "CQRS and Event Sourcing" - 经典演讲
- 《Domain-Driven Design Distilled》 - Vaughn Vernon - DDD精简版,适合评估适用性
- Udi Dahan - "Race conditions don't exist" - 对并发和聚合根边界的深度思考
- Alexey Zimarev - "Event Sourcing: You are doing it wrong" - ES反模式集合
- ThoughtWorks Tech Radar对DDD相关技术的评级 - 行业共识参考
明日预告
Day 11: 领域建模实战(金融级) —— 理论到此为止,明天开始真刀真枪。我们将实现金融级的领域模型:复式记账的Account/Entry/Transaction/Journal,处理BigDecimal陷阱和Money模式,解决并发控制(乐观锁/悲观锁)和幂等性设计。提供完整的TypeScript伪代码,这是你10年金融经验的直接变现。