领域驱动设计学习记录目录
实体(Entity)
工作以来,接触过很多项目,大多数项目上,大多数项目都会使用ORM,同时大多数项目上,ORM会变成一个单纯的数据库映射对象,没有任何行为,只有一堆setter
和getter
。一开始接收到的思想是,尽量保持实体的简单,只用来和数据库作为交互。当时觉得挺对的,但是当时没有注意到一个东西。那就是,如果要保持这个ORM对象简单,那是可以的,但是,如果直接拿这个ORM
对象作为Entity那么就会出现问题了。要说明为什么,需要慢慢解释、需要慢慢品味下文。
什么是Entity
Entity不是通过属性来定义的,而是通过它在其生命周期内的连续性和标识来定义的。
标识
标识很好解释,就是说如果需要比较两个Entity,那么,确定两个Entity是同一个Entity的充要条件就是,它们的标识是相同的。
所以标识需要具有唯一性以及不变性。
生命周期
生命周期是指一个对象的创建修改和销毁。
连续性
它指对象从被创建开始到被销毁的过程中,标识都没有被改变。(只是个人的理解,在领域驱动设计中并没有明确给出定义)
为什么需要Entity
为了将现实场景中的概念映射到我们的系统中,就需要有相应的程序中的概念与之对应,所以当需要表示一个唯一的东西,并且可以在相当长的一段时间内持续变化的时候,我们就需要用实体来表示。
比如一个人,5岁的时候和15岁的时候是同一个人,但是身体状况其实已经发生了翻天覆地的变化了。
要表示这样一个人,就需要用Entity。
唯一标识
标识的种类
创建唯一标识的策略从简单到复杂依次有:
-
用户提供一个或多个初始唯一值作为程序输入,程序应该保证这些初始值是唯一的:
比如用户输入username的标识,程序通过检查重复来确保唯一性。
-
程序内部通过某种算法自动生成身份标识,此时可以使用一些类库或框架。
比如UUID或GUID
-
程序依赖于持久化存储,比如数据库来生成唯一标识。
比如使用MySQL的自增ID来作为标识。
-
另一个限界上下文已经决定出唯一标识了,这作为程序的输入,用户可以在一组标识中进行选择。
比如上一篇中图书编辑上下文会依赖图书计划上下文的标识作为标识。
标识的生成时间
标识的生成时间分为两种:
-
对象创建的时候。
- 当需要提早知道Entity的标识,用来发送领域事件的时候。
- 当对象的equals方法是用标识作为比较的,然后想要一次性持久化多个对象时(如果标识在创建对象的时候没有生成,那么就会出现标识都是null,所以被视为同一个对象的情况)。
-
持久化对象的时候。
- 用于对对象的创建时间不是很关心的时候。
标识的稳定性
在绝大多数情况下,都不应该修改Entity的唯一标识。所以可以通过一些措施来确保标识不被修改。
- 如果能在创建对象的时候知道对象的标识,那么可以将标识设置成final来防止被改变。
- 如果不能在对象创建的时候知道对象的表示,那么可以通过在
setter
里面设置检查来防止标识被改变。
Entity的本质
当对于一个对象,我们在谈论的时候会提及”修改”这个词的时候,就应该将这个对象视为一个Entity。
重要属性
对于一个实体,首先需要确定它的关键属性,
- 到底需要什么样的属性。
- 属性是实体还是值对象。
- 属性是否需要自定义类型(多个限界上下文中都需要使用标识的时候,一个自定义类型会更明确某个字段代表的实体)。
关键行为
行为并不是简单的setter
和getter
,比如一个对象拥有active的布尔值,相比于setActive
,activate
+deactivate
会是更好的选择,因为这样客户端并不会关心到我们Entity中有什么属性,能实现更好的封装。
Entity扮演多个角色
一个Entity可以扮演多个角色,比如一个对象可以同时实现User和Person的接口。
- User角色的职责是负责账号密码管理的。
- Person角色的职责是负责地址信息,用户信息的管理的。
创建实体
- 创建不太复杂的对象可以通过构造函数来创建。
- 创建太过复杂的对象可以通过Factory来创建。
- 也可以通过相关的对象来创建,比如说由Tenant对象创建出User对象。
验证
-
验证单个属性
如果需要验证单个属性,直接在
setter
里面进行验证即可,而此时的setter
是被设置为private的,所有对象内部的属性设置都需要调用setter
。 -
验证整体对象
可以专门写一个
validate
方法,当整个对象创建完毕的时候,调用validate
方法来进行验证。 -
验证对象组合
可以通过领域时间的方式,当验证条件都满足的时候,再进行联合验证。