2.2 服务层
处理领域逻辑的常见方法是将领域层再细分成两层。服务层独立出来,置于底层的领域模型或表模块之上。通常只有使用领域模型或表模块时才会这样细分,因为仅使用事务脚本的领域层并不复杂,没有必要再单独设服务层。表现逻辑与领域层的交互完全通过服务层,就好像应用程序的API一样。
在提供一个清晰的API的同时,服务层也是放置事务控制和安全等功能的好场所。这样做可以使你获得一个简单的、包含了服务层所有方法并描述了其事务和安全特征的模型。通常,通过一个独立的特性文件来描述这一模型,但.NET中的属性值提供了一个直接在代码中进行描述的好方法。
如果设立了服务层,在其中置入行为的多少是一个至关重要的决定。最小化情况下,服务层只是一个外观,所有实际的行为都在下层的对象中,服务层所做的只是将上层调用传递到更低层。在这种情况下,服务层提供一个更易于使用的API,因为它的方法通常根据用例来组织。此时,它也提供一个很方便的切入点,用来增加事务封装和安全检查等功能。
另一个极端则是将大多数业务逻辑都以事务脚本的形式置于服务层中。下层的领域对象变得极为简单。如果下层是领域模型,则其中的对象与数据库一一对应,因而此时你就可以使用诸如活动记录等较简单的数据源层。
以上二者的折中是一个行为的混合体:控制器-实体风格(controller-entity style)。这一术语来源受到了[Jacobson et al.]的强烈影响。此处要点在于:将单个事务或用例所特有的逻辑置于事务脚本之中,它们通常被称为控制器或服务。有许多不同的控制器,如模型-视图-控制器中的输入控制器和我们稍后将会接触到的应用控制器。所以在此处我使用术语“用例控制器”(use-case controller)。可供多个用例调用的行为访问那些被称为实体的领域对象。
虽然控制器-实体方法很常用,但我一直不是很欣赏它。与事务脚本一样,用例控制器容易产生代码副本。我认为,如果你确实决定完全选用领域模型,就应当彻底贯彻这一模式,使它在应用程序中占主导地位。一个例外是你已经从采用了行数据入口的事务脚本开始了,那么可以将冗余行为移到行数据入口中,这样就将它转变成一个使用活动记录的简单领域模型。但是我不会一开始就这样做。我只会用这种办法来改进已有的存在缺陷的设计。
我并不是说绝不应设计包含了业务逻辑的服务层对象,而是说未必一定要将它们组成一个固定的层。过程化的服务层对象有时对细分逻辑是很有用的,但我偏向于只在必要时才使用这种对象,而不是将它们组织成一个架构中的层来使用。
因此,我的建议是:如果你确实需要,尽可能使用最小化的服务层。我通常首先假定并不需要这么一层,然后当发现应用确实需要时才增加。但是,我也知道许多优秀的软件设计者总是使用一个包含了合理数量逻辑的服务层,因此读者也不要拘束于我的观点。Randy Stafford在复杂服务层的应用方面有许多成功的案例,这也是我为什么请他执笔本书的服务层模式部分的原因。