1288 字
6 分钟
DDD 战术设计:实体、值对象与聚合

战略设计决定了系统的骨架,战术设计则决定了骨架内部的肌肉。这篇笔记用尽量通俗的方式,讲清楚战术设计中几个最核心的概念。

实体(Entity):有身份的”东西”#

实体是有唯一标识、且会变化的对象。

类比:一个人。哪怕他改了名、换了工作、搬了家,他还是”这个人”——因为他的身份证号不变。

对应到代码,实体的关键是:

  • 唯一标识(ID);
  • 生命周期,状态会随业务变化;
  • 身份稳定,ID 是判断”是不是同一个”的标准。

比如订单、用户、账户,都是典型的实体。两个订单即使内容完全一样,也是两个不同的对象,因为它们的 ID 不同。

值对象(Value Object):只描述”值”的东西#

值对象是没有独立身份、完全由属性值决定的对象。

类比:一个地址。北京市朝阳区某某路 1 号——这就是它本身,你不需要给这个地址一个”ID”。换了个地址,就是换了个新对象。

值对象的特征:

  • 没有身份,两个内容相同的值对象就是”相等”的;
  • 不可变,值对象创建后不应被修改;
  • 常用来封装一些概念,如地址、金额、坐标、时间段。

比如”金额”可以做成值对象,包含 金额数值币种,而不是裸用一个 double。这样能避免”不同币种乱相加”这类低级错误。

实体 vs 值对象#

判断一个对象该做成实体还是值对象,就看它有没有独立身份是否可共享复用

判断维度实体值对象
是否有 ID没有
是否可变会变不可变
是否被共享独立拥有可被共享引用
例子订单、用户金额、地址

聚合(Aggregate):一组对象的边界#

聚合是 DDD 战术设计里最重要的概念。它把一组关联紧密的对象绑定在一起,由一个聚合根统一对外提供访问。

类比:一辆汽车。发动机、变速箱、轮子是它的部件,但你要启动汽车,只需要操作”启动按钮”(聚合根),不需要直接去操作每一个零件。

聚合的三个要点:

  1. 一个聚合根:对外唯一的入口,只有通过聚合根才能修改聚合内部的对象;
  2. 一致性边界:聚合内部的数据变更必须保持业务上的一致性;
  3. 数据访问最小化:外部不能绕过聚合根直接操作内部对象。

为什么需要聚合?#

聚合是为了解决事务一致性和并发控制的复杂度。如果不设聚合,所有对象之间都能随意互相引用、互相修改,数据一致性就完全失控了。

聚合有一个重要设计原则:优先用小聚合。聚合越小,事务范围越小,并发冲突越少,系统扩展性越好。不要贪大。

仓储(Repository):数据访问的抽象#

仓储负责在领域模型和持久化存储之间做桥接。它让领域层不需要关心数据到底存的是数据库、还是 Redis、还是文件。

类比:仓储就像书架。书架帮你把书放好、按需取回,但你不用关心书是放在书架的哪一格。

仓储的要点:

  • 面向聚合根设计,一个聚合对应一个仓储;
  • 只暴露领域需要的接口(如 findByIdsave),把数据库细节藏在实现里;
  • 让上层可以方便地替换持久化方案。

领域服务(Domain Service):放不下实体里的业务逻辑#

有些业务逻辑不属于某一个实体,而是跨多个实体/聚合协作。这时就用领域服务来承载。

类比:一次转账。转账涉及转出账户和转入账户两个对象,它既不属于转出账户,也不属于转入账户,于是把它放在一个”转账服务”里。

判断是不是领域服务的标准:这个操作是否不适合放在任何一个实体/值对象里。如果可以放,就不要硬造服务,否则会退化成”贫血模型”。

战术设计的小总结#

战术设计提供了一套建模工具箱:

概念作用
实体有身份的、会变化的核心对象
值对象无身份、不可变、按值相等的对象
聚合由聚合根统领的一致性边界
仓储聚合与持久化的桥接
领域服务承载跨对象的业务逻辑

这几个概念组合起来,就能把某个限界上下文内部建模得清晰、内聚。下一篇笔记,我会用一个实际的业务例子,把这些概念完整串起来,看看 DDD 到底怎么落地。

DDD 战术设计:实体、值对象与聚合
https://www.bczai.xyz/posts/ddd-tactical-design/
作者
边城仔
发布于
2026-08-24
许可协议
CC BY-NC-SA 4.0