3. 支付订单领域分析:识别核心子域与通用语言建立
在领域驱动设计(DDD)中,领域分析的第一步是识别核心子域、支撑子域和通用子域。对于支付订单系统,我们通过业务事件风暴和用例分析,将整个业务域拆解为四个核心子域:订单、支付、账户和库存。每个子域拥有独立的业务逻辑、数据边界和演化节奏。
3.1 核心子域识别矩阵
下表从业务价值、复杂度、变更频率三个维度对比四个子域,帮助团队建立统一的优先级认知。
| 子域 | 业务价值 | 业务复杂度 | 变更频率 | DDD 分类 |
|---|---|---|---|---|
| 订单子域 | 核心流程入口 | 高(状态机、超时、拆单) | 中 | 核心子域 |
| 支付子域 | 资金流转核心 | 极高(渠道对接、对账、幂等) | 高 | 核心子域 |
| 账户子域 | 用户资产安全 | 高(余额、冻结、流水) | 低 | 支撑子域 |
| 库存子域 | 实物/虚拟资源 | 中(预占、释放、超卖) | 中 | 支撑子域 |
实践提示:支付子域通常需要独立部署,因为它对接多个外部支付网关,变更频率最高。订单子域作为流程编排者,应保持对支付和库存的最终一致性依赖,而非强事务。
3.2 通用语言(Ubiquitous Language)建立
通用语言是 DDD 的基石。以下是我们为支付订单系统定义的统一术语表,所有团队成员(产品、开发、测试、运维)在沟通时必须使用这些词汇。
| 术语 | 定义 | 所属子域 |
|---|---|---|
| 订单(Order) | 用户购买行为的业务载体,包含商品明细、金额、收货信息 | 订单 |
| 支付单(Payment) | 针对一笔订单发起的支付请求记录,与订单 1:N 关系(支持拆单支付) | 支付 |
| 交易流水(Transaction) | 支付网关返回的不可变资金变动记录,用于对账和审计 | 支付 |
| 账户(Account) | 用户在平台内的资金容器,包含可用余额和冻结余额 | 账户 |
| 库存预占(Reservation) | 下单时临时锁定库存,防止超卖,超时未支付则释放 | 库存 |
| 幂等键(Idempotent Key) | 全局唯一标识,用于保证同一请求多次提交只生效一次 | 支付/订单 |
| 结算(Settlement) | 支付成功后,资金从支付渠道划入平台账户的过程 | 账户 |
避坑指南:避免使用“订单支付”“支付订单”等模糊组合词。在通用语言中,“订单”和“支付单”是两个不同的聚合根,各自拥有独立的生命周期和状态机。
3.3 子域上下文边界与交互关系
下图展示了四个子域之间的核心交互流程,以及每个子域对外暴露的领域服务接口。
3.4 子域间的领域事件
子域之间通过领域事件实现松耦合通信。以下是核心领域事件清单:
| 事件名称 | 发布子域 | 订阅子域 | 触发条件 |
|---|---|---|---|
OrderCreated |
订单 | 库存、支付 | 用户提交订单成功 |
PaymentSucceeded |
支付 | 订单、账户 | 支付网关返回成功 |
PaymentFailed |
支付 | 订单 | 支付网关返回失败或超时 |
InventoryReserved |
库存 | 订单 | 库存预占成功 |
InventoryReleased |
库存 | 订单 | 订单取消或超时未支付 |
AccountCredited |
账户 | 支付 | 资金成功入账 |
架构决策:领域事件采用事件总线(Event Bus)异步投递,配合本地消息表保证至少一次投递。支付成功事件是系统中最关键的事件,必须支持幂等消费。
3.5 通用语言在代码中的映射
通用语言不仅停留在文档中,还要直接映射到代码的包结构和类名。以下是一个典型的 Java 项目模块划分:
com.payment.order
├── order-subdomain
│ ├── domain
│ │ ├── aggregate
│ │ │ └── Order.java
│ │ ├── valueobject
│ │ │ ├── OrderId.java
│ │ │ ├── Money.java
│ │ │ └── OrderStatus.java
│ │ └── event
│ │ └── OrderCreatedEvent.java
│ └── application
│ └── OrderService.java
├── payment-subdomain
│ ├── domain
│ │ ├── aggregate
│ │ │ └── Payment.java
│ │ ├── valueobject
│ │ │ ├── PaymentId.java
│ │ │ └── IdempotentKey.java
│ │ └── event
│ │ └── PaymentSucceededEvent.java
│ └── application
│ └── PaymentService.java
├── account-subdomain
│ ├── domain
│ │ ├── aggregate
│ │ │ └── Account.java
│ │ └── valueobject
│ │ ├── AccountId.java
│ │ └── Balance.java
│ └── application
│ └── AccountService.java
└── inventory-subdomain
├── domain
│ ├── aggregate
│ │ └── Inventory.java
│ └── valueobject
│ ├── SkuId.java
│ └── Reservation.java
└── application
└── InventoryService.java
编码规范:每个子域的
domain 包对外部子域完全不可见,只通过 application 层的服务接口暴露能力。聚合根命名必须与通用语言完全一致,例如 Order、Payment、Account、Inventory。
3.6 子域依赖与通信模式
下图展示了子域之间的依赖方向与通信模式,强调核心子域(订单、支付)不应直接依赖支撑子域的内部实现。
关键原则:订单子域作为流程编排者,只依赖支付和库存的防腐层(Anti-Corruption Layer)接口,不直接依赖内部实现。支付子域对账户子域的调用同样遵循此原则。这种设计保证了每个子域可以独立演进、独立部署。
3.7 本章小结
- 四个核心子域:订单、支付、账户、库存,各自拥有独立的聚合根和数据库。
- 通用语言:统一了 7 个核心术语的定义,确保跨团队沟通无歧义。
- 领域事件:6 个关键事件驱动子域间的最终一致性协作。
- 代码映射:包结构直接反映子域边界,聚合根命名与通用语言一致。
- 通信模式:同步 RPC 用于实时性要求高的场景,异步消息用于最终一致性场景。