DDD场景下如何正确设计附件模块的Aggregate Root
附件处理模块的DDD聚合根设计方案
识别聚合根不要被数据库表的外键关联误导,核心判断依据永远是业务一致性边界和实体生命周期独立性。针对你描述的场景,正确的设计是拆分为两个独立聚合,两个实体分别作为自身聚合的根,具体逻辑如下:
先厘清两个实体的核心业务职责
你当前的设计误区是先入为主将数据库层面的一对多关系直接映射为聚合内的包含关系,先把两个实体的本质拆清楚:
AttachmentType属于配置类实体:虽然只有4个固定类型,但它有独立的状态变更逻辑(启停配置、修改多文件上传规则),核心作用是作为附件上传的校验规则存在。它不需要、也不应该持有该类型下的所有附件——如果按原有设计,每次修改附件类型的启停状态都要加载其下成千上万条附件记录,既无业务必要,还会造成严重的性能问题。Attachment是核心业务实体:核心业务操作全围绕附件展开,附件具备完全独立的生命周期(上传、存储、下载、删除),不会因为所属附件类型被禁用就直接消失。
具体聚合设计
聚合1:以 AttachmentType 为聚合根
聚合内仅包含AttachmentType自身,移除原有代码中持有的_attachments集合,代码参考如下:
public class AttachmentType { public long Id { get; private set; } public string Name { get; private set; } public bool IsActive { get; private set; } public bool AllowMultipleFiles { get; private set; } public void Disable() => IsActive = false; public void Enable() => IsActive = true; public void UpdateMultipleFilesRule(bool allow) => AllowMultipleFiles = allow; public bool CanUploadNew(int currentTypeAttachmentCount) { if (!IsActive) return false; if (!AllowMultipleFiles && currentTypeAttachmentCount >= 1) return false; return true; } }
这个聚合负责所有和附件类型配置相关的逻辑,不需要关联任何附件实体。
聚合2:以 Attachment 为聚合根
聚合内仅包含Attachment自身,保留AttachmentTypeId作为对另一个聚合根的ID引用(注意不要直接持有AttachmentType的对象引用,符合聚合间仅通过ID关联的设计原则),代码参考如下:
public class Attachment { public long Id { get; private set; } public string FileName { get; private set; } public string Location { get; private set; } public long AttachmentTypeId { get; private set; } public void Rename(string newFileName) { if (string.IsNullOrWhiteSpace(newFileName)) throw new ArgumentException("文件名不能为空"); FileName = newFileName; } public void MoveTo(string newLocation) { if (string.IsNullOrWhiteSpace(newLocation)) throw new ArgumentException("存储路径不能为空"); Location = newLocation; } }
跨聚合业务规则的落地
你提到的“停用的附件类型不能上传新附件”“单文件类型不能重复上传”这类规则,不需要强行把两个实体放到同一个聚合里,只需要在应用服务层协调两个聚合完成校验即可:
上传附件的标准流程:
- 接收用户传入的附件类型ID、文件信息
- 加载对应
AttachmentType聚合根- 统计当前该类型已存在的附件数量
- 调用
AttachmentType.CanUploadNew()方法做上传校验,不通过直接返回错误- 校验通过后创建
Attachment实例,完成持久化
原有两个设计思路的问题
- 将
AttachmentType作为聚合根并持有全量附件:属于典型的数据模型驱动设计,完全违背DDD以业务行为为核心的思路,会带来不必要的性能损耗,和核心操作围绕附件的业务实际也不匹配。 - 将
AttachmentType实现为枚举类:忽略了它本身有独立的状态变更行为和业务规则,不是静态不变的枚举值,无法支持动态启停、修改规则的实际需求。
内容的提问来源于stack exchange,提问作者Merna Mustafa
相关产品推荐
相关产品推荐

