您的位置:首页 > 其它

【译文】借助“上下文地图”进行战略级别的“领域驱动设计”

2009-12-17 21:20 309 查看
借助“上下文地图”进行战略级别的“领域驱动设计” 作者:Alberto Brandolini 译者:Abbott Zhao

介绍

当应用程序逐渐增大和变得更复杂时,面向对象建模的许多方法不能更好地适应这个变化。“上下文地图”是一个多功能技术,是“领域驱动设计(DDD)”工具包中的一部分,帮助架构师和开发者管理他们在软件开发项目中所面临的许多种类的复杂性。与其它大家共知的DDD模型不同的是,上下文地图可以应用到任何类型软件开发场景中,能够为开发者做出战略级别的决策提供高层面视图的帮助,比如,是否需要全面按照DDD实现的方式做,或者仅应用在特定的项目环境中。 本文中,我们研究有界限的上下文环境的许多边界问题,如何使用构建的上下文地图支持软件开发项目的关键决策。

涉及到的许多模式

领域驱动设计着重强调要保持应用程序模型在概念上的完整性。通过以下几个因素的组合来达到目的: l敏捷过程强调不停地从用户和领域专家那里得到反馈 真正领域专家的存在,同他们有创造性的协作 从Ubiquitous Language(见术语定义)方面看,能够精确定义出唯一、共享的模型版本(在应用程序和测试代码中) l有一个能够为改进和研究提供开放、透明的环境
这为高质量的设计创建了一个安全海港,能够繁荣和传达它的价值是至关重要的。在这样的场合,通常的DDD元素,如实体、值对象和聚焦,会按照明显的顺序提交给复杂领域模型。即使如此,传统的DDD方法,也不能够无视在没有提供完整的概念模型的情况下,应用到无限大的领域模型。 就如图1所示,DDD中的Ubiquitous Language的关键角色是起到对我们模型完整性检查的作用。使用非常精确和不含糊的相同术语定义含义,这些术语能够跨越领域专家到代码层面,无障碍地讨论,使团队中的每个人共享相同的领域和软件的远景。

图1 Ubiquitous Language是表达模型语言的唯一选择。团队的每一个人都清晰地、不需转换的理解每个特定的术语 代码是模型的最原始表达。尽管在捕获需求或者设计的片段期间,其它工件也是必须的,能和应用程序行为经常保持一致步伐的仍然是代码本身。这种建模的极乐世界是一个有点脆弱的生态系统:它能够完成以前所描述的条件,但是它不能无限制地扩展。最大化扩展一个可以被拉伸的模型,而没有影响它概念上的完整性,这个就叫做“上下文环境”。

进入有界限的上下文环境

在领域驱动设计中,“上下文环境”如此定义: “隐藏在一句话或者一个声明中,能够确定它背景的含义”,它能够探测到起初读它时的某些模糊歧义。说不清楚的期望规模、模糊的情况,或者其它的上下文环境特征。最终,我们发现,这个定义是相当精确,准确地描述一个上下文环境是什么,但,我们可以使用一些具体的例子来获取它的一些含义。

例子1:同语不同义

让我们用一个简单的例子说明术语表中的歧义发生在哪里。一些用语在它所依赖于的上下文环境中,会有不同的含义。 假设,我们使用一个基于WEB的个人理财管理应用程序(PFM)。我们使用这个应用程序管理我们的银行帐、股票和储蓄,跟踪预算和费用,等等。 在这个应用程序中,领域术语“账户”代表了不同的概念。谈论银行时,账户在逻辑上是“钱匣子”的意思;然后,我们期望这个类能够有一些属性,如:余额、账号等等。但是,在WEB应用程序的环境中,账户的有一个非常不同的含义,代表的是一个审核和用户证书。对应的模型,如图2中所示,会使用不同的术语表示它们的不同。

图2 有点不能说明意义的歧义:术语“账户”在不同的上下文环境中代表不同的含义 这个或许是在我们为我们的应用程序建模时,遇到的简单的歧义情况:相同的术语,有不同依赖于上下文环境的含义。这个可以通过对类名(作为包的不同部分)增加前缀区分它们来解决。但在概念层面,我们必须注意,我们有两个不同的上下文环境在参与,有时,它们的不同,足以可以预防开发者做错事,但,有时,差异是很微妙。 不幸地,使用类名这个方法,不总是一个切实可行的解决方法:在银行的领域,术语“银行账户”已经存在,又有了一个不同的意思,或者领域专家坚持认为,“账户“是一个正确的术语。坚持不接受一个特定的妥协术语,或者从领域专家术语中导入一个转换补充到代码中。这里,你要面对两个隔离的上下文环境。

画出我们的第一个上下文环境地图

当歧义发生妨碍时,我们需要一个工具来让开发团队明白两个不同上下文环境在应用程序中的不同。歧义是Ubiquitous Language(见术语说明)的超级恶棍,我们需要消除它。实现这个的最好方法是,在一个上下文环境地图里,从有界限的上下文环境方面,暴露领域结构。图3显示了一个简单的上下文环境地图。

图3 一个简单的上下文环境地图,有两个领域上下文环境参与 在领域驱动设计的书中,“上下文环境地图“的设计,是用来区分上下文环境边界的基本工具。基本的理念是,在白板上画出上下文环境的边界,使用相关的领域类术语填充到他们中去。这不是一个准确的UML范型:它是一个可采用的工具,允许我们映射模糊情景,所以,有些模糊似乎是必然的。

例子2:同概念,不同应用

更让你难缠的差异发生在替在的概念是相同的,但使用的地方不同,最终会导致不同模型。我们的“银行账户”模型就象下面图4中所再现的BankingAccount类一样。

图4 一个真实但简单的BankingAccount类版本 一些PFM应用程序也允许我们管理支付,通常会保存一个“收款人注册薄”。在这个场景中,一个收款人可能被分配一个或多个“银行账户”,这个情况下,我们并不知道收款人的银行帐户的内部情况,我们也不能在这些账户上作任何操作。刚才我们定义的“收款人”账户的BankingAccount类又有什么意义呢?

图5 收款人与BankingAccount类 那么…听起来很合理:所有相同概念的背后,在真实的世界中,我们的账户和收款人的一个账户甚至可能是相同的物理账户。但,仍然感觉不出来完全正确:我们不期望能够在收款人“银行账户“上有任何操作,或者能够跟踪到什么。即使最坏:如此的作法,在我们的应用程序中,或许是一个概念性的错误。 那么,我们要作些什么?我们仅需要在相同的应用程序中带出两个不同的上下文环境:这次,我们能够在两个不同的方法中,建立相同的领域概念的建模,因为我们有了两个清晰且截然不同的应用,每一个都需要截然不同的模型。我们增加一个一个隔离类PayeeAccount,这个类可能与BankingAccount有一些一样的数据(如:账户编号),除了更简单的模型和一些已定义好的不同的行为(如,我们不能访问收款人余额),这个时候,BankingAccount仍然是一个我们可以执行(或跟踪)特定操作(如,deposit(存款)和withdraw(取款))的类。图6中所显示,尽管拥有一个清晰的含义,而且术语仅有一个潜在的概念,我们却能够使用在应用程序中的不同地方。

图6 银行和收款人账户类 这个看起来似乎有明显的优势,但不尽然。当它显示在类图中时,或者UML建模工具中时,你很自然地会构建一个“收款人”,这个“收款人”拥有一个“银行账户”的属性,而且,认为”我已经为它准备了一个类“。有时,试图去掉代码的冗余是弊大于利。 前面的简单上下文环境地图例子可能看起来像下面的图7所示。注意,就环境增长而言,也反应在这个地图中。这个情况下,我们分隔了PFM应用程序上下文环境到“银行”和”费用跟踪“中去。

图7 一个非常简单的上下文环境:围绕在维持概念完整性的范围内的领域模型组成的一部分轮廓 这种情况下,两个上下文环境有逻辑上的重叠部分:在应用程序不同部分中,银行账户的概念被不同方法使用,意味着我们有不同的模型参与。然而,两个模型或许正在进行着松散的交互。在上下文环境的边界内,维持着模型的完整性,这个上下文环境帮助我们关注,不同上下文环境之间发生的事情。这种情况,假如两个上下文环境被应用在同一个团队中,我们需要团队中的每个人明白两个不同上下文环境,最终共享一个显示在两个模型中的术语和概念的交换地图。

例子3:外部系统

让我们再次思考下PFM应用程序。许多这些的应用程序允许与财务非银行金融机构的在线服务一些类型进行数据交换,其它的一些情况,它们简单地允许用户使用通常的标准格式(如,货币或者Quicken格式)下载银行结算单。然而,从上下文环境地图角度看,与通讯的方向和交互(单向或者双向)不相关。这样的事情其中之一是,我们将再次会有不同的模型出来。图8显示PFM银行应用程序与在线银行服务应用程序之间的交互。

图8 一个外部应用程序同在我们的上下文环境的一个隔离的有界限上下文环境的交互 即使两个模型的设计把它们的不同表现了相同的数据(至少给定一个范围),随着时间的推移,它们的关注点也会遭受不同的演变,它们会为截然不同目的提供服务。因此,上下文环境的隔离是必须的。例子1中,如果用户的信息被第三方类库提供,它们也会陷进这类情况。

管理多种上下文环境

当我们的应用程序跨越多个上下文环境时,我们也需要管理它们之间发生的事情。不同的上下文环境之间的关系经常提供非常重要的洞察力。 我们知道这些重要性其中一个是两个上下文环境之间关系的方向。DDD使用术语,上流或者下流:上流的对立面不为真时,上流的上下文环境将会影响下流对应的事物。这个会被用到代码(类型其它的类库),而且涉及到很少的技术因素,如进度或者外部请求的反应时间。在例子中,我们有一个外部系统,很明显,在我们的PFM银行应用程序必须快速地刷新时,它不能按照我们的要求而改变,这个情况下,在线银行服务因为任何原因都可能改变它们的API。如图9,说明了两个领域上下文环境之间的这个关系。

图9 隔离上下文环境之间的上流――下流关系 我们能够接受,基于外部的需求,为我们所通信的外部系统,提供相适应的方法,但是,我们或许还需要一些类型的保护,应变来自上流上下文环境的变化,保护我们银行上下文环境概念的完整性。DDD描述了几个组织模式,帮助我们来描述或(与)管理不同上下文环境交互之间的方法。这里有一个名字叫Anti-Corruption Layer(ACL,见术语解释),是最新合适的模式,在代码层面,两个上下文环境之间要求有发生一个明确的转换,或者在银行上下文环境的外界上进行转换会更好。这个不仅是一个技术转换,如从使用JAVA进行XML转换,而且还要在两个终端的模式之间,有一个地方可以处理所有微妙的差异。在上下文环境地图上指出我们的ACL,能够产生一些如下图10所显示的东西。

图10 PFM应用程序边界的ACL,防止在线银行服务泄漏我们的绑定的上下文环境 不仅外部系统需要一个隔离的上下文环境。一个存在的遗留的组件,也经常会有一个不容易演化成功的模型。尽管在我们组织内会持续维护它,即使这个模型会遇到不同类型的关注,且会导致不同与我们当前使用的。如果我们必须与遗留的系统进行交互,与不同的绑定上下文环境的相遇机会是高的。 上下文环境地图的其它关系怎么样?我们是否可以按照相关的DDD模式来进行分类?由于我们假设开发是单个团队中进行的,所以,这里的模式交互是很轻微的。然而,如果银行和费用跟踪是被不同的团队维护,它们可能就是合作关系:它们二者的开发向一个共同的目标前进(那么,它们就处于相同的层面,上流――下流就没有意义)。如果WEB用户概况(WEB User Profiling)需要用一个外部模块来实成,我们就要打算使用它的“as-is”,这意味着,我们处于下流,且要遵守规成,向WEB用户概况(WEB User Profiling)靠近。

图11 简化相关模式之后的上下文环境地图

例子4 按比例放大结构

迄今为止,我们仅考虑了一个开发团队的简单场景。这可以让我们忽略通讯成本,假设(或许是乐观地)认为,团队中的每一个开发者都能够明白“使用的模型是什么”。更很复杂的场景可能包括以下的一些影响因素: l 领域复杂(需要许多不同的领域专家) l 结构复杂 l 更长期的项目(时间) l 非常大的项目(每人日) l 许多外部、隔离的或者遗留的系统包括进来 l 大型团队规模,或者多个开发团队 l 分散的或者海外团队 l 人的因素
这些因素每一个都会影响发生在开发团队里面的沟通途径,通常情况下的组织结构,最终会影响到成型的软件。 单个团队,特别是配对编程的敏捷环境中,有许多效率高的方法使团队成员共享信息:面对面交流,参加设计会谈,成对编程,会议,信息散播器(information radiator)等等。不幸地是,在团队规模和数量增加时,这些技巧不能更好地按比例放大,以致在横穿单个开发团队的边界中,很难共享模型的概念完整性。 毕竟,我们一致认为,模型是十分精密的沟通格式,包括问题的公共理解、解决方案上的可能的相同视觉。在沟通不易的场景下,这样作能够逐步会比协商所成生的结果更能廉价。这个沟通瓶径的一个典型的结果是,相同代码基础的不同地方,存在各种各样的类的后代,但它们作的基本上是作相同的事情。 让我们假设,现在,我们的PFM应用程序变得很大,另一个团队(团队B)已经在相同的应用程序中的新的交易模块上同我们(显然我们是团队A的)合作。团队B可能是在不同的房间、建筑物、城市、公司或者国家,且全心于集中在交易领域。这个例子的背后,团队A与团队B要共享一些代码,即使它们倾向隔离在代码基础的局部。最终,团队B写一些类(下面图12中A),实现了团队B需要的一些功能,这些在类A中已经存在。

图12 不同的团队访问相同代码基础的一个场景,在模型的局部上,它们可能有不同的视觉。团队的物理分布将会影响团队之间共享信息的质量 那里的代码是冗余的,所有讨厌之源!在一个人的情况下,即时已经定义好,边界的上下文环境,这也是非常真实存在的。但,在几乎每个具有价值的项目里,发生的这些也是有一些原因的。这通常是一个信号,在一些项目领域中,可能没有很好隔离开涉及的上下文环境。有时,造就两个隔离的上下文环境更有效的方法是,结构化思考我们领域模型,然后,迫使两个团队持续化集成它们的想象。 那么,如何在我们的地图上画出这个?一个上下文环境反映了我们能够理解的整体系统当前层面,且随着我们学习到更多,或者随着环境的变化,我们会修订它。当前,我们不能够准确知道将来会发生什么,这就是“我们当前能够理解的层面”。

图13 没有很好隔离的交易上下文环境,需要未来进一步探究,或者合情合理的设计决策 图中的警告标志意思是,这里的有些事情不正确:两个上下文环境局部重叠,且它们的关系不清晰。这是我们要首先解决领域中的一个问题是,努力创建一致的意见,且维持上下文环境里面的合理使用的关系,就像客户-供应商关系,持续集成或者共享内核。但,这是明天的工作。一张上下文环境地图是为现在准备的一个工具,今天的问题尚未解决,所以,我们要离开图中的警告标志。 不要被颜色和阴影所迷惑:我就是为了打印更好看一些。真实的上下文环境地图可能看起来会很零乱,至少与你的项目一样零乱。但,这里的警告标志要告诉我们,这个地方是上下文环境没有被清晰隔离的领域,如果我们不再作点什么,整体上的东西可能容易转换成一个Big Ball Of Mud(见术语介绍)(适应性最强的DDD组织模式)。

一个非常规的视觉

上下文环境地图迫使我们把非软件方面的内容也包括到全局的写照中,最后确定出传统架构分析“范围之外”的热点领域的位置。 例如,组织内对最终软件产生大量影响的沟通途径。通常,如果小规模的使用是定义上下文环境边界的第一因素,那么,大规模的沟通效率和项目组织将会逐渐成为关键因素。像Wikis、e-mail或者即时通讯这样的工具,伪造了一个能够同步共享团队每一个成员知识的概念。但,我们都知道这仅是一个梦想:通常的大规模项目中,我们并不会成为像Borg(博格,瑞典网球运动员)那样集体智慧的一部分,一些家伙几乎不知道团队之外正在发生什么。 在大规模组织时定义上下文环境是一种挑战,但也一个值得作的工作。许多次,团队并不通晓涉及到的上下文环境;由于很少人或者没有明白整体局面的原因,从而发生了违背模型完整性事情。画出上下文环境地图是一个值得研究的课题,当初的许多地方的信息都没有被修补,边界最初是模糊的,数个步骤后,才获得了整体局面的快照。



图14 地图的最后版本。不期望它是“最终的”,有些东西总是还需要进一步学习 可能会有更多的上下文环境参与,例如,交易可能需要被链接到一些在线股票行市服务,但,那是一个“交易”问题!一个上下文环境地图同我们周围的环境相关,我们(团队A)工作在银行和费用跟踪应用程序领域:我们仅对直接关联到和影响到我们软件的上下文环境有兴趣。 只要我们采集更多信息,地图就会更清晰。就像以前提及的,只需要简单地承认,在我们的应用程序中有不同的模型参与,承认仅在已定义好的上下文环境边界里面保持模型的完整性,就给我们领域建模观点中,提供大量的价值。许多模型随着它的增长,逐渐地失去完整性,在这种意义中,上下文环境地图给予我们大量的帮助。

关于战略上的DDD模式

我们在这里使用的模式在使用方式上有微妙的差异:虽然定义是相同的--已证实解决重复问题的方案――但很难成为代表我们选择的解决方案。多半情况下,在从事一个败局之前,组织结构会强加于一些模式,而我们仅有的希望是来认识它们。有时,我们也会偶尔有机会来选择最好方法,或者改变存在的败局,但,我们必须能够认清,在组织层面的改变比在我们的项目范围层面上需要花费更多的时间,或者简单地说,超越了我们的能力。 如果您从开始的地方就存在疑问,开始于开发团队。一个团队或许是一个能够有效共享一个模型上的远景的大规模组织单元。一旦认识到这一点,多上下文环境能够被相同团队来管理,最终归结于主架构选择上。 每个模式都有不同成本分摊:即使解决相似不容易交换的问题(起链接的上下文环境)。例如,防出错层(Anti-Corruption Layer,防止由于软件或者硬件出错,而造成数据的欠缺的层)有一个代码层面上的覆盖区(一个额外的层),而在组织上却是一个非常小的覆盖区。在一个合作伙伴,或者一个客户-供应商或许只需要很少的代码,和单个代码的时候,却没有能够有效的通讯通道和一个已定义好的进程。努力建立的一个合作伙伴,却没有一个协调的环境,很明显会最终走向死亡。

总结

转换到上下文环境最初的定义-“隐藏在一句话或者一个声明中,能够确定它的背景含义”――是相当准确的定义,从设计层面到架构和组织层面的增长,也没有丢失准确性和有效性。尽管有一些合理的“无差异的期望”存在,模型也不能无限制地被拉伸。有界限的上下文环境提供很好的安全港湾,允许模型在不破坏概念的完整前提下,适应不断增长的复杂性。 对于它的另外作用,在应用到大规模项目上时,上下文环境地图也显示了存在组织内部的边界,提供鲜明地、非图片化的,我们项目所处在的阶段上快照。一个好的上下文环境地图应该能够提供给我们一个对你有利的写照。实际上,即使在项目开始之前,你也知道,组织是否――有意识地或者没有意识地――为你的项目成功提供有利的条件。 作为一个顾问,我也发现上下文环境难以置信地有利于快速抓住我的客户项目整体风景中关键明细,而且能够成为一个战略决策支持工具(无论从何角度看,地图都是为这个服务)。一个上下文环境地图提供一张系统的总览图,UML或者架构图在完整性上可能遗漏的,它能够帮助我们关注于选择,在我们的场景下是切实可行,而没有在“大范围内一厢情愿”地浪费金钱。

关于作者

Alberto Brandolini是一位信息技术顾问和培训师,具有全能的软件开发方法。Avanscoperta的创始人和拥有者――一个意大利的咨询和软件开发公司 ―― 他也是DDD和Grails的意大利社区的赞助者。

术语定义

Ubiquitous Language

摘自《领域驱动设计》 定义: 服务于领域模型的结构化语言,被所有的团队成员使用,链接软件团队的所有活动。
问题:
在它的语言被割断时,一个项目需要面对一序列的问题。在技术团队成员有他们自己的语言来进行讨论设计方面的领域的同时,领域专家也有他们使用的行话。 日复一日讨论的术语,被内嵌在代码(最终体现在软件项目的最重要产品)里面的术语所中断。相同的人使用不同的语言谈论和写,以便最直接的领域压力经常是浮现在转换格式中,这些是无法在代码或者写中过程中能够被捕获。 通过用可供选择的表达式的实验来消除困难,表达了可供选择的模型。然后,重构代码,重命名类,方法和模块来适应新的模型。解决了交流过程方面的烦恼,使用这个方法,我们就唤醒了对平常单词的含义共同理解。 承认Ubiquitous Language中的一个变化是对模型的一个变化。
领域专家应该反对不灵活的或者不足承担传达领域理解的术语和结构;开发者应该观察含糊的话,或者阻碍设计的矛盾。

Anticorruption Layer

问题: 当一个必须有与其它系统之间有大量的接口的新系统构建时,与两个模型相关的困难最终会覆盖新模型的意图,用特定方式,引起它的修改,相似于其它系统模型。遗留系统的模型通常是弱的,甚至作的很好的异常也不适合当前项目的需求。然而,在合并中,存在大量的价值,有时它是一个绝对的需求。 解决方案:
创建一个隔离层,提供他们自己拥有的领域模型的功能的代理。这个层通过它存在接口来沟通其它系统,需要很少或者不要修改其它系统。主观上,这个层转换了两个模型之间必须的两个方向。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: