The DoYouHaoBaby PHP Framework中文开发文档手册
Copyright(c) DoYouHaoBaby.NET All Rights Reserved.
[ 服务 ]

当我们分析领域并试图定义构成模型的主要对象时,我们发现有些方面的领域很难映射成对象。对象要通常考虑的是拥有属性,对象
会管理它的内部状态并暴露行为。在我们开发通用语言时,领域中的主要概念被引入到语言中,语言中的名词很容易被映射成对象。
语言中对应那些名词的动词变成那些对象的行为。但是有些领域中的动作,它们是一些动词,看上去却不属于任何对象。它们代表了
领域中的一个重要的行为,所以不能忽略它们或者简单的把它们合并到某个实体或者值对象中。给一个对象增加这样的行为会破坏这
个对象,让它看上去拥有了本该属于它的功能。但是,要使用一种面向对象语言,我们必须用到一个对象才行。我们不能只拥有一个
单独的功能,它必须附属于某个对象。通常这种行为类的功能会跨越若干个对象,或许是不同的类。

例如,为了从一个账户向另一个账户转钱,这个功能应该放到转出的账户还是在接收的账户中?感觉放在这两个中的哪一个也不对劲。
当这样的行为从领域中被识别出来时,最佳实践是将它声明成一个服务。

这样的对象不再拥有内置的状态了,它的作用是为了简化所提供的领域功能。服务所能提供的协调作用是非常重要的,一个服
务可以将服务于实体和值对象的相关功能进行分组。最好显式声明服务,因为它创建了领域中的一个清晰的特性,它封装了一个概
念。把这样的功能放入实体或者值对象都会导致混乱,因为那些对象的立场将变得不清楚。服务担当了一个提供操作的接口。服务
在技术框架中是通用的,但它们也能被运用到领域层中。一个服务不是在执行服务的对象,而与被执行操作的对象相关。在这种情况
下,一个服务通常变成了多个对象的一个链接点。这也是为什么行为很自然地依附于一个服务而不是被包含到其他领域对象的一个原因。
如果这样的功能被包含进领域对象,就会在领域对象和成为操作受益者的对象之间建立起一个密集的关联网。众多对象间的高耦合度是
糟糕设计的一个信号,因为这会让代码很难阅读与理解,更重要的是,这会导致很难进行变更。

一个服务应该不是对通常属于领域对象的操作的替代。我们不应该为每一个需要的操作来建立一个服务。但是当一个操作凸现为一个领域
中的重要概念时,就需要为它建立一个服务了。以下是服务的3个特征:

  1. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象。
  2. 被执行的操作涉及到领域中的其他的对象。
  3. 操作是无状态的。


当领域中的一个重要的过程或者变化不属于一个实体或者值对象的自然职责时,向模型中增加一个操作,作为一个单独的接口将其声
明一个服务。根据领域模型的语言定义一个接口,确保操作的名字是通用语言的一部分。让服务变得无状态。

使用服务时保持领域层的隔离非常重要。很容易弄混属于领域层的服务和属于基础设施层的服务。服务层也可能有服务,这会继续增
加层级的复杂性。这些服务甚至更难从与领域层中的近似的服务中分离开来。当我们在设计阶段建立模型时,我们需要确保领域层保
持从其他层中隔离开来。

不论是应用服务还是领域服务,通常都是建立在领域实体和值对象的上层,以便直接为这些相关的对象提供所需的服务。决定一个服
务所应归属的层是非常困难的事情。如果所执行的操作概念上属于领域层,那么服务就应该放到这个层。如果操作和领域对象相关,
而且确实也跟领域有关,能够满足领域的需要,那么它就应该属于领域层。

让我们考虑一个实际的Web 报表应用的例子。报表使用存储在数据库中的数据,它们会基于模版产生。最终的结果是一个在Web 浏览
器中可以显式给用户查看的HTML 页面。用户界面层被合并成Web 页面,允许用户登录,选择所期望的报
表,单击一个按钮就可以发出请求。应用层是非常薄的一个层,它位于用户界面和领域层以及基础设施层的中间位置。它在登录操作时,
会跟数据库基础设施进行交互;在需要创建报表时会和领域层进行交互。领域层中包含了领域的核心部分,对象直接关联到报表。有两个
这样的对象是报表产生的基础, 它们是Report 和Template。基础设施层将支持数据库访问和文件访问。

当一个用户选择创建一个报表时,他实际上从名称列表中选择了一个报表的名称。这会是一个字符串类型的reportID。还会传递其他的参数,
例如要在报表中显示的项目以及报表中要包括的数据的时间间隔等。但出于简化的考虑我们将只提到reportID。这个名字会通过应用层传递
到领域层。领域层有义务根据所给的名字来创建并返回报表。因为报表会基于模版产生,我们需要创建一个服务,它的作用是根据一个reportID
获得对应的模版,这个模版被存放在一个文件或者数据库中。这不适于作为Report 对象自身的一个操作。它也同样不属于Template 对象。所以
我们创建了一个分离服务出来,这个服务的目的是基于一个report 的标识符来检索一个报表模版。
这会是一个位于领域层的服务。它也会用到文
件类的基础设施,以从磁盘上检索模版。