课程名称:DDD(领域驱动设计)思想解读及优秀实践
课程章节: 战术设计
课程讲师: 尤达_技术咖啡
经典的 Java 三层架构对领域模型的设计。在这个三层架构中,领域逻辑被定义在业务逻辑层的 Service 对象中,至于反映了领域概念的领域对象则被定义为 Java Bean,这些 Java Bean 并没有包含任何领域逻辑,因此被放在了数据访问层。
这些 Java Bean 由于仅包含了访问私有字段的 get 和 set 方法,违背了面向对象设计原则的“对数据与行为进行封装”。”Martin Fowler 则将这种没有任何业务行为的对象称之为“贫血对象”。基于这样的贫血对象进行领域建模,得到的模型则被称之为“贫血模型”。
反之,如果对象的设计包含了属性和自身的行为的话,得到的模型则成为“充血模型”。
为什么要聊着两种模型呢?目的是引出下面两个在DDD中非常重要的概念,实体和值对象。
首先, 实体和值对象都是领域模型中的领域对象。 但是它们一定不是仅仅是普普通通的JavaBean或者POJO类就可以成为实体或者值对象。
上一节提到的贫血模型,就是在DDD概念中要尽量避免构建出来的领域对象。只有把对象的属性和行为都做好抽象和封装,才能发挥出面向对象设计和编程的强大威力。
那么到底啥是实体,啥是值对象?它们之间有什么区别呢? 这个地方在DDD里面是比较容易混淆的两个概念。
实体
一个典型的实体具有三个要素:身份标识,属性, 领域行为。
身份标识
身份标识类似于主键ID一样,是这个实体对象的唯一标识。
注意,包括 UUID 在内的随机数并不能支持分布式环境的唯一性,它需要特殊的算法,例如采用 SnowFlake 算法来避免在分布式系统内产生身份标识的碰撞
属性
属性,就是对象上的property, 如果通过ORM映射到数据库,就是表上的一个个的字段。 这个跟典型的三层架构中的DAO层的JavaBean的属性概念是基本类似的。
领域行为
领域行为,也就是实体对象上的方法。代表这个业务对象的各种业务操作。上面说的贫血对象和充血对象的核心差异点就在是否对象自身拥有领域行为上。
值对象
值对象:一般来说是否拥有唯一的身份标识才是实体与值对象的根本区别。
也就是说,值对象一般是一个具有不变性的无状态的对象。
举个例子:
如代码:Money 值对象的定义就保证了它的不变性。另外,值对象一般是需要依附于某一个实体来存在的。
某公司的实践中, 我们管依附于实体的值对象,称之为“内嵌对象”。我们的实现方式类似于下例所示:
聚合和聚合根
首先说一下聚合。简单的说聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
在公司的实践中,一个业务对象就是一个聚合。
而聚合根呢,就是这一个聚合中的N个实体中的最核心的那一个实体。
举个简单的例子。 有一个客户的聚合,其中包括了客户实体,线索实体,公海实体,地址值对象,账号值对象。
而这个聚合中,最核心的那个实体是客户实体,那么客户就是这个聚合的聚合根。其他的对象都是普通的聚合。
领域服务
对于一些复杂的业务,需要跨多个实体和值对象来进行业务操作,这时候,如果把相关操作封装在某个单一的实体中,是不合适的。这时候需要引入领域服务的概念。
领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。如果它需要暴露给用户接口层,领域服务就需要封装成应用服务。
资源库Repository
资源库(Repository)是对数据访问的一种业务抽象,使其具有业务意义。利用资源库抽象,就可以解耦领域层与外部资源,使领域层变得更为纯粹,能够脱离外部资源而单独存在。
资源库的概念跟之前经典的三层结构的DAO层有类似之处。不过资源库的概念更加的抽象,如下图:
资源库的理念,是一种典型的依赖倒置。