背景
在DDD中有两个比较重要的对象,即值对象和实体。而聚合根就是由这两个对象组成的,所以业务建模前我们都会先定义好实体和值对象,然后再构建聚合根,所以再研究复杂的聚合根之前我们先来研究相对简单但是基础的值对象和实体吧。
定义
- 实体:实体以 DO(领域对象)的形式存在,拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致(唯一标识)。实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现。简单来说就是有唯一标识+业务行为方法。
- 值对象:与实体相对应的就是值对象,如果没有唯一标识就是值对象。值对象一般是嵌套在实体里面的。嵌套方式可能有两种,一种是直接嵌套整个Java对象,二是嵌套一个json对象。其次值对象一般是不会改变的,变化的时候也是整体替换。值对象没有太多的业务行为更多的是对实体的一个修饰和描述,离开了实体就没有任何存在的业务意义
举例说明
拿常见的电商项目来说,每个用户一般会有一个唯一标识比如身份证。或者唯一id,而用户会有收货地址,收货地址是没有唯一标识的,是依赖实体而存在的,没有用户地址也就失去了存在的意义。所以收货地址是值对象。
两者对应的关系大致是如下
如果在数据库中设计,可能是拆装两张表或者使用一张,还有一种对应关系就是使用json去嵌套。这种好处就是不用多维护一张表,查询使用都很方便,唯一的缺点就是修改太麻烦了,毕竟是json
在我们构建值对象的时候,一般都是会使用Builder模式去构建值对象,然后不暴露set方法,原因如下:
在传统通过new 构造对象,然后各种set方法,在我们出现bug或者一些业务排查的时候,整个对象在各种地方set了属性,我们无法准确定位到对象的某个属性在哪个时候悄悄被改变了,排查起来特别困难,而值对象一般是不可变类似一种配置的概念,正好特别适合这种方式去构造。
但是这种方式也会有一定缺陷,比如某些三方框架都会调用无参构造方法去初始化,然而使用了Builder模式私有化了构造方法,就会有意想不到的报错,但这种错误一般在我们的可控范围,所以具体是否使用这种方式去构造实体也是自己在落地DDD去考虑,但是不用强制去执行,建议灵活应用。毕竟DDD在所有人的理解中都不太一样,团队中使用起来觉得好用合适才是最好的。
总结
实体和值对象有时候很容易区分,有时候又不太容易区分,不过主要还是看有没有唯一标识,如果有唯一表示就是实体,如果能够独立存在也是实体,否则则是值对象,比如像上面的地址,如果脱离了用户其实就没有实际意义了。在不同的业务场景下,值对象和实体可能会互换身份。
上面的实体和值对象是我自己的理解,如果觉得有什么偏差欢迎大家补充。期望在DDD的设计上与大家共同进步。
关于我
觉得文章不错请扫码关注我吧