软件开发项目中的关键决策:基于上下文图的策略性领域驱动开发(2)

发表于:2011-11-01来源:infoQ作者:Alberto点击数: 标签:
图4. 精简版本的BankingAccount类 通常,有些PFM应用也允许我们管理支付行为,并且持有一个收款人(Payee)注册器。在这个场景中,收款人可能与一个或者多个

  图4. 精简版本的BankingAccount类

  通常,有些PFM应用也允许我们管理支付行为,并且持有一个收款人(Payee)注册器。在这个场景中,收款人可能与一个或者多个银行帐户关联,但是对于收款人来说,我们既不能获取其银行帐户的内部情况,也不能在该帐户上触发任何操作。那么将“收款人帐户”与刚刚定义的BookingAccount类关联在一起是否正确呢?

  图5. Payee类与BankingAccount类

  恩......这听上去有些道理:毕竟它们都是相同的概念,在现实世界中,我们的帐户和收款人的帐户甚至会处在同一个物理上的银行里。另一方面,这样做似乎又不完全正确:因为我们不允许调用收款人银行帐户的任何操作,也不能追踪他们的任何信息。更糟的是,这样做了后,可能会在我们的程序中埋下一个概念的错误。

  我们应该如何做?我们应该再一次回到应用程序的两个不同的上下文里去:这一次我们可以采取两种不同的方式对同一个领域概念进行建模,因为对领域概念的两种使用场景明显不同,每一种都需要一个不同的模型。BankingAccount类仍然允许我们执行(或者跟踪)特定的操作(比如存款与取款),同时另一个独立的PayeeAccount类可能有一些和BankingAccount相同的通用数据(比如accountNumber),但是有一个简化的模型和完全不同的行为(比如我们不能访问收款人的余额信息)。图6所示的正是这种场景:尽管“银行帐户”有着清晰的含义,其底层概念也是惟一的,但是在应用程序中却以不同的方式被使用着。

  图6. BankingAccount和PayeeAccount类

  这看着似乎挺明显的,其实不然。当你设计类图,或者使用UML建模工具时,你可能很自然地让收款人具有一个bankingAccount属性,而且会庆幸“我刚好有一个这样的类”。Pavlovian试图去除代码中的重复,有时,它的作用会弊大于利。

  如图7所示的上下文图,可以用于表述上面讨论的示例。注意,只要我们关于环境的知识在增加,就将它反映在图中。在这个例子中,我们将PFM的应用上下文分成了“银行”和“开销跟踪”。

  图7. 非常简单的上下文图:画上了领域模型区域间的轮廓,可以看出在这些区域内保证了概念的完整性

  在这个例子中,两个上下文拥有一些逻辑上重叠的区域,即“银行帐户”的概念,它在应用程序的不用区域中,使用方式也不同,这意味着我们要使用不同的模型。但是两个模型又可能有非常紧密的交互。上下文图除了能帮助我们保证模型的概念在不同上下文边界内的完整性,它还能帮助我们关注在不同上下文之间出现的情况。在这个例子中,假设同一个团队正在两个上下文上同时工作,我们就需要让团队中的每位成员的明确两个上下文的区别,并且就两个上下文中出现的术语和概念,分享同一个转换的映射关系。

  示例3:外部系统

  再来考虑一下PFM。很多这种应用程序都需要与某些金融在线服务进行数据交换。在这个例子中,银行会为家庭银行服务提供实时的访问。其他的例子还包括允许用户下载通用标准格式(比如Money或者Quicken格式)的银行对帐单。但是从上下文图的视角来看,无论是交互活动还是通讯的方向(单向或是双向),并不重要。有一件事是要关注的,我们有了不同的模型。图8展示了PFM与在线银行应用程序的交互行为。

  图8. 在上下文图中,与外部应用的交互行为很自然地需要独立的界限上下文

  即使设计两个模型之初是让它们展现相同的数据(至少在一定程度上),但随着时间的推移,它们还是会受到不同因素的影响,而且它们也会用于不同的目的。因此,分离上下文边界是必须的。如果假设用户档案(User profiling)模块是由第三方库实现的,那么示例1也能够归入到这一类中。

  管理多个上下文

  当应用程序跨越了多个上下文后,我们必须管理上下文之间的关联。不同的界限上下文之间的关系,通常是我们深入观察项目的线索。

  有一件事情非常关键,即两个上下文之间的联系是有方向的。DDD用两个专门的术语表述它们:“上游(upstream)”和“下游(downstream)”,一个上游上下文会影响到相应的下游上下文,但是反过来就不一定了。这不仅体现在代码上(一个库依赖于另一个),还体现在技术含义较少的因素上,比如进度、对外部请求的响应,比如,当在线银行服务更改了API或者其他什么原因,我们的PFM银行应用程序都必须要快速地更新。所以我们的PFM上下文应该是下游的,而在线银行服务很明显就是上游的了。图9演示了这两种领域上下文的关系。

  图9. 分离的上下文之间的Upstream-downstream关系

  如果外部系统发生变化,我们可以接受这种变化,来更新与外部系统通讯的方式。不过我们仍然需要一些保护措施隔离来自上游上下文的变化,保证我们自己的“银行”的上下文的概念完整性。DDD包含了几种组织模式,帮助我们描述和管理不同的上下文交互方式。最适合我们在这里使用的是模式叫做反腐化层(Anti-Corruption Layer,ACL),它会在代码层面上实现显式的转换,转换可以在两个上下文之间,或者在“银行”上下文的外部边界上完成。这不仅局限于技术上的转换,比如Java转化为XML,同时也是一个很合适的机会,能够管理各个模型之间的所有微妙的不同。如下面的图10所示,我们在上下文图上添加了ACL。

原文转自:http://www.ltesting.net