模块边界定义原则:高内聚低耦合、单一职责、通用语言
在领域驱动设计(DDD)中,模块(Module)是限界上下文(Bounded Context)内部的逻辑分区,也是微服务拆分的前置决策单元。模块边界的质量直接决定了后续微服务内聚性、变更影响范围以及审批流故障的可追溯性。本节围绕三条核心原则展开:高内聚低耦合、单一职责、通用语言,并给出它们在模块划分中的具体应用方法。
1. 高内聚低耦合:模块间依赖最小化
高内聚指模块内部元素(类、聚合、领域服务)在功能或业务逻辑上紧密关联;低耦合指模块之间的依赖关系简单、明确、可替换。在模块划分时,应优先将同一业务变更原因的元素放在同一模块,将不同变更原因的元素分离到不同模块。
| 维度 | 高内聚表现 | 低耦合表现 |
|---|---|---|
| 变更影响 | 修改一个功能只需改动一个模块 | 修改一个模块不影响其他模块 |
| 依赖方向 | 模块内部依赖密集 | 模块间仅通过接口/事件交互 |
| 测试粒度 | 可独立进行单元测试 | Mock 外部模块即可完成集成测试 |
| 部署单元 | 模块可作为独立部署单元候选 | 模块替换不影响整体架构 |
实践检查点: 如果修改一个业务规则需要同时修改两个以上模块,说明内聚性不足或耦合度过高,应重新审视模块边界。
2. 单一职责原则(SRP)在模块划分中的应用
单一职责原则在模块层面意味着:一个模块只应有一个引起它变化的原因。在 DDD 中,这个“原因”通常对应一个业务能力或一个子域(Subdomain)。例如,“订单管理”模块不应同时处理“库存扣减”和“支付对账”,因为这三个职责分别对应不同的业务变更驱动。
- 按业务能力拆分: 每个模块对应一个明确的业务能力(如“报价计算”、“合同生成”、“审批流转”)。
- 按变更频率拆分: 将高频变更的业务逻辑与低频变更的稳定逻辑分离到不同模块。
- 按领域事件归属拆分: 同一领域事件的发布、订阅、处理逻辑应集中在同一模块内。
避坑提示: 不要将“数据表归属”作为模块划分的唯一依据。数据库表结构是技术实现细节,模块边界应由领域概念驱动,而非数据库范式。
3. 通用语言(Ubiquitous Language)驱动模块命名与边界
通用语言是 DDD 的核心实践,要求团队(业务专家、产品经理、开发人员)使用统一的术语进行沟通。在模块划分中,通用语言的作用体现在:
- 模块命名: 模块名称必须来自业务术语,而非技术术语。例如使用“理赔核算模块”而非“CalcEngineModule”。
- 边界验证: 如果业务专家无法理解模块名称或模块职责,说明边界划分与通用语言脱节。
- 代码与文档对齐: 模块内的包名、类名、方法名应与通用语言保持一致,减少认知转换成本。
常见反模式: 模块命名为“CommonUtil”、“BaseService”、“Helper”等模糊词汇。这类命名通常意味着职责不清晰,是模块边界腐化的前兆。
4. 三条原则的协同应用:模块划分决策流程
在实际建模中,三条原则需要协同使用。以下决策流程可帮助团队系统化地定义模块边界:
- 事件风暴(Event Storming): 识别领域事件、命令、聚合,形成初步的领域概念清单。
- 通用语言对齐: 与业务专家确认每个概念的业务含义,统一术语。
- 单一职责检查: 将同一业务变更原因的概念归入同一模块,不同原因的概念分离。
- 耦合度评估: 检查模块间的依赖关系,通过引入领域事件或防腐层(Anti-Corruption Layer)降低耦合。
- 边界命名与文档化: 使用通用语言为模块命名,并记录模块职责、依赖关系、核心聚合。
5. 案例:审批流模块边界划分
以企业审批流为例,展示三条原则如何指导模块边界定义:
| 业务概念 | 通用语言术语 | 归属模块 | 划分依据 |
|---|---|---|---|
| 审批表单定义 | 表单模板 | 表单管理模块 | 单一职责:表单模板变更独立于审批流转 |
| 审批节点配置 | 审批链 | 审批流引擎模块 | 高内聚:节点配置与流转逻辑紧密相关 |
| 审批人指派 | 审批人策略 | 审批流引擎模块 | 高内聚:与审批链配置同属一个变更原因 |
| 审批通知发送 | 通知事件 | 通知服务模块 | 低耦合:通过领域事件解耦,可独立扩展 |
| 审批历史记录 | 审批日志 | 审计日志模块 | 单一职责:日志存储与查询独立于业务逻辑 |
关键结论: 模块边界不是一成不变的。随着业务演进,通用语言可能调整,单一职责的粒度可能细化,耦合度也可能因新需求而变化。建议在每个迭代结束时重新审视模块边界,保持架构与业务的同步演进。