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


2.3.-  工场模式
      当创建一个对象或整个聚合时,会变得复杂或揭示了太多的内部结构,工厂提供了封装。创建其它对象的程序元素叫做工厂。
      如果创建的对象并不简单或者需要要做很多验证,在创建期间检查一些对象,建议使用工厂来创建对象。
      另一方面,如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖。
      我们可以使用这个模式不意味着我们一定要使用。就像提到的,工厂应该只用来使用在添加的时候。如果只是一个new()操作,并不增加值。
      一些工程增加值的场景如下:
-  当创建对象是非常复杂的操作(例如不仅仅是创建实例),最好封装在API(工厂)中。例如,在我们的示例应用中,我们使用工厂创建复杂的聚合(例如订单工厂)。
-  抽象工厂可以让仓储库实现独立于客户端代码。这很好的契合了SOLID的"L",但是你也可以通过使用依赖注入的仓储库来实现。因此,使用控制反转/依赖注入容器的场景通过不需要使用工厂,由于容器会扮演工厂的角色创建仓储库的所有依赖。事实上,它是一个更好的办法,这是我们实现示例应用的方法。同样地,由于这和仓储库相关,也就和数据持久化层相关,而不是和领域模型层。 复杂的对象创建是领域层的职责,然而,这个任务不属于表示模型的对象。关于领域实体,它们从工厂类开始生命周期,这意味着主要目的是封装创建的知识。创建的是实例是透明的实体,没有提到关于持久化的。
规则 Nº: 当创建 实体和值对象复杂时使用工厂
o 规则 
  
     如果创建的对象并不简单或者需要要做很多验证,在创建期间检查一些对象,建议使用工厂来创建对象,工厂自己没有领域模型的职责但也是领域设计的一部分。
     另一方面,如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖。(POCO实体不应该有外部依赖,因此不需要使用控制反转容器创建简单的POCO实体)。
  参考
工厂 – (136页 – 领域驱动设计, by Eric Evans)

良好工厂的要求:

1.- 每个创建方法都是原子的,要求所有创建对象的不变量。一个工厂应该只能生产透明状态的对象。对于实体,意味着创建整个聚合时满足所有的不变量。例如,一个订单工厂类应该创建整个聚合,包括根实体订单和子实体集合订单项。以后可能会添加订单项到聚合上。

2.- 如果可能使用抽象类型的参数而不是具体的类。工厂和生产的具体类耦合,但不需要和具体参数耦合。

      一个单独的工厂通常生产整个聚合,传出一个根实体的引用,确保聚合的不变量都有。如果对象的内部聚合需要工厂,通常工厂方法的逻辑放在在聚合根上。这样对外部隐藏了聚合内聚的实现,同时赋予了根确保聚合完整的职责。如果聚合根不是子实体工厂的合适的家,那么继续创建一个单独的工厂。但是注意聚合内的访问限制。

  下面的图显示了订单聚合的工厂例子:

    值得一提的是工厂也可以用在值对象上。概念很相似,但是请记住值对象是不可变的,所以工厂创建的
是值对象唯一更新的地方。

2.3.1.- 领域层的仓储库契约/接口

      仓储的实现不属于领域,输入基础结构层;然而,仓储库的契约应该属于仓储。那就是为什么我们在这里提到它。契约指定了仓储库应该提供什么,而不管它的内部实现。这些接口对技术无知。因此,仓储库接口应该在领域层定义。这也是基于 Martin Fowler定义的“接口分离模式”的面向领域驱动架构的建议方法。 为了实现这个模式,里关于实体和值对象需要是POCO。换句话说,它们应该对数据访问技术无知。我们要记住,领域实体是那些仓储库发送和接收的对象类型的参数。
      总之,持久化透明的目标是领域类对仓储库实现一无所知。当编写领域层时,必须对仓储库的实现无知。这也提高了代码的可维护性和可读性。

Table 6.-  框架架构导则
 
Rule Nº: D12. 按照接口分离模式在领域层定义仓储库接口

o  建议

-  从在领域层和数据库访问层解耦的观点看,我们推荐在领域层定义仓储库接口,在数据持久化层实现该接口。
    因此,一个领域模型类可能

-  这条规则完全符合基于控制反转容器的解耦技术。

   参考

-  Martin Fowler的“接口分离”模式

  “在一个包中定义接口在另一个包中实现。这样客户端代码在接口中需要依赖时可以对实现完全无知。”

http://www.martinfowler.com/eaaCatalog/separatedInterface.html
2.3.2.- 领域模型服务

     大多数情况下,我们设计的操作在概念上不属于领域实体对象。这种情况下我们把操作包含在领域模型服务中。
  注意:
      需要指出领域驱动设计中服务的概念不是分发服务。Web服务可能发布领域服务的实现,有可能Web应用只有领域服务没有Web服务。那些不是明确属于领域实体的操作一般是活动,不是领域实体的内部属性。但由于我们的编程模型是面向对象的,我们应该把它们搭建策划那个对象。这些对象就是我们所说的服务。  硬把那些领域操作作为领域对象的一部分会歪曲领域对象的定义。服务是一个或一组已由模型提供的操作。 服务这个词在服务模式中是这么定义的:“服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。” 高层的服务通常由活动命名。这种情况下,他们和用例的动词而不是名词相关,甚至有一个领域业务操作的抽象定义。 服务操作的名字应该从领域的语言中来。参数和返回值应该是领域对象。服务类也是领域组件,但这种情况下它们是领域层中的最高级别的对象。多数情况下领域服务覆盖了不同的概念,协调业务情景和用例中相关的实体。
      当一个领域操作被视为一个重要的领域概念,一般就应该作为领域服务。 服务应该是无状态的。这不意味着实现它的类是静态的;它可能是一个实例化类。一个服务是无状态的意味着客户端程序可以使用服务的任何一个实例,而不管服务内部的状态。
      此外,服务的执行可能使用或者修改全局信息。但是服务不应该有影响它行为的状态。
      至于领域服务的类型,一个银行应用中的例子是,从一个账户到另一个账户转账,因为它需要调用账户类型的支付和入账操作。另外,转账这个行为/行为是银行领域的典型操作。这种情况下,服务本身不需要做很多,只是调用领域类的入账和支付方法。另一方面,把转账方法放在账户类中会是一个错误因为该操作设计两个账户,可能需要考虑其他的业务规则。
      异常处理和业务异常抛出应该在领域服务和实体类两者的内部实现。
      从领域的外面看,服务通常是可见的,为了完成每层相关任务/操作。
 7.- 框架架构导则
规则 Nº: D13. 设计实现领域服务来协调业务逻辑
o  推荐
-  有这些完成协调领域实体逻辑的组件很重要,而不会把领域逻辑和数据访问逻辑混淆。
-  一个好的服务通常有下面的特征:
  
  o 操作和领域概念相关而不是实体内部逻辑的部分。 
参考
- 《领域驱动设计》的“服务模式” - Eric Evans。
-  服务层模式 – By Martin  Fowler.《企业应用架构模式》:"服务层指定了一组操作,协调主要操作的响应“。
      另一个需要考虑的规则是当处理数据实体、类、方法的定义时,是定义我们将要用到的。我们不应该定义只因为似乎符合逻辑就去定义实体和方法,因为最终可能很多都不会被用到。简言之,我们应该遵照一个有用敏捷方法,叫”你不会需要它“。
      我们应该在我们需要时定义领域服务,确实有需要有实体领域逻辑的协调。
      如下图所示,我们可以有一个领域服务(BankTransferService类)协调BankAccount实体的业务逻辑:
 
      一个简单的UML顺序图会有下面的交互。重要地,我们指出方法的调用是可以和领域专家或最终用户讨论的领域逻辑。顺序图中有三个对象。第一个是领域服务,另外两个是有领域逻辑的领域实体对象。
 
规则 Nº: D15. 只在领域服务中实现领域逻辑的调用
o 建议
-  领域服务逻辑须以非常干净简洁的代码实现。因此,我们必须实现对领域低层组件的调用。通常应用的调用,例如仓储库的调用,创建事务,使用UoW对象等,不应该在这里实现。这些操作应该在应用层实现。
-  这样可以使领域类更干净。然而,把持久化调用代码,Uow和事务代码混淆到领域服务的业务逻辑代码也是可行的。但是我们更倾向于把领域模型层和应用层分开。
-  只在需要时实现领域服务。
2.3.3.- 规格模式
 规格模式处理在查询中按照对象来决定选择什么对象类型。规格对象会和使用它的领域对象分隔并解耦开。
  该模式由 Martin Fowler and Eric Evans在下面的报告中在逻辑层面解释:
http://martinfowler.com/apsupp/spec.pdf 
  因此,决定获取什么数据和被查询的对象分隔开,从使用的机制中获取到。
  规格模式的一个好处可以用下面的代码看出:
IEnumerable<Customer> customers = customerRepository.GetBySpec(IsPremiumCustomerSpecification); 

  使用这样的代码可以在查询方法时复用领域相关仓储库的规格。

  我们将在理论上解释这种模式。然而在领域层实现的章节,我们会看到我们选择了不同的逻辑模式因为
.NET提供了更强大的语言。这指的是表达树,在处理内存中的对象时提供了更好的实现,而我们认为在这里
解释原始的定义来对该模式有更好的理解。

  规格模式很有用的情况

  规格在应用中有用户可以使用开放的组合查询,可以保存这样的查询使得以后可以使用。

  归入模式(相关的模式)

  一旦我们使用规格模式,另一个非常有用的模式是归入模式。归入指的是归并的作用和效果。这来自于拉丁文的
'sumĕre',意思是“to take”;包含某种东西,例如组件,在一个广泛的总结或分类或考虑更广泛的一部分或一个通用普遍
原则的特例或者规则。
  换句话说,规格的一般使用是测试这些候选对象看是否复合规格的所有需要。归入可以比较规则来看是否符合一个
规格也就符合另一个规格。有时可能使用归入模式来实现这种灵活。如果候选对象可以生产一个规格,然后测试一个
规格就像是比较相似的规格。归入特别适合在复合应用中使用。
  由于归入的逻辑概念始于使事情变得复杂,最好看Martin Fowler和Eric Evans提供的明细表,根据需要使用哪种模式:


Table 10.- 归入模式表 – By Martin Fowler和Eric Evans


                               问题                                                  解决方案                   模式


  需要根据规范选择对象的子集。                   创建一个规格来确定是否满足规范。        规格模式
  需要确定某个对象用来某个角色。                 规格有IsSatisfiedBy(anObject)的方法
  需要描述对象可以做什么而不用解释对象如何做,   返回是否所有的规范都由anObject满足。
描述候选对象如何构建以满足需求。


  
  怎样实现规格?  我们把选择方法放到代码的方法中。       硬编码模式


 在规格里为容易改变的值创建属性。
 把IsSatisfiedBy()方法作为参数来整合    参数模式
 这些参数。
  
 为各种情况创建叶元素。创建"and","or",  复合规格
 "not"复合节点的操作符。


  如何比较两个规格来确定一个是另一个的特例        创建一个叫做IsGeneralizationOf来回应。 归入模式
或一个可以用另一个替代。  


  
 需要弄清楚需要做什么来满足需求。  增加一个RemainderUnsatisfiedBy的方法   部分满足规格模式
 需要告诉用户为什么规格没被满足。  返回没被目标对象满足的需求。(最好和
 复合规格一起使用)

表 11.- 什么时候使用规格模式


规则 Nº: D16. 需要设计实现动态或复合查询时使用规格模式


o 规则


-  识别在应用使用模式是有用的,在设计实现领域组件时使用该模式,在仓储库内实现规格的执行。


  什么时候使用规格模式
 
问题


-  选择: 我们需要基于特定的条件查询一组对象,并在一定的时间间隔刷新结果。
 
-  验证: 需要确保合适的对象用作特殊用途。


-  构建要求:需要描述对象可以做什么而不用解释对象如何做,但在某种程度上可以构建一个候选对象
来满足需求。


 解决方案 


-  创建一个规格看查看是否一个对象复合特定的规范。规格会有一个 IsSatisfiedBy(anObject)的方法,
当规范满足时返回True。


  使用规格的优点


-  我们解耦了需求,灵活性和验证的设计。


-  查询的定义明确且明确


  什么时候不使用规格模式


-  当过度使用规格模式时会陷入反模式,最终是对所有类型的对象过度使用。如果我们发现不用规格模式
的通用方法或者规格对象其实是领域实体,应该重新考虑该模式的使用。


-  无论如何,我们不应该使用到所有类型的查询上。不应该过度使用该模式。


参考
 
-   Martin Fowler 和 Eric Evans的规格模式报告: http://martinfowler.com/apsupp/spec.pdf 


  Martin Fowler 和 Eric Evans的最处定义在下面的UML图中,说明了怎样对象和对象集合必须满足规格。



这正是我们说的,在使用.NET和EF时没有意义,因为查询会直接面对数据库而不是内存中的对象,正如
规格模式最初建议的。
  上述声明的主要原因来自于模式本身的定义,其中涉及直接面对内存中的对象;IsSatisfiedBy()会有
对象的实例来检查是否满足特定的条件。因此,我们会稍微改变规格模式的定义来替代返回布尔值否定或
确认特定规格的结果,会返回一个声明。
  这点会本章中领域层的实现部分,实现规格模式时讲到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值