DDD设计原则
本文档引用的文件
- MyEntityName.cs
- IMyEntityNameRepository.cs
- MyEntityNameAppService.cs
- MyEntityNameDto.cs
- MyEntityNameSpecification.cs
- MyEntityNameConsts.cs
- MyEntityNameEto.cs
- MyEntityNameDataSeedContributor.cs
目录
- 引言
- 聚合根设计与职责边界
- 实体标识与生命周期管理
- 构造函数与属性保护机制
- 业务方法封装与一致性保障
- 值对象识别与提取建议
- 领域层与应用层解耦
- 领域服务与领域事件的潜在应用
- 违反DDD原则的风险示例
- 正确实现方式与最佳实践
引言
本项目采用领域驱动设计(DDD)作为核心架构原则,通过清晰的分层结构和领域模型设计,确保业务逻辑的高内聚与低耦合。MyEntityName作为核心聚合根,体现了DDD在实际项目中的典型应用。本文将深入分析其设计原理与实现细节。
聚合根设计与职责边界
MyEntityName实体继承自FullAuditedAggregateRoot<Guid>,表明其为一个具备完整审计功能的聚合根。作为聚合根,它承担了以下职责:
- 一致性边界:所有对
MyEntityName内部状态的修改必须通过其自身提供的方法进行,确保业务规则的一致性。 - 唯一标识管理:使用
Guid作为全局唯一标识符,避免主键冲突。 - 生命周期控制:通过仓储(Repository)模式统一管理实体的创建、更新和删除操作。
- 事务边界:聚合根是数据库事务的最小单位,所有变更应在同一事务中完成。
该设计确保了领域模型的完整性,防止外部对象直接修改内部状态导致数据不一致。
Section sources
实体标识与生命周期管理
MyEntityName使用Guid作为主键类型,继承自FullAuditedAggregateRoot<Guid>,自动包含创建时间、创建人、最后修改时间、最后修改人等审计字段。这种设计具有以下优势:
- 全局唯一性:
Guid保证了分布式环境下的标识唯一性。 - 不可变性:一旦生成,标识符不可更改,符合聚合根的基本原则。
- 生命周期追踪:通过审计字段,可完整追踪实体的整个生命周期。
实体的生命周期由IMyEntityNameRepository统一管理,遵循标准的CRUD流程,确保所有变更都经过领域层的业务规则校验。
Section sources
构造函数与属性保护机制
MyEntityName定义了两个构造函数:一个受保护的无参构造函数用于ORM框架反序列化,另一个为带参数的构造函数用于业务创建。
所有属性均采用protected set访问控制,确保:
- 外部无法直接修改属性值
- 所有状态变更必须通过领域方法进行
- 在赋值时可执行业务规则校验
例如,在构造函数中调用Check.NotNullOrWhiteSpace()进行参数验证,确保Code和Name不为空且长度符合限制(由MyEntityNameConsts定义),从源头保障数据完整性。
Diagram sources
业务方法封装与一致性保障
MyEntityName通过封装业务方法确保规则一致性:
- Update方法:更新实体信息时,重新执行与构造函数相同的校验逻辑,确保数据始终有效。
- AdjustSort方法:专门用于调整排序值 ,避免直接暴露
Sort属性的修改权限。 - Clone方法:提供克隆能力,复用已有数据创建新实例,符合开闭原则。
这些方法将业务规则内聚于领域模型内部,避免应用层绕过规则直接操作数据。
Section sources
值对象识别与提取建议
当前代码中存在潜在的值对象可提取场景:
Code、Name、Remark等字符串字段虽有长度限制,但目前作为普通属性存在。可考虑提取为IdentifierCode、DisplayName等值对象,封装格式校验与业务含义。- 排序逻辑涉及多个实体的相对顺序调整,
SortOrder可作为值对象封装排序规则。
值对象的特点是:
- 无唯一标识
- 通过属性值判断相等性
- 不可变性
- 可跨聚合复用
提取值对象有助于进一步提升领域模型的表达力与复用性。
Section sources
领域层与应用层解耦
项目严格遵循分层架构,实现领域层与应用层解耦:
- 领域层(Domain):包含
MyEntityName实体、仓储接口、规约(Specification)等,专注业务逻辑。 - 应用层(Application):
MyEntityNameAppService仅 orchestrator 角色,调用领域对象和仓储,不包含核心业务规则。
例如,MyEntityNameAppService.CreateAsync方法中:
- 调用仓储检查名称唯一性
- 创建
MyEntityName实例(触发构造函数校验) - 持久化到数据库
核心校验逻辑仍在领域层,应用层仅协调流程,符合“将业务逻辑放在领域层”的DDD原则。
Diagram sources
领域服务与领域事件的潜在应用
尽管当前未实现,但存在领域服务与领域事件的应用场景:
- 领域服务:当业务逻辑涉及多个聚合或外部系统时,应引入领域服务。例如批量调整排序涉及多个
MyEntityName实例,可封装为MyEntityNameSortingService。 - 领域事件:当
MyEntityName被创建或更新时,可发布MyEntityNameCreatedEto或MyEntityNameUpdatedEto事件,通知其他模块(如缓存更新、日志记录)。
Diagram sources
违反DDD原则的风险示例
若违反DDD原则,可能出现以下风险:
- 贫血模型:若将校验逻辑移至应用层,领域对象退化为数据容器,失去业务含义。
- 事务边界模糊:跨多个聚合直接操作数据,导致事务过大或数据不一致。
- 重复代码:相同校验逻辑在多处出现,增加维护成本。
- 测试困难:业务规则分散,难以进行单元测试。
例如,若允许直接设置Sort属性而不通过AdjustSort方法,则可能破坏排序业务规则。
正确实现方式与最佳实践
正确的DDD实现方式包括:
- 富领域模型:将业务规则封装在实体内部
- 聚合边界清晰:每个聚合有明确的边界和一致性规则
- 仓储抽象:通过接口隔离数据访问细节
- 分层职责明确:领域层专注业务,应用层专注流程
- 使用规约模式:
MyEntityNameSpecification封装查询条件,提高可读性与复用性
遵循这些实践可构建高内聚、低耦合、易于维护的领域模型,为系统长期演进奠定坚实基础。
Section sources