Appearance
DDD 基础概念
DDD 的基础概念可以分成两组:一组用于划边界,一组用于写模型。划边界时关注领域、子域、限界上下文和上下文映射;写模型时关注实体、值对象、聚合、领域服务、仓储和领域事件。更完整的业务主线说明见 DDD 模型详细说明。
概念关系
| 概念 | 主要回答的问题 | 工程判断 |
|---|---|---|
| 领域 | 系统解决哪一类业务问题 | 不从技术组件命名,而从业务能力命名 |
| 子域 | 哪些业务能力重要性不同 | 核心域重点投入,通用域优先复用成熟方案 |
| 限界上下文 | 一个模型在哪个范围内成立 | 同词不同义时要拆上下文,而不是做全局大对象 |
| 实体 | 哪个对象需要长期追踪身份 | 业务会追问“是哪一个”时,应建成实体 |
| 值对象 | 哪组值共同描述一个概念 | 业务只比较值是否相等时,优先建成值对象 |
| 聚合 | 哪些规则必须同时保持一致 | 围绕不变量划边界,不围绕数据库外键划边界 |
| 聚合根 | 谁控制聚合内部修改 | 外部只通过聚合根修改内部对象 |
| 领域服务 | 哪些规则不属于单个对象 | 承载领域行为,不做通用事务脚本 |
| 仓储 | 聚合如何保存和取回 | 领域层定义接口,基础设施层实现存储细节 |
| 领域事件 | 哪些业务事实已经发生 | 用过去式命名,只表达事实,不表达命令 |
容易混淆的地方
实体不是数据库表的对象映射。订单实体不只是 orders 表的一行,它应能保护订单状态流转、金额计算、订单项修改等业务规则。值对象也不是附属字段的集合,金额、地址、时间范围这些概念如果有自己的校验和比较规则,就值得独立建模。
聚合不是对象越多越完整。订单、库存、支付都有关系,但不代表它们应该放在同一个聚合里。聚合边界应围绕强一致规则:如果某条规则必须在一个事务中成立,就考虑放进同一聚合;如果可以通过事件、补偿或流程编排完成,就不要把边界扩大。
领域服务不是所有业务逻辑的收纳箱。单个实体或值对象能表达的规则,优先放回对象内部;只有跨对象、跨聚合且仍然属于领域规则的行为,才适合放进领域服务。否则系统会从贫血实体变成“胖服务”。
领域事件不是消息队列的技术别名。它先是业务事实,再考虑是否发布到消息中间件。事件命名和字段应站在业务视角,例如 OrderPaid 比 SendStockMessage 更稳定,因为前者描述事实,后者描述技术动作。
