天天看点

DDD领域驱动设计实战(三)-深入理解实体(上)1 前言2 为什么使用实体3 唯一标识

1 前言

实体是领域模型中的领域对象。

MVC开发人员总将关注点放在数据,而非领域。因为在软件开发中,DB占据主导地位。首先考虑的是数据的属性(即数据库的列)和关联关系(外键关联),而不是富有行为的领域概念。

导致将数据模型直接反映在对象模型,那些表示领域模型的实体(Entity)被包含了大量getter/setter。虽然在实体模型中加入getter/setter并非大错, 但这不是DDD做法。

过于强调实体的作用却忽视了值对象。受到DB和持久化框架影响,实体被滥用,于是开始讨论如何避免大范围使用实体…

2 为什么使用实体

当我们需要考虑一个对象的个性特征或需要区分不同对象时,就引入实体这个领域概念。

一个实体是一个唯一的东西,可在一段时间内持续变化。

这些对象重要的不是属性,而是其延续性和标识,会跨越甚至超出软件生命周期。

也正是 唯一身份标识和可变性(mutability) 特征将实体对象区别于值对象。

实体建模并非总是完美。很多时候,一个领域概念应该建模成值对象,而非实体。这意味着DDD开发CRUD软件系统时可能更适用。但由于只从数据出发,CRUD系统是不能创建出好的业务模型的。

在可以使用DDD时,我们会将数据模型转变为实体模型。

通过标识区分对象,而非属性:

此时应将标识作为主要的模型定义。同时保持简单类定义,关注对象在生命周期中的连续性和唯一标识性。不应该通过对象的状态形式和历史来区分不同实体……对于什么是相同的东西,模型应该给出定义。

如何正确使用和设计实体呢?

3 唯一标识

在实体设计早期,关注能体现实体身份唯一性的主要属性和行为及如何查询实体,忽略次要的属性和行为。

设计实体时,首先考虑实体的本质特征,特别是实体的唯一标识和对实体的查找,而不是一开始便关注实体的属性和行为。只有在对实体的本质特征有用的情况下,才加入相应的属性和行为。

找到多种能够实现唯一标识性的方式,同时考虑如何在实体生命周期内维持唯一性。

实体的唯一标识不见得一定有助对实体的查找和匹配。将唯一标识用于实体匹配通常取决于标识的可读性。

比如:

若系统提供根据人名查找功能,但此时一个Person实体的唯一标识可能不是人名,因为重名情况很多

若某系统提供根据公司税号的查找功能,税号便可作为Company实体的唯一标识

值对象可用于存放实体的唯一标识。值对象是不变(immutable)的,这就保证了实体身份的稳定性,并且与身份标识相关的行为也可得到集中处理。便可避免将身份标识相关的行为泄漏到模型的其他部分或客户端中去。

3.1 创建实体身份标识的策略

每种技术方案都存在副作用。比如将关系型DB用于对象持久化时,这样的副作用将泄漏到领域模型。创建前需考虑标识生成的时间、关系型数据的引用标识和ORM在标识创建过程中的作用等,还会考虑如何保证唯一标识的稳定性。

3.2 标识稳定性

绝大多数场景不应修改实体的唯一标识,可在实体的整个生命周期中保持标识的稳定性。

可通过一些简单措施确保实体标识不被修改。可将标识的setter方法向用户隐藏。也可在setter方法种添加逻辑以确保标识在已存在时不再更新,比如可使用一些断言:

username属性是User实体的领域标识,该属性只能进行一次修改,并且只能在User对象内修改。setter方法setUsername实现了自封装性且对客户端不可见。当实体的public方法自委派给该setter方法时,该方法将检查username属性,看是否已被赋值。若是,表明该User对象的领域标识已经存在,程序将抛异常。

DDD领域驱动设计实战(三)-深入理解实体(上)1 前言2 为什么使用实体3 唯一标识

这个setter方法并不会阻碍Hibernate重建对象,因对象在创建时,它的属性都是使用默认值,且采用无参构造器,因此username属性的初始值为null。然后,Hibernate将调用setter方法,由于username属性此时为null,该 setter方法得以正确地执行,username属性也将被赋予正确的标识值。