DDD的核心思想
通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型和代码模型的一致性
三步划定领域模型和微服务的边界
第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系、根据这些要素梳理出领域实体等领域对象
第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成的聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界
第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个界限上下文内,形成领域模型。在这个图里,限界上下文是第二层边界,这一层 边界可能就是未来微服务的边界,不同限界上下文的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界
实体和值对象
**实体的理解:**是一个具体的类对象,它们拥有唯一标识符(ID),且标识符在历经各种状态变更后仍能保持一致
**值对象的理解:**通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体(没有唯一标识符)
实体的业务形态
领域模型中的实体是多个属性、操作或行为的载体
在事件风暴中,我们可以根据命令、操作或者事件,找出产生这些行为的业务实体对象,进而按照一定的业务规则将依存度和业务紧密关联的对个实体对象和值对象进行聚类,形成聚合
实体和值对象是组成领域模型的基础单元
实体的代码形态
代码模型中,实体的表现形式是实体类,这个类包含了实体的属性和方法,通过这些方法实现自身的业务逻辑
在DDD里,这些实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现
实体的运行形态
实体以DO(领域对象)的形式存在,每个实体对象都有唯一的id
可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。但是,由于它们拥有相同的id,它们依然是同一个实体
实体的数据库形态
与传统数据模型设计优先不同,DDD是先构建领域模型,针对实际业务场景构架实体对象和行为,再将实体对象映射到数据持久化对象
- 在领域模型映射到数据模型时,一个实体可能对应0个、1个或者多个数据库持久对象大多数情况下实体和持久化对象是一对一
在某些场景中,有些实体只是暂驻静态内存的一个运行态实体,它不需要持久化。比如,基于多个价格配置数据计算后生成的折扣实体。 - 在有些复杂场景下,实体与持久化对象则可能是一对多或者多对一的关系。比如,用户 user 与角色 role 两个持久化对象可生成权限实体,一个实体对应两个持久化对象,这是一对多的场景。
- 有些场景为了避免数据库的联表查询,提升系统性能,会将客户信息 customer 和账户信息 account 两类数据保存到同一张数据库表中,客户和账户两个实体可根据需要从一个持久化对象中生成,这就是多对一的场景。
值对象的业务形态
本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。
而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。
值对象的代码形态
- 如果值对象是单一属性,则直接定义为实体类的属性;
- 如果值对象是属性集合,则把它设计为 Class 类,Class 将具有整体概念的多个属性归集到属性集合,这样的值对象没有 ID,会被实体整体引用。
值对象的运行形态
实体实例化后的 DO 对象的业务属性和业务行为非常丰富,但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外,其它业务行为就很少了。
值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式和序列化大对象的方式。
-
引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。以上面的代码为例:
-
引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入。以上面的代码为例:
值对象的数据库形态
DDD 引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变,减少数据库表的数量和表与表之间复杂的依赖关系,尽可能地简化数据库设计,提升数据库性能。
值对象在数据库持久化方面简化了设计,它的数据库设计大多采用非数据库范式,值对象的属性值和实体对象的属性值保存在同一个数据库实体表中。
在这块建议的具体做法是:
- 在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;
- 在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
以上面的代码为例,在领域建模时,我们可以把地址作为值对象,人员作为实体,这样就可以保留地址的业务涵义和概念完整性。而在数据建模时,我们可以将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表。
值对象的优势和局限
值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。
这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
在使用时要充分的考虑值对象的优缺点。
实体与值对象关系的理解
理解和分析聚合思想:聚合和聚合根
在事件风暴中,我们会根据一些业务操作和行为找出实体或值对象,进而将业务关联紧密的实体和值对象进行组合,构成聚合,再根据业务语义将多个聚合划定到同一个限界上下文中,并在限界上下文完成领域建模
对聚合的理解
聚合就是由业务和逻辑机密关联的实体和值对象组合而成的,聚合是数据修改后和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化
聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的
聚合在DDD分层架构中属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑
聚合内实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现
对聚合根的理解和分析
聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合,实体之间数据不一致性的问题
如果把聚合比作组织,那聚合根就是这个组织的负责人.聚合根也称为根实体,它不仅是实体,还是聚合的管理者
- 首先它作为实体本身,拥有实体的属性和业务行为,实现自身而定业务逻辑
- 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑
- 最后在聚合之间,他还是聚合对外的接口人,以聚合根Id关联的方式接受外部任务和请求,在上下文实现聚合之间的业务协同
聚合、聚合根、实体、值对象的对比
聚合 | 高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但不建议你对微服务过度拆分。 |
在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。 | |
一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。 | |
聚合根 | 聚合根是实体,有实体的特点,具有全局唯一标识,有独立的生命周期。 |
一个聚合只有一个聚合根,聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过id关联的方式实现聚合之间的协同 | |
实体 | 有id标识,通过id判断相等性,id在聚合内唯一 |
状态可变,它依附于聚合根,其生命周期由聚合根管理 | |
实体一般会持久化,但与数据库持久化对象不一定是一对一关系 | |
实体可以引用聚合内的聚合根、实体和值对象 | |
值对象 | 无id、不可变、无生命周期、用完即扔 |
值对象之间通过属性值判断相等性 | |
核心本质是值,是一组概念完整的属性组成的集合,用于描述实体的状态和特征 | |
值对象尽量只引用值对象 |