在DDD中,实体和值对象是很重要的两个概念。在系统中实体和值对象都是以某个事物对象的形式呈现,但他们又有很多的不同。我觉得,他们最大的不同之处在于,实体表示是什么,而值对象表示有什么。
- 实体
实体的最大特点是有唯一标识,这个唯一标识,可能是系统自动生成的,也可能是数据库的自增ID,甚至是用户提供的。不管怎么样,系统都可以通过这个唯一标识找到一个唯一的事物。
例如人,如果在中国,可以通过身份证来确定一个人,那么身份证就是一个唯一标识。在一个实体里,最好不要通过多个属性来确定唯一性,这样可能会给系统带来复杂性。
例如一个人有姓名,有详细地址,通常通过姓名和详细地址能确定这个人的唯一性。但是地址会变,甚至姓名会变,当地址变的时候,我们还能确定当初的那个人就是这个人吗?唯一标识应该是由不变的特性来表示,应该是不变的。
- 值对象
值对象跟实体的区别是值对象没有唯一标识的。我们只关心值对象有哪些属于(也即有什么),而不关心这个值对象是哪一个值对象。值对象我们拿之即用,用完即丢,我们不会关心这个值对象持续的状态。并且,值对象应该尽可能设计成不可变的,如果需要改变值对象的属性,
就重新创建一个值对象而不是在原来的基础上改变。
有一条箴言:如果值对象是共享的,那么它应该是不可变的。并且,我们应该尽可能将对象定义为值对象而不是实体。这样设计能够带来很多好处,特别是在并发上。
例如两个人同时定同一个航班,如果航班信息不是值对象而是,在最后A由于事务繁忙改了航班,那么就会导致B的航班信息也跟着改变。
又例如一个人下了一个订单,收货地址选择是”广东省 广州市 天河区”,在订单还没发货的时候,他将该条收货地址改成了”广东省 广州市 海珠区”,那么前面下的订单收货地址就会变成”广东省 广州市 海珠区”。收货地址本来应该是一个值对象,这就是值对象的可变性带来的问题。
为了修改收货地址后,不改变原来订单的收货地址,我们可以在下订单的时候,将收货地址copy一份保存到订单信息中。但是,我们将收货地址定义为不变就会避免带来上述的问题,每一次收货地址的改变都是新增一个收货地址,再在收货地址的显示上做一些处理。
最后,并发问题很多都是由于共享和可变性带来的。事物的不可变性,能避免很多并发问题。