从零掌握CleanArchitecture领域模型:实体与值对象实战
你还在为业务代码混乱不堪而头疼?团队协作时总因模型设计分歧争论不休?本文将通过CleanArchitecture项目的真实案例,带你彻底搞懂实体(Entity)与值对象(Value Object)的设计精髓,学会用领域驱动设计(DDD)思想构建稳定可靠的业务核心。读完本文你将掌握:实体与值对象的核心区别、实战设计技巧、以及如何在.NET项目中落地实现。
领域模型的基石:实体与值对象
在CleanArchitecture中,领域模型是业务规则的核心载体。项目采用经典分层架构,领域层(Core)包含了所有业务实体与值对象,如sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/目录下的实现。这两种模式的正确应用,直接决定了系统的可维护性和扩展性。
实体(Entity):有身份的业务对象
实体是具有唯一标识的业务对象,其生命周期内标识保持不变。以项目中的Project类为例:
public class Project : EntityBase<Project, ProjectId>, IAggregateRoot
{
public ProjectName Name { get; private set; }
private readonly List<ToDoItem> _items = [];
public IEnumerable<ToDoItem> Items => _items.AsReadOnly();
public ProjectStatus Status => _items.All(i => i.IsDone) ? ProjectStatus.Complete : ProjectStatus.InProgress;
public Project(ProjectName name)
{
Name = name;
}
public Project AddItem(ToDoItem newItem)
{
Guard.Against.Null(newItem);
_items.Add(newItem);
var newItemAddedEvent = new NewItemAddedEvent(this, newItem);
base.RegisterDomainEvent(newItemAddedEvent);
return this;
}
}
实体的三大特征在代码中清晰体现:
- 唯一标识:继承
EntityBase<Project, ProjectId>,使用强类型ProjectId作为标识 - 生命周期:通过
AddItem等方法维护状态变化 - 领域行为:封装业务规则(如计算
Status属性)
值对象:无身份的不可变数据容器
值对象用于描述业务属性,没有唯一标识,通过属性值判断相等性。项目中的ProjectName是典型实现:
[ValueObject<string>(conversions: Conversions.SystemTextJson)]
public partial struct ProjectName
{
private static Validation Validate(in string name) =>
String.IsNullOrEmpty(name) ? Validation.Invalid("Name cannot be empty") : Validation.Ok;
}
值对象设计要点:
- 不可变性:所有属性设置为只读
- 值相等性:通过
GetEqualityComponents实现值比较 - 自我验证:在创建时验证数据有效性
实战应用:实体与值对象的协同工作
在CleanArchitecture中,实体与值对象通常配合使用。以项目任务管理场景为例:
// 创建项目(实体)
var project = new Project(new ProjectName("网站重构"));
// 添加任务(实体)
var task = new ToDoItem(Priority.High)
{
Title = "设计首页原型",
Description = "完成移动端响应式设计"
};
project.AddItem(task);
// 分配负责人(值对象操作)
task.AddContributor(contributorId);
实体与值对象的典型组合:
| 实体 | 包含的值对象 | 用途 |
|---|---|---|
Project | ProjectId, ProjectName | 项目基本信息 |
ToDoItem | ToDoItemId, Priority | 任务详情 |
Contributor | PhoneNumber | 贡献者联系方式 |
最佳实践与避坑指南
1. 何时使用实体vs值对象
- 实体:需要跟踪生命周期、有独立业务行为(如
Project.AddItem) - 值对象:仅描述属性、无业务行为、可整体替换(如
PhoneNumber)
2. 强类型ID的优势
项目广泛使用强类型ID,如ProjectId和ToDoItemId:
[ValueObject<int>]
public partial struct ToDoItemId;
相比int类型ID,强类型ID避免了参数顺序错误,提升代码可读性。
3. 领域事件的集成
实体可通过注册领域事件实现跨对象通信:
// 在ToDoItem中标记完成时触发事件
public ToDoItem MarkComplete()
{
if (!IsDone)
{
IsDone = true;
RegisterDomainEvent(new ToDoItemCompletedEvent(this));
}
return this;
}
总结与下一步
实体与值对象是CleanArchitecture的核心构建块,正确应用这两种模式能显著提升代码质量。通过本文介绍的:
- 实体的身份标识与生命周期管理
- 值对象的不可变性与自我验证
- 强类型ID与领域事件的最佳实践
你已具备设计清晰领域模型的基础。建议进一步阅读:
掌握这些设计模式,让你的.NET项目架构更清晰、业务逻辑更健壮!收藏本文,关注后续关于仓储模式与领域服务的深度解析。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



