领域模型管理与AOP

  英文原文:ASPects of Domain Model Management

  (作者:Mats Helander,译者:王丽娟)

  2007-12-23

  导言

  正如从像《领域驱动设计》[Evans DDD]和《领域驱动设计和模式应用[Nilsson ADDDP]这些书中学到的一样,在应用架构中引入领域模型模式(《企业应用架构模式》[Fowler PoEAA])一定会有很多益处,但是它们并不是无代价的。

  使用领域模型,很少会像创建实际领域模型类、然后使用它们那么简单。很快你就会发现,领域模型必须得到相当数量的基础架构代码的支持。

  领域模型所需基础架构当中最显著的当然是持久化——通常是持久化到关系型数据库中,也就是对象/关系(O/R)映射出场的地方。但是,情况并不止持久化那么简单。在一个复杂的应用中,用来在运行时管理领域模型对象的部分占了基础架构的很大一部分。我将基础架构的这部分称为领域模型管理(Domain Model Management)[Helander DMM],或简称为DMM。

  基础架构代码放在哪里?

  随着基础架构代码的增长,找到一个处理它的优良架构变得越来越重要。问题主要在于——我们是否允许把一些基础架构代码放在我们的领域模型类里面,还是无论如何应该避免这样做?

  避免基础架构代码进入领域模型类的论点是强有力的:领域模型应该表示应用程序所处理的核心业务概念。对于想大量使用其领域模型的应用来说,保持这些类干净、轻量级、易于维护是一个极好的架构目标。

  另一方面,我们接下来将会看到,保持领域模型类完全不含基础架构代码——通常被称为使用POJO/POCO(Plain Old Java/CLR Objects)领域模型,这种极端的路线也被证明是有问题的。最终往往导致采用笨重的、低效率的变通方法来解决问题——而且有些功能用这种方式根本不可能实现。

  也就是说,我们遇到的还是一个权衡利弊的情况,我们应该尽量在领域模型类里面只放必不可少的基础架构代码,决不超出这个限度。我们付出领域模型的轻微发胖,换来效率的提高以及使一些必要领域模型管理功能有可能实现。毕竟,软件架构很大程度上是关于如何做一笔好买卖。

  重构的时机到了

  不幸的是,长远看来,台面上的交易条件可能不够好。为了支持许多最有用和最强大的功能,你需要在领域模型类中放入基础架构代码实在太多了。其数量之大,很可能你的系统还没完成,业务逻辑代码就已经被淹没了。

  也就是说,除非我们能找到一种方法鱼和熊掌兼得。本文试图分析我们能否找到这样一种方式,既能将必要的基础架构代码分布到领域模型中,却又不会使领域模型类变得杂乱。

  我们先从一个应用看起,它将所有有关的基础架构代码都放到了领域模型类中。接着我们将重构这个应用,并且只用众所周知的、可靠的、真正面向对象的设计模式,使应用最后能具备相同的功能,但是基础架构代码却不会弄乱领域模型类。最后,我们将看看我们如何使用面向方面编程(ASPect Oriented Programming,AOP)来更简单地达到相同的效果。

  但是,为了看出AOP为何能帮助我们处理DMM需求,我们首先看看没有AOP的时候我们的代码会是什么样——首先是“最原始”的形式,这种形式里,所有的基础架构代码都放在领域模型类里面,然后是重构后的形式,其中基础架构代码已经被分离出领域模型类——虽然仍然分布在领域模型中!

  重构肥领域模型

  大部分的领域模型运行时管理是基于拦截的——也就是说,当你在代码中访问领域模型对象时,你所有对对象的访问都会根据相应功能的需要被拦截下来。

  一个明显的例子就是脏跟踪(dirty tracking。它可以用于应用的很多部分,以了解一个对象什么时候已经被修改了、但是仍未保存(它处于“脏”状态)。用户界面可以利用该信息提醒用户是否打算放弃任何未保存的修改,而持久化机制则可以利用它来辨明哪些对象是真正需要被保存到持久化介质中的,从而避免保存所有的对象。

  脏跟踪的一种方法是保持领域对象最初的、未修改版本的拷贝,并在每次想知道一个对象是否已经被修改的时候去比较它们。这个方案的问题是既浪费内存,又慢。一个更有效率的方法是拦截对领域对象setter方法的调用,以便每当调用对象的一个setter方法的时候,都为该对象设置一个脏标记。

  脏标记放在哪里?

  现在我们来看看把脏标记放在哪里的问题。一种是将它放在一个字典结构中,对象和标记分别作为键和值。这样做的问题在于,我们必须让程序中所有需要它的部分都能访问到这个字典。前面的例子已经可以看出,需要访问字典的包括用户界面和持久化机制这样截然不同的部分。


图 1

  将字典放在这些组件的任何一个内部,都会使其它组件难以访问它。在分层结构中,底层不能调用其上层(除了中心领域模型,它常常处于一个公共的、垂直的层里面,能被其它所有的层调用),因此要么把字典放在需要访问它的最低一层(图1),要么放在公共的、垂直的层里面(图2)。两种选择都不是很有吸引力,因为它引起了应用组件间不必要的耦合和不均衡的责任分配。


图 2

  一个更吸引人的、顺应面向对象思想的选择,是将脏标记放到领域对象本身中去,这样每个领域对象都带有一个布尔型的脏属性,来表明它是不是脏的(图3)。这样,任何组件想知道一个领域对象脏与否,可以直接问它。


图 3

  因此,我们把部分基础架构功能代码放在领域模型中,其部分原因就是我们希望从应用的不同部分都能拥有这些功能,而不会过度地增强耦合。用户界面部分不该知道如何向持久化组件询问脏标志,并且,我们宁愿在分层的应用架构中设计尽可能少的垂直层。

  这个理由很重要,单凭它就足以让一些人考虑采纳本文将要检验的这种方法,不过我们还是先看看其他方法。但是在这样做之前,我们先粗略地看一下争论的另一方——我们在领域模型类中限制基础架构代码的原因。

  肥领域模型反模式

  让我们看看,引入脏标记、并在适当时机要求拦截唤醒脏标记之后,领域类会是什么样子。这是一个C#代码的例子。

public class Person : IDirty
{
protected string name;
public virtual string Name
{
get { return name; }
set
{
if (value != name)
((IDirty)this).Dirty = true;
name = value;
}
}

private bool dirty;
bool IDirty.Dirty
{
get { return dirty; }
set { dirty = value; }
}
}

public interface IDirty
{
bool Dirty { get; set; }
}

it知识库领域模型管理与AOP,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。