返回架构笔记
Arch Day 10

Arch Day 10: DDD的争议与取舍

DDD不是信仰——它是一套在"领域复杂度高"的场景下降低认知负担的方法论。问题在于,太多团队在领域复杂度不高的场景下强行使用DDD,或者在使用DDD时犯了系统性错误,最终让DDD从"工具"变成了"负担"。

2026-04-09
第一阶段 - 架构基础
DDD争议过度设计Event-SourcingCQRS贫血模型反模式

日期: 2026-04-09 (Day 10) 阶段: 第一阶段 - 架构基础 标签: #DDD争议 #过度设计 #Event-Sourcing #CQRS #贫血模型 #反模式


核心概念

一句话定义

DDD不是信仰——它是一套在"领域复杂度高"的场景下降低认知负担的方法论。问题在于,太多团队在领域复杂度不高的场景下强行使用DDD,或者在使用DDD时犯了系统性错误,最终让DDD从"工具"变成了"负担"。

为什么资深架构师仍需关注

做了10年软件的人,对DDD通常有两种态度:要么是"DDD太好了,什么项目都该用"的传教士,要么是"DDD太重了,不实用"的怀疑者。两种都是错的。资深架构师需要的是:

  1. 知道DDD的边界:它解决什么问题?不解决什么问题?
  2. 识别过度设计的信号:聚合根太大、Repository膨胀、事件风暴(Event Storm)后没人看的文档
  3. 理解隐藏成本:Event Sourcing的调试地狱、CQRS的最终一致性噩梦、DDD战术模式的学习曲线
  4. 从失败中学习:能分析DDD实施失败的根因,并避免重蹈覆辙

常见误区与反模式

误区真相后果
"所有项目都需要DDD"DDD只在高领域复杂度场景有ROICRUD项目被迫写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战术模式选择矩阵

场景聚合根/实体值对象领域事件RepositoryDomain ServiceEvent SourcingCQRS
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天然走这条路,而传统系统需要刻意设计。

今日思考

  1. 你在过去的项目中是否遇到过DDD过度设计的情况? 回想一下,有没有哪些模块用了DDD战术模式(聚合根、领域事件等),但其实简单的Service+DTO就能搞定?当时为什么选择了DDD?是技术判断还是团队氛围驱动的?

  2. 贫血模型真的那么差吗? 在你的10年经验中,大部分项目的大部分模块是不是用贫血模型就足够了?只有核心领域(比如金融记账、风控规则)才真正需要充血模型?如何在一个项目中混用两种模型?

  3. 区块链强制的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。其他场景,用传统模型+审计日志表就足够了。


学习资源

  1. Martin Fowler - "AnemicDomainModel" - https://martinfowler.com/bliki/AnemicDomainModel.html
  2. Greg Young - "CQRS and Event Sourcing" - 经典演讲
  3. 《Domain-Driven Design Distilled》 - Vaughn Vernon - DDD精简版,适合评估适用性
  4. Udi Dahan - "Race conditions don't exist" - 对并发和聚合根边界的深度思考
  5. Alexey Zimarev - "Event Sourcing: You are doing it wrong" - ES反模式集合
  6. ThoughtWorks Tech Radar对DDD相关技术的评级 - 行业共识参考

明日预告

Day 11: 领域建模实战(金融级) —— 理论到此为止,明天开始真刀真枪。我们将实现金融级的领域模型:复式记账的Account/Entry/Transaction/Journal,处理BigDecimal陷阱和Money模式,解决并发控制(乐观锁/悲观锁)和幂等性设计。提供完整的TypeScript伪代码,这是你10年金融经验的直接变现。