领域设计:Entity与VO

本文探讨以下内容:编程

  • 什么是状态
  • 什么是标识
  • 什么是Entity
  • 什么是VO(ValueObject)
  • 在设计中如何识别Entity和VO

要理解Entity和VO,须要先理解两个概念:「状态」和「标识」!咱们先来聊聊「状态」!网络

状态

你们确定都在淘宝买过东西吧!在淘宝购买商品后,会有一个订单,记录了你购买的商品信息、价格、店铺信息、还有一个特别重要的信息,就是订单状态。经过这个订单状态,咱们能够知道咱们的购物流程如今进行到哪一步了。若是你犹豫了好久才下定决心购买了一件心仪已久的商品,你是否是很在乎订单状态?时不时要刷新一下页面,看看订单状态是否显示已送达了?架构

开发过系统的都知道,通常订单状态都是使用一个字段来表示的,好比status,不一样的状态就是给status赋不一样的值。可是这个status就是「订单状态」吗?难道状态就是一个字段?!并发

Order{
 product
 location
 seller
 buyer
 status
 ...
}

你有没有想过,当咱们说「状态」的时候,咱们实际上指的是什么?编程语言

咱们在不少场景下会用到「状态」这个词,好比:分布式

  • 你今天「状态」不错哦
  • 朋友又发朋友圈「状态」了
  • 我在淘宝买的商品已是发货「状态」了
  • REST(表述性状态转移)中的状态

以「你今天状态不错」这句为例,若是状态就是一个字段!那么,「你今天状态不错」就是status=1?!「你今天状态不行」就是status=0?!很明显,这不合理!动画

若是「状态」不是简单的一个字段的话,那么「状态」究竟是什么呢?设计

其实在架构风格:你真的懂REST吗?已经提过了!文中对REST的解释,有这么一句:一个由网页组成的网络(一个虚拟状态机),用户经过选择连接在应用中前进(状态迁移),致使下一个页面(应用的下一个状态的表述)被转移给用户,而且呈现给他们,以便他们来使用。指针

结合上面的几个场景,你有没有发现,「状态」实际上表示的是「目标对象在当前时刻所呈现出的内容」!在软件系统中经过一个字段来表示状态只是一种简化手段!对象

如无特殊说明,下面所提到的「状态」指的是「目标对象在当前时刻所呈现出的内容」,而不是指状态字段

  • 你今天「状态」不错哦:你今天给人的感受很好
  • 朋友又发朋友圈「状态」了:朋友圈当前的内容
  • 我在淘宝买的商品已是发货「状态」了:你的购物流程目前所在的环节
  • REST(表述性状态转移)中的状态:当前呈如今用户面前的页面

既然「状态」表示的是「当前时刻所呈现出的内容」!那么说明了「状态」是个快照/瞬态!也就是说,「目标对象」有多个「状态」,「当前状态」只是「目标对象」众多「状态」中的一个!

你们应该玩过定格动画吧?就像下面这样(下图截自《大侦探福尔摩斯2:诡影游戏》):

领域设计:Entity与VO

 

图中的小册子就是「目标对象」,册子的每一页就是「状态」,当前展现出来的那一页就是「当前状态」!

在理解了什么是「状态」之后,咱们就能够来初步区分Entity和VO了:

  • Entity在整个生命周期中,有多个「状态」,也就是说「状态」是可变的(至于变不变就看实际状况了)
  • 而VO在整个生命周期中,只有一个「状态」,也就是说「状态」不变

如今,问题又来了,对于VO来讲,由于「状态」是不可变的,咱们就能够用其「状态」来表示VO!可是对于Entity来讲,由于有多个「状态」,且「状态」是可变的,那咱们如何来表示呢?以上面的Order为例,假设同一个买家在同一个卖家那里买了两个一样的商品,那两个订单里的信息都是同样的,可是它是两个不一样的订单,咱们如何区分这两个订单呢?

如今就轮到下一个主角登场了:「标识」!

标识

说到「标识」,咱们最早想到的是编程语言中的「引用」或「指针」!好比下面的代码:

Order orderA = new Order("productA",...);
Order orderB = new Order("productA",...);
orderA.productName = "productB";
  • 前面两行,orderA和orderB虽然订单信息(状态)都相同,可是这是两个不一样的订单
  • 第三行,即便改了orderA的产品名称(状态),依然仍是相同的订单

这解决了「区分相同状态的不一样Entity」的问题,可是没有解决Entity有多个状态的问题。由于「标识」指向的是目标对象的当前状态。并且,不少编程语言中有个很大的问题,就是不区分「标识」和「状态」!什么意思呢?

假设咱们在看一部电影,当咱们开始观看时,就是这部电影生命周期的开始,观看结束就是这部电影生命周期的结束,在这段时间里,电影的画面(状态)一帧帧的呈如今咱们面前,咱们能够经过播放、快进、后退、暂停改变电影的状态,每一个状态都是相互独立的,相似这样:

领域设计:Entity与VO

 

随着时间的改变,咱们能获取到电影的不一样状态,每一个状态是相互独立的。可是实际上咱们的代码逻辑像下面这样:

var movie1 = new Movie();
movie1.setCurrentFrame("第三帧");
var currentMovie = movie1
movie1.setCurrentFrame("第四帧");
currentMovie // 仍是第三帧吗?

电影播放到第三帧,咱们用一个变量currentMovie保存了电影的当前状态(第三帧),可是后面电影播放第四帧了,currentMovie也就变成了第四帧的状态了。

语言中的这种「标识」(我称为「隐式标识」)还有另一个问题,就是没法跨系统。好比,在分布式系统中,须要保证两个系统中的对象是同一个对象,这种「隐式标识」是作不到的。

因此「隐式标识」并不能知足咱们的需求。咱们须要「显示标识」,「显示标识」在现实中很常见:

  • 每一个人都有身份证,即便有两我的名字相同、性别同样、身材相同、甚至整容了样貌都同样,可是身份证号码是不同的,身份证号码就是每一个人的「显示标识」
  • 一个产品线上生产的产品能够说如出一辙,可是都会有一个惟一的产品编号,这个产品编号就是产品的「显示标识」

在上面购物的列子中,就至关于给Order一个惟一标识,好比一个惟一的订单号:

Order{
 orderNo // 显示标识
 product
 location
 seller
 buyer
 status
 ...
}

给定订单号之后,不管订单的状态如何变化,只要订单号不变,那么它就是同一个订单。

因此,「标识」是另外一个区分Entity和VO的关键点:

  • Entity有标识
  • 而VO没有标识

注意标识并不必定只是一个字段,多是多个字段的组合,这须要根据不一样的业务逻辑来肯定。好比在一个学校系统里,能够经过学年+班级+学号来标识一个学生。

Entity和VO

理解了标识和状态,咱们就能够来定义Entity和VO了:

  • Entity是具备多个「状态」的对象,「状态」在其生命周期中可能会改变,经过「标识」来惟一肯定这个对象
  • VO只有一个「状态」,且是在建立时就肯定的,也就是说VO是不可变的

如今咱们知道了什么是Entity,什么是VO,那么咱们如何在系统中识别哪些对象是Entity,哪些对象又是VO呢?

如何识别Entity和VO

一个对象是表示成Entity仍是VO,取决于系统的关注点。

咱们还以淘宝购物为例,假设你在某家店铺买了个商品,质量很好。过了一段时间后,你想再买一个,可是你记不得是哪家店了,因而你从已完成的订单列表中点击商品想进去再次购买。可是你点进去后发现,商品下架了。

这是由于「商品」在「订单系统」中是个VO,而在「商品管理系统」中是Entity!其实很好理解:

  • 在「商品管理系统」中,系统须要关注「商品」的「状态」,须要维护是否上架、库存多少、各类属性等信息(多种状态)。就是说在「商品管理系统」中,商品状态是可变的。因此它也有「标识」,即商品ID
  • 而「订单系统」并不关心「商品」的「状态」变化,它只关注在建立订单时,这个「商品」的当前「状态」是什么,而且在订单建立完成后,这个「商品」的「状态」就不会再改变了

在「商品管理系统」中,商品能够这样表示:

Product {
 id // 商品标识
 name
 desc
 status
 ...
}

而在「订单系统」中,订单是个Entity,商品是个VO,能够这么表示:

Order{
 orderNo // 订单标识
 product:Product
 status
 ...
}
Product {
 id // 这里不是标识,只是状态
 name
 desc
 status
 ...
}

注意这里的id并非标识,这里的id实际上退化成了状态的一部分,保留这个id是为了和「商品管理系统」进行交互,经过id从商品管理系统中查询商品。固然还有其它方式,例如保存「商品管理系统」中该商品的历史URL。

总结

本文从对「状态」和「标识」的理解开始,一步步来解释什么是Entity和VO,以及如何在系统中识别Entity和VO。后面将进一步讨论Entity与VO的关系,以及与其它组件的关系,例如DTO,Service,Resporitory,DAO等

参考资料

  • 《领域驱动设计:软件核心复杂性应对之道》
  • 《实现领域驱动设计》
  • 《Clojure编程乐趣》
  • 《七周七并发模型》
相关文章
相关标签/搜索