您的位置:首页 > 编程语言

领域驱动设计之代码优先-领域层设计-2 (翻译)

2013-03-21 10:03 543 查看
2.2.- 领域层的要素

在本章,我们简单解释每种元素的职责:

2.2.1.- 领域实体
这个概念代表了实体模式的实现。

实体表示领域对象,主要由对象的标识和连续性定义,并不只是由组成对象的属性定义。

实体一般和主要的业务/领域对象有一个直接的关系,例如客户,雇员,订单等等。因此,把这些实体保存到数据很正常,虽然这完全取决于每一个具体应用。虽然不是强制的,但是“连续性”这方面通常存储到密切相关的数据库。连续性意味着实体应该可以在应用的执行周期"生存“。每次重新启动应用时,应该可以在内存中重建这些实体。 为了区别实体,标识的概念就是必须的,尤其是两个实体都着相同的值/数据时。标识是应用的根本方面。应用中错误的标识可以导致数据损坏问题或程序错误。很多时候在真实的领域或应用领域模型是由它们的标识定义而不是属性定义的。一个好的实体的例子是人。实体的属性,例如住址,财务数据甚至姓名都可以改变;然而,同一个人的实体的标识将保持不变。因此,一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。 一些对象并不主要是由它们的属性定义;这些对象表示特定生命周期的标识,经常有不同的表现形式。一个实体应该可以区分别的实体,即使当它们有相同的描述性属性时。关于领域驱动设计,根据Eric
Evans的定义,”一个由它的标识定义的对象叫做实体“。实体在领域模型中是非常重要的,应该仔细的辨别和设计。在一些应用中可能是实体的在另一些应用中可能不是。例如,在一些系统中地址可能没有标识。然而在其它系统中例如电力公司,客户的地址就非常重要,因此地址必须有一个标识。这种情况下,地址应该看作一个领域实体。在其它情况,例如电子商务应用,地址可能就是一个属性。在后一种情况下,地址不是特别重要,应该作为值对象(稍后会解释)。一个实体可以有很多类型,可以是一个人,汽车,银行交易等,但重要的一点是,是不是实体取决于特定的领域模型。一个特定的对象可能在领域模型中不是一个实体。同样地,不是所有领域模型中的对象都是实体。例如,在银行交易的情景中,在同一天的两笔存入账款被视作两个不同的银行交易,所以它们有标识,一般是实体。即使,
两个实体的属性都一样,他们也应该当作不同的实体。

实体的实现设计

关于设计和实现,这些实体是离线的对象,用来在层间获取,传输实体数据。这些对象代表了真实世界的业务实体,例如产品或订单。另一方面,应用中的实体内部用来在内存中作为数据结构。此外,如果这些实体依赖特定的数据持久化技术,那么这些类就应该放在数据持久化层中。另一方面,如果我们按照面向领域设计的模式使用POCO,他们是不和任何技术相关的纯粹的类。因此,这些实体应该在领域层中,由于他们是领域实体,独立于任何基础结构技术。
Table 1.- 持久化技术透明原则

PI原则(持久化透明),POCO和STE

这个概念,推荐使用POCO来实现领域实体,可能是根据面向领域架构当实现实体时最要考虑的。原则完全支持领域层的所有组件都必须是完全对基于的数据持久化层的技术透明的。 持久化透明是一个建议隐藏对象的持久化细节的设计原则。”透明“表明行为被封装,这是一个面向对象的设计宗旨。这种实现实体的方法对很多设计尤其重要。在那些设计中(例如DDD),把这些元素和数据访问技术隔离是至关重要的。换句话说,实体不从任何基类继承,不实现任何和技术相关的接口,这样的实体在.NET叫做POCO。 相反,继承特定基类或和技术相关接口的对象称为”指令性类"。选择哪个不是小事,必须被认真考虑。另一方面,使用POCO给我们对选择持久化模型很大的自由空间。另一方面,它带来了关于“透明的程度”的限制和过载,持久化引擎有这些实体和它们的关系。POCO类在开始时的实现成本较大,除非使用ORM帮助映射甚至生成代码。 自跟踪实体STE
(Self Tracking Entities)的概念比较宽松。就是说,由实体定义的数据类并不完全”纯粹“,而是需要实现一个或更多的接口。在这种情况,并不完全符合持久化透明原则,重要的是这个接口需要在我们的掌控。换句话说,这个接口不能是任何外部基础结构技术的一部分。否则,我们的实体会变成”指令性类"。

无论如何,实体是贯穿整个架构或至少在应用服务器层的对象。后者的情况是当使用DTO进行远程通信的情况,这样领域模型的内部实体不会到表现层或任何超越内部层的服务。使用DTO或者实体在分发服务层分析,因为这是和多层应用和分布式开发相关的。 最后,我们必须考虑到当处理远程通信时类的序列化需求。把实体从一层传到另一层需要实体序列化;必须支持特定的序列化机制,例如XML或二进制格式。需要确认选定的实体类型支持序列化。另一个选项是,在分发服务层和应用层使用转换DTO的聚合。实体逻辑包含在实体内部实体对象自己具备一定的逻辑。例如,在银行账号实体中可以有当金钱增加或支付的业务逻辑。自运算字段的逻辑可以是另一个例子。当然,也可有有没有自身逻辑的实体,但这种情况只会发生在确实没有内部逻辑时,因为如果所有的实体都没有逻辑,那我们将陷入
Martin Fowler提出的”贫血领域模型“的反模式。参照贫血领域模型:http://www.martinfowler.com/bliki/AnemicDomainModel.html

"贫血领域模型“发生在当只有数据实体例如类,属于实体的领域逻辑混在了较高层的类中。需要注意的是

在一般情况下,领域服务不应包含任何内部实体的逻辑。 如果服务(领域服务)拥有100%的实体逻辑,属于不同实体域逻辑的混合可能是危险的。这是“事务脚本" 实现的特征,和”领域模型“或”面向领域“正相反。“事务脚本"的方法具有”贫血模型“,因为所有的逻辑都在服务中,领域实体对象是什么?只是承载数据的架构。客户端代码请求领域提供数据,然后客户端代码根据这些数据作出决定。 事务脚本和业务服务层的方法容易上手,但当应用变得更复杂时会导致一堆问题。



我们实现应用领域驱动设计模式是为了消除这些事务脚本和业务服务。事务脚本只是负责找到合适的领域的入口点,然而告诉领域类做一些工作。 结果是业务逻辑可以在领域中更深的表达。你可以方便的找到正确的抽象,可以让你的代码容易理解,可以建有单一职责的类。这也意味着,对象的创建和持久化会抽象和封装,不必知道数据访问就可以调用客户端代码或脚本。这是因为领域实体应该是持久化透明,不应该有任何数据访问的引用。这些工作都不需要在事务脚本中进行,移动到了领域中去做。

此外,使用/调用仓储库的相关逻辑应该是在应用层的服务里。除非我们确实需要,它不应该在领域服务中,一个对象(实体)不需要知道是如何保存自己,就像一个引擎提供引擎的能力,但不制造本身,或是一本书不知道怎么把自己放在书架上。这就是为什么我们不应该从实体内部调用仓储库。 我们应该包含在领域层的逻辑应该是我们可以和领域/业务专家讨论的。这种情况,我们通常不和他们讨论任何有关仓储库或事务的事。关于使用领域服务的仓储库,当然有例外。那就是当我们需要根据领域逻辑的状态来获取数据。这种情况,我们需要从领域服务调用仓储库。但这通常用户查询数据。所有的事务,工作单元管理等等,应该放在应用层的服务。
Table 2.- 框架架构导则

Rule Nº: D8. 基于标识识别实体
o 规则
- 当一个对象由它的标识而不是属性区分时,这个对象应该是基本的定义领域模型。应该是一个实体。应该维护一个简单的类定义,专注于生命周期和标识的连续性。它应该以某种方式区分,甚至当属性或形式变化时。关于该实体,对每个对象应该有保证获得唯一结果的操作,可能是通过选择一个唯一的标识符。模型必须定义该模型是同一实体对象。
参考

- "领域驱动设计”书中的实体模式 by Eric Evans.

- 实体设计模式

- http://www.codeproject.com/KB/architecture/entitydesignpattern.aspx
Table 3.- 框架架构导则

Rule Nº: D9. 面向领域架构的实体必须是POCO或STE(自跟踪实体)。
o 规则
- 为了实现持久化透明原则,没有对基础结构技术的直接依赖,实体使用POCO或STE实现是很重要的。什么时候使用自跟踪实体一些ORM框架可以使用POCO和自跟踪实体。自跟踪实体使我们更轻松的实现先进的方面(比如乐观并发管理)。所以,对于多层应用和中型应用的情况,使用自跟踪实体比较方便因为它提供了很大的潜力和更少的人工。
 什么时候使用POCO
 纯POCO领域实体是更领域驱动设计的方法,因为POCO实体更好的遵循持久化透明原则。并且如果我们想让表现层和领域、应用层不是同步开发,想要在领域实体的改变不影响表现层,最好使用DTO配合POCO实体。DTO是专门为分布式服务创建的,在表现层中使用,POCO领域实体只会用在应用服务器的层中。因此,推荐使用POCO领域实体,提供了完全的和持久化层的独立。使用DTO是更纯净的领域驱动设计。然而,需要话费更高的开发成本和复杂性,由于需要数据的双向转换。决定是否用自跟踪实体还是DTO很大取决于应用的大小。如果有几个开发团队在一个应用上开发,使用DTO可能更有利。
- 最后一种是混合的,使用自跟踪实体同时有一个为外部集成设计的SOA层。因此,该SOA层会为应用/外部使用带DTO的Web服务提供服务。
参考

- "领域驱动设计”书中的实体模式 by Eric Evans.

- 实体设计模式

- http://www.codeproject.com/KB/architecture/entitydesignpattern.aspx
2.2.2.- 值对象模式
“很多对象没有概念上的标识符。这些对象描述了一个东西的特征。” 正如我们所看到的,跟踪的实体的标识符很重要;然而,系统中有一些对象和数据不需要标识符。实际上,很多情况下不应该这样做,因为可能损害系统的一方面性能,很多情况下是不需要的。软件设计是一个不断和复杂性斗争的过程,如果可能,应该尽量最小化复杂性。因此,我们必须区分值对象。值对象的定义是:描述事物的对象;更准确的说,一个没有概念上标识符描述一个领域方面的对象。简言之,这些对象是用来表示临时的事物。我们关心它们是什么,而不是它们是谁。基本的例子是数字,字符串等,但是也在高层的概念中存在。例如,系统中的“地址”可以是实体。但在其它系统中,“地址”可以是一个值对象,一个公司或个人的描述性的属性。一个值对象也可以是一组其它值或者其它实体的引用。例如,在应用中生成了一个路线,路线可以是一个值对象尽管它的内部是引用了不同的实体。在实现层面,一个值对象的生命周期一般比较短且没有标识符跟踪。 从不同的角度来看,一个实体通常由不同是属性组成。例如,一个人可以认为是一个有由一些内部属性的有标识符实体,而这些属性是一些简单的值。这些值中,那些重要的可以作为集合的必须当作值对象。

下面的图展示了特定应用中的类,强调的是有时可以作为实体,有时可以作为实体中的值对象:



Table 4.- 框架架构导则

Rule Nº: D10. 需要时识别和实现值对象模式
o 建议
- 当模型的元素作为一个租对我们很重要,但是对象不能有一个标识符时,我们需要将它们作为值对象。这些属性的应该表达它们的意义,应该有相关的功能。我们应该将值对象看作它的生命周期内不可变的,从创建到销毁。
参考

- Martin Fowler的“值对象”模式。《企业应用架构模式》中提到:“一个小型简单的对象,像是钱或者时间区间,它们不是基于标识符的。”
- 《领域动设计》中的“值对象” - Eric Evans.
组成值对象的属性应该形成一个“概念上的整体”。例如,街道,城市,邮政编码不应该是单独的属性。实际上,这些属性应该是“地址”值对象的部分。
设计值对象
由于缺乏值对象的限制,可以用不同的方法设计,但应该用最简单的设计或者最优化系统性能。值对象的一个限制是他们的值从开始就不能更改。因此,在创建时就需要提供数据,不允许在对象的生命周期更改。

至于性能,由于值对象不可更改的性质,使我们可以执行一定的“技巧”。这在系统中有数千个值对象的实例而可能有值是相同的巧合。它们不可变的性质,使我们能够重用它们;它们会是“可互换”的对象,由于它们的值是相同的而它们没有标识符。这种优化对有的软件运行缓慢而有的有良好性能起重要作用。当然,所有这些推荐要根据应用的环境和部署环境。共享对象有时提供更好的性能但在特定的上下文中可能减少可伸缩性,因为访问共享的重用对象的中心点可能导致通信的瓶颈。
2.2.3.- 聚合模式
聚合一个用来定义领域对象所有权和边界的领域模式。

一个模型可以有任意数量的对象,大多数对象会和其它有关联。我们会有不同种类的关联。大多数对象间的关系必须要在代码和数据库中体现。例如,雇员和公司间的一对一关系会体现在对象间的引用,也会在数据库两张表间有关联。关于一对多关系情况就更复杂。但是也有很多关系对于领域模型是没有用的。简言之,当模型有很多复杂的关系时很难保证模型变化的一致性。

因此,我们要考虑的是尽量简化中的实体模型的关系。这就是聚合模型的所在。一个聚合是一组相关的被视为整体的对象。聚合由一个边界把内部和外部的对象分开。每个聚合有一个根对象(聚合根实体),从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。如果在集合内有其它的实体或值对象,这些实体对象的标识符是本地的,只有在属于聚合时才有意义。如果独立时没有意义。

这种单点的聚合访问准确地确保数据的完整性。只有聚合根才能使用仓储库直接查询,其它的只能通过相关的聚合访问。聚合的外部没有访问或修改聚合内的数据,只有通过根,这意味着一个非常重要的控制水平。如果根实体被删除,聚合内部的其它对象也被删除。

如果聚合对象需要保存到数据库,应该只能通过根实体访问。聚合内的二级对象必须通过关联获取。这意味着只有聚合的根实体可以和仓储库有关联。同样的情况发生在一个较高的层的服务。我们可以有服务直接和聚合根实体相关,但是不能和聚合的二级对象相关。

然而一个聚合的内部对象,允许有其它聚合根实体的引用。
下面的图展示了一个特殊模型的聚合的例子:



重点
前面的聚合只是一个例子。并不是每个模型都包含相似的实体。聚合不是在项目开始时就明确知道的。不只是聚集一些实体和表。当你和领域专家讨论所有可能实体的每个属性时才会发现聚合的模型。我们并不真是要在定义实体和聚合之前倾听领域专家。不是把从领域专家描述的每个名词创建实体这么简单。根据他们的需求和用例,可以会创建不用的实体,特殊关系的特殊实体。动词和业务任务甚至比名词更重要,因为很多时候领域专家嘴中的名词可能是为了另一个情境。我们在定义任何实体前首先需要理解用例、业务情景。此外,我们会按照原来的方法设计正常的实体关系图,但是现在使用实体类和聚合根。这不是重点。
表 5.- 聚合识别规则

Rule Nº: D11. 在需要的情景识别和实现聚合模式来简化模型对象间的关系
o 建议
- 我们需要考虑的目标之一是尽量简化领域实体模型的关系。这儿聚合模式就出现了。一个聚合是一组相关的被视为整体的对象。
- 请记住这意味着这有聚合根实体可以和仓储库有关系。同样的情况发生在一个较高的层的服务。我们可以有服务直接和聚合根实体相关,但是不能和聚合的二级对象相关。
参考

- 《领域驱动设计》的聚合模式 - Eric Evans.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: