【第四部分】战略设计
第14章:保持模型的完整性
模型最基本的要求是它应该保持内部一致,术语总具有相同的意义,并且不包含相互矛盾的规则:虽然我们很少明确地考虑这些要求。模型的内部一致性又叫统一(unification),这种情况下,每个术语都不会有模棱两可的意义,也不会有规则冲突。除非模型在逻辑上是一致的,否则它就没有意义。在理想世界中,我们可以得到涵盖整个企业领域的单一模型。这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义。每个有关领域的逻辑声明都是一致的。
但,大型系统开发并非如此理想。在整个企业系统中保持这种水平的统一是一件得不偿失的事情。在系统的各个不同部分中开发多个模型是很有必要的,但我们必须慎重地选择系统的哪些部分可以分开,以及它们之间是什么关系。我们需要用一些方法保持模型关键部分的高度统一。所有这些都不会自行发生,而且光有良好的意愿也没用的。它只有通过有意识的设计决策和建立特定过程才能实现。大型系统领域模型的完全统一既不可行,也不划算。
模式:BOUNDED CONTEXT(边界上下文)
细胞之所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜。
任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现bug,变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。
因此:明确地定义模型所应用的上下文。根据团队的组织,软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要收到边界之外问题的干扰和混淆。
但记住,BUOUNDED CONTEXT不是MODULE。有时这两个概念易引起混淆,但它们是具有不同动机的不同模式。确实,当两组对象组成两个不同模型时,人们几乎总是把它们放在不同的MODULE中。这样做的确提供了不同的命名空间(对不同的CONTEXT很重要)和一些划分方法。但人们也会在一个模型中用MODULE来组织元素,它们不一定要表达划分CONTEXT的意图。MODULE在BOUNDED CONTEXT内部创建的独立命名空间实际上使人们很难发现意外产生的模型分裂。
我们通过定义这个BOUNDED CONTEXT,最终得到了什么?对CONTEXT内的团队而言:清晰!对于CONTEXT之外的团队而言:自由。当然,边界只不过是一些特殊的位置。各个BUONDED CONTEXT之间的关系需要我们仔细地处理。CONTEXTMAP(上下文图)画出了上下文范围,并给出了CONTEXT以及它们之间联系的总体视图,而几种模式定义了CONTEXT之间的各种关系的性质。CONTINUOUS INTEGRATION(持续集成)的过程可以使模型在BOUNDED CONTEXT中保持一致。
如何识别BOUNDED CONTEXT中的不一致?很多征兆都可能表明模型出现了差异。最明显的是已编码的接口不匹配。对于更微妙的情况,一些意外行为也可能是一种信号。采用了自动测试的CONTINUOUS INTEGRATION可以帮助捕捉到这类问题,但语言上的混乱往往是一种早期信号。
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念,而假同源是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。假同源可能稍微少见一点,但它潜在的危害更大。
模式:CONTINUOUS INTEGRATION(持续集成)
当很多人在同一个BOUNDED CONTEXT中工作时,模型很容易发生分裂。团队越大,问题就越大,但即使3、4个人的团队也有可能会遇到严重的问题。然而,如果将系统分解为更小的CONTEXT,最终又难以保持集成度和一致性。
因此:建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分类问题。严格坚持使用UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同的概念时,是所有人对模型都能达成一个共识。
模式:CONTEXT MAP(上下文整体关联图)
其他团队中的人员并不是十分清楚CONTEXT的边界,他们会不知不觉地做出一些更改,从而使边界变得模糊或者使互连变得复杂。当不同的上下文必须互相连接时,它们可能会互相重叠。
因此:识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。先将当前的情况描绘出来,以后再做改变。
CONTEXT MAP无需拘泥于任何特定的文档格式。我发现类似本章的简图在可视化和沟通上下文图方面很有帮助。有些人可能喜欢使用较多的文本描述或别的图形表示。在某些情况下,团队成员之间的讨论就足够了。需求不同,细节层次也不同。不管CONTEXT MAP采用什么形式,它必须在所有项目人员之间共享,并被他们理解。它必须为每个BOUNDED CONTEXT提供一个明确的名称,而且必须阐明联系点和它们的本质。
下面介绍的这些模式涵盖了将两个模型关联起来的众多策略。这些模式的主要区别包括你对另一个模型的控制程度、两个团队之前合作水平和合作类型,以及特性和数据的集成程度。
模式:SHARED KERNEL(共享内核)
当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够获得快速进展,但他们开发出的产品可能无法结合到一起。租后可能不得不耗费大量精力在转换层上,并且频繁地进行改动,不如一开始就用CONTINUOUS INTEGRATION那么省心省力,同时这也造成重复工作,并且无法实现公共的UBIQUITOUS LANGUAGE所带来的好处。
因此:从模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位,一个团队在没与另一个团队商量的情况下不应擅自更改它。功能系统要经常进行集成,但集成的频率应该比团队中CONTINUOUS INTEGRATION的频率低一些。在进行这些集成的时候,两个团队都要运行测试。
模式:CUSTOMER/SUPPLIER DEVELOPMENT TEAM(客户/供应商 开发团队)
我们经常会碰到这样的情况:一个子系统主要服务于另外一个子系统;“下游”组件执行分析或其他功能,这些功能向“上游”组件反馈的信息非常少,所有依赖都是单向的。两个子系统通常服务于完全不同的用户群,其执行的任务也不同,在这种情况下使用不同的模型会很有帮助。
如果下游团队对变更具有否决权,或请求变更的程序太复杂,那么上游团队的开发自由度就会受到限制。由于担心破坏下游系统,上游团队甚至会受到抑制。同时,由于上游团队掌握优先权。下游团队有时也会无能为力。
因此:在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。两个团队共同开发自动化验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。
模式:CONFORMIST(承诺)
当两个具有上游/下游关系的团队不归同一个管理者指挥时,CUSTOMER/SUPPLIER TEAM这样的合作模式就不会凑效。勉强应用这种模式会给下游团队带来麻烦。
当两个开发团队具有上/下游关系时,如果上游团队没有动力来满足下游团队的需求,那么下游团队将无能为力。出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。下游项目只能被搁置,直到团队最终学会利用现有条件自力更生为止。下游团队不会得到根据他们的需求而量身定做的接口。
因此:通过严格遵从上游团队的模型,可以消除在BOUNDED CONTEXT之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY模式可以极大地简化集成。此外,这样还可以与供应商团队共享UBIQUITOUS LANGUAGE。供应商处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息。
SHARED KERNEL是两个高度协调的团队之间的合作模式,而CONFORMIST模式则是应对一个对合作不感兴趣的团队进行集成。
模式:ANTICORRUPTION LAYER(隔离层)
新系统几乎总是需要与遗留系统或其他系统进行集成,这些系统具有自己的模型。当把参与集成的BOUNDED CONTEXT设计完善并且团队相互合作时,转换层可能很简单,甚至很优雅。但是,当边界那侧发生渗透时,转换层就要承担起更多的防护职责。
当正在构建的新系统与另一个系统的接口很大时,为了克服连接两个模型而带来的困难,新模型所表达的意图可能会被完全改变,最终导致它被修改得像另一个系统的模型了(以一种特定风格)。遗留系统的模型通常很弱。即使对于那些模型开发得很好的例外情况,它们可能也不符合当前项目的需要。然而,集成遗留系统仍然具有很大的价值,而且有时还是绝对必要的。
因此:创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。
这种连接两个系统的机制可能会使我们想到把数据从一个程序传输到另一个程序,或者从一个服务器传输到另一个服务器。我们很快就会讨论技术通信机制的使用。但这些细节问题不应与ANTICORRUPTION LAYER混淆,因为ANTICORRUPTION LAYER并不是向另一个系统发送消息的机制。想反,它是不同的模型和协议之间转换概念对象和操作的机制。
如何设计ANTICORRUPTION LAYER的接口?
ANTICORRUPTION LAYER的公共接口通常以一组SERVICE形式出现,但偶尔也会采用ENTITY的形式。在我们的模型中,把外部系统表示为一个单独组件可能是没有意义的。最好是使用多个SERVICE(或偶尔使用ENTITY),其中每个SERVICE都使用我们的模型来履行一致的职责。
如何实现ANTICORRUPTION LAYER?
对ANTICORRUPTION LAYER设计进行组织的一种方法是把它实现为FACEDE、ADAPTER和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。
模式:SEPARATE WAY(各行其道)
我们必须严格划定需求的范围。如果两组功能之间的关系并非必不可少,那么二者完全可以彼此独立。因为集成总是代价高昂,而有时获益却很小。
因此:声明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。
采用SEPARATE WAY(各行其道)模式需要预先决定一些选项。尽管持续重构最后可以撤销任何决策,但完全隔离开发的模型是很难合并的。如果最终仍需要集成,那么转换层将是必要的,而且可能很复杂。当然,不管怎样,这都是我们将要面对的问题。现在,让我们回到更为合作的关系上,来看一下几种提高集成度的模式。
模式:OPEN HOST SERVICE(开放主机服务)
当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。需要维护的东西会越来越多。而且进行修改的时候担心的事情也会越来越多。
因此:定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。
这种通信形式暗含一些共享的模型词汇,它们是SERVICE接口的基础。这样,其他子系统就变成了与OPEN HOST(开放主机)的模型相连接,而其他团队则必须学习HOST团队所使用的专用术语。在一些情况下,使用一个众所周知的PUBLISHED LANGUAGE(公开发布的语言)作为交换模型可以减少耦合并简化理解。
模式:PUBLISHED LANGUAGE(公开发布的语言)
与现有领域模型进行直接的转换可能不是一种好的解决方案。这些模型可能过于复杂或设计得较差。它们可能没有被很好地文档化。如果把其中一个模型作为数据交互语言,它实际上就被固定住了,而无法满足新的开发需求。
因此:把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换。
“大象”的统一
讲一个盲人摸象的故事:
第一个盲人碰巧摸到了大象宽阔结实的身躯,就以为大象就像一堵墙;
······
第三个盲人碰巧把扭动着的象鼻抓在书中,就大胆认为大象就像一条蛇;
第四个盲人急切伸出双手,摸到了大象的膝盖,就很明显地认为大象就像一颗树;
······
第六个盲人一开始摸这头大象,就抓住了它摆动着的尾巴,就认为大象就像一根绳子。
即便他们对大象的本质不能达成完成的一致,这些盲人仍然可以根据他们所触摸到的大象身体的部位来扩展各自的认识。如果并不需要集成,那么模型统不统一就无关紧要。如果他们需要进行一些集成,那么实际上并不需要对大象是什么达成一致,而只要接受各种不同意见就会获得很多价值。这样,他们就会在不知不觉中各执己见。
当盲人想要分享更多大象的信息时,他们会共享单个BOUNDED CONTEXT得到更大的价值。但统一多个模型几乎总是意味着创建一个新模型:
经过一些想象和讨论(也许是激烈的讨论)之后,盲人们最终可能会认识到他们正在对一个更大整体的不同部分进行描述和建模。从很多方面来讲,部分-整体的统一可能不需要花费很多工作。至少集成的第一步只需弄清楚各个部分是如何相连的就够了。
尽管我们已经把部分合并成一个整体,但得到的模型还是很简陋的。他缺乏内聚性,也没有形成任何潜在的领域的轮廓。在持续精华的过程中,新的理解可能会产生更深层的模型。
承认多个相互冲突的领域模型实际上正式面对现实的做法。通过明确定义每个模型都适用的上下文,可以维护每个模型的完整性,并清楚地看到要在两个模型之间创建的任何特殊接口的含义。盲人没办法看到整个大象,但只要他们承认各自的理解是不完整的,他们的问题就能得到解决。
选择你的模型上下文策略
在任何时候,绘制出CONTEXT MAP来反映当前状况都是很重要的。但是,一旦绘制好CONTEXT MAP之后,你很可能想要改变现状。现状,你可以开始有意识地选择CONTEXT的边界和关系。以下是一些指导原则:
1、团队决策或更高层决策
按照本身价值来说,在决定是否扩展或分割BOUNDED CONTEXT时,应该权衡团队独立工作的价值以及能产生直接且丰富集成的价值,以这两种价值的成本-效益作为决策的依据。
在实践中,团队之间的行政关系往往决定了系统的集成方式。由于汇报结构,有技术优势的统一可能无法实现。管理层所要求的合并可能并不实用。你不会总能得到你想要的东西,大你至少可以评估出这些决策的代价,并反映给管理层,以便采取相应的措施来减少代价。
2、置身上下文中
开发软件项目时,我们首先是对自己团队正在开发的那些部分感兴趣(“设计中的系统”),其次是对那些与我们交互的系统感兴趣。这是一种简单、典型的情况,能让你对可能遇到的情形有一些粗略的了解。
实际上,我们正式自己所处理的主要CONTEXT的一部分,这会在我们的CONTEXT MAP中反映出来。只要我们知道自己存在偏好,并且超过该CONTEXT MAP的应用边界时能够意识到已越界,那么就不会有什么问题。
3、转换边界
在画出BOUNDED CONTEXT的边界时,有无数种情况,也有无数种选择。但权衡时所要考虑的通常是下面所列出的某些因素。
首选较大的BOUNDED CONTEXT:
□ 当用一个统一模型来处理更多任务时,用户任务之间的流动更顺畅。
□ 一个内聚模型比两个不同模型再加它们之间的映射更容易理解。
□ 两个模型之间的转换可能会很难(有时甚至是不可能的)。
□ 共享语言可以使团队沟通起来更清楚。
首选较小的BOUNDED CONTEXT:
□ 开发人员之间的沟通开销减少了。
□ 由于团队和代码规模较小,CONTINOUS INTEGRATION更容易了。
□ 较大的上下文要求更加通用的抽象模型,而掌握所需技巧的人员会出现短缺。
□ 不同模型可以满足一些特殊需求,或者是能够把一些特殊用户群的专门术语和UBIQUITOUS LANGUAGE的专门术语包括进来。
4、接受那些我们无法更改的事物:描述外部系统
最好从一些简单的决策开始。一些子系统显然不在开发中的系统的任何BOUNDED CONTEXT中。一些无法立即淘汰的大型遗留系统和那些提供所需服务的外部系统就是这样的例子。我们很容易就能识别出这些系统,并把它们与你的设计隔离开。
在做出假设时必须要保持谨慎。我们会轻易地认为这些系统构成了其自己的BOUNDED CONTEXT,但大多数外部系统只是勉强满足定义。
5、与外部系统的关系
这里可以应用3种模式。首先,可以考虑SEPARATE WAY模式。当然,如果你不需要集成,就不用把它们包括进来。但一定要真正确定不需要集成。只为用户提供对两个系统的简单访问确实够用吗?集成要花费很大的代价而且还会分散精力,因此要尽可能为你的项目减轻负担。
如果集成确实非常重要,可以在两种极端的模式之中选择:CONFORMIST模式或ANTICORRUPTION LAYER模式。
6、设计中的系统
你的项目团队正在构建的软件就是设计中的系统。你可以在这个区域内声明BOUNDED CONTEXT,并在每个BOUNDED CONTEXT中应用CONTINOUS INTEGRATION,以便保持它们的统一。但应该有几个上下文呢?各个上下文之间又应该是什么关系呢?
情况可能非常简单:设计中的整个系统使用一个BOUNDED CONTEXT。例如,当一个少于10个人的团队正在开发高度相关的功能时,这可能就是一个很好的选择。
随着团队规模的增大,CONTINOUS INTEGRATION可能会变得困难起来(尽管我也曾看过一些较大的团队仍能保持持续集成)。你可能希望采用SHARED KERNEL模式,并把几组相对独立的功能划分到不同的BOUNDED CONTEXT中,使得在每个BOUNDED CONTEXT中工作的人员少于10人。在这些BOUNDED CONTEXT中,如果有两个上下文之间的所有依赖都是单向的,就可以建成CUSTOMER/SUPPLIER DEVELOPMENT TEAM。
你可能认识到两个团队的思想截然不同,以致他们的建模工作总是发生矛盾。如果这种矛盾的原因是你无法改变或不想改变的,那么可以让他们的模型采用SEPARATE WAY模式。在需要集成的地方,两个团队可以共同开发维护一个转换层,把它作为唯一的CONTINOUS INTEGRATION点。这与同外部系统的集成正好相反,在外部集成中,一般由ANTICORRUPTION LAYER来起调节作用,而且从另一端得不到太多的支持。
一般来说,每个BOUNDED CONTEXT对应一个团队。一个团队也可以维护多个BOUNDED CONTEXT,但多个团队在一个上下文中工作却是比较难的。
7、用不同模型满足特殊需求
你可能决定通过不同的BOUNDED CONTEXT来满足这些特殊需求,除了转换层的CONTINOUS INTEGTATION以外,让模型采用SEPARATE WAY模式。UBIQUITOUS LANGUAGE的不同专用术语将围绕这些模型以及它们所基于的行话来发展。如果两种专用术语有很多重叠之处,那么SHARED KERNEL模式就可以满足特殊化要求,同时又能把转换成本减至最小。
最重要的是:这个用户群的专门术语有多大的价值?你必须在团队独立操作的价值与转换的风险之间做出权衡,并且留心合理地处理一些没有价值的术语变化。但记住,在需要大量集成的地方,转换成本会大大增加。
8、部署
在复杂系统中,对打包和部署进行协调是一项繁琐的任务,这类任务总是要比看上去难得多。BOUNDED CONTEXT策略的选择将影响部署。由于部署环境和技术存在不同,有很多技术因素需要考虑。但BOUNDED CONTEXT关系可以为我们指出重点问题。转换接口已经被标出。所以,绘制CONTEXT边界时应该反映出部署计划的可行性。
9、权衡
通过总结这些知道原则可知有很多统一或集成模型的策略。一般来说,我们需要在无缝功能集成的益处和额外的协调和沟通工作之间做出权衡。
10、当项目正在进行时
很多情况下,我们不是从头开发一个项目,而是会改进一个正在开发的项目。在这种情况下,第一步是根据当前的状况来定义BOUNDED CONTEXT。这很关键。为了有效地定义上下文,CONTEXT MAP必须反映出团队的实际工作,而不是反映那个通过遵守以上描述的指导原则而得出的理想组织。
描述了当前真实的BOUNDED CONTEXT以及它们的关系以后,下一步就是围绕当前组织结构来加强团队的工作。在CONTEXT中加强CONTINOUS INTEGRATION。把所有分散的转换代码重构到ANTICORRUPTION LAYER中。命名现有的BOUNDED CONTEXT,并确保它们处于项目的UBIQUITOUS LANGUAGE中。
下一节将讨论如何修改CONTEXT边界:转换。
转换
像建模和设计的其他方面,有关BOUNDED CONTEXT的决策并非不可改变的。在很多情况下,我们必须改变最初有关边界以及BOUNDED CONTEXT之间关系的决策,这是不可避免的。一般而言,分割CONTEXT是很容易,但合并它们或改变它们之间的关系却很难。下面将介绍几种有代表性的修改,它们很难,但也很重要。
1、合并CONTEXT:SEPARETE WAY→SHARED KERNEL
合并BOUNDED CONTEXT的动机很多:翻译开销够高、重复现象很明显。合并很难,但什么时候做都不晚,只是需要一些耐心。
即使你的最终目标是完全合并一个采用CONTINUOS INTEGRATION的CONTEXT,也应该先过渡到SHARED KERNEL。
(1)评估现状。在开始统一两个CONTEXT之前,一定要确信它们确实需要统一。
(2)建立合并过程。你需要决定代码的共享方式以及模块应该采用哪种命名约定。SHARED KERNEL的代码至少每周要集成一次,而且它必须有一个测试套件。在开发任何共享代码之前,先把它设置好。(测试套件将是空的,因此很容易通过!)
(3)选择某个小的子领域作为开始,它应该是两个CONTEXT中重复出现的子领域,但不是CORE DOMAIN的一部分。
(4)从两个团队中共选出2~4位开发人员组成一个小组,有他们来为子领域开发一个共享的模型。
(5)来自两个团队的开发成员一起负责实现模型(或修改要共享的现有代码)、确定各种细节并使模型开始工作。如果这些开发人员在模型中遇到了问题,就从第(3)步开始重新组织团队,并进行必要的概念修订工作。
(6)每个团队的开发人员都承担与新的SHARED KERNEL集成的任务。
(7)清除那些不再需要的翻译。
2、合并CONTEXT:SHARED KERNEL→CONTINOUS INTEGRATION
如果你的KERNEL正在扩大,你可能会被完全统一两个BOUNDED CONTEXT的优点所吸引。但这并不只是一个解决模型差异的问题。你将改变团队的结构,而且最终会改变人们所使用的语言。这个过程从人员和团队开始准备。
(1)确保每个团队都已经建立了CONTINOUS INTEGRATION所需的所有过程(共享代码所有权、频繁集成等)。两个团队协商集成步骤,以便所有人都以同一步调工作。
(2)团队成员在团队之间流动。这样可以形成一大批同时理解两个模型的人员,并且可以把两个团队的人员联系起来。
(3)澄清每个模型的精髓。
(4)现在,团队应该有了足够的信心把核心领域合并到SHARED KERNEL中。
(5)随着SHARED KERNEL的增长,把集成频率提高到每天一次,最后实现CONTINOUS INTEGRATION。
(6)当SHARED KERNEL逐渐把先前两个BOUNDED CONTEXT的所有内容都包括进来的时候,你会发现要么形成了一个大的团队,要么形成了两个较小的团队,这两个较小的团队共享一个CONTINOUS INTEGRATION的代码库,而且团队成员可以经常在两个团队之间来回流动。
3、逐步淘汰遗留系统
好花美丽不常开,好景怡人不常在,就算遗留计算机软件也一样会走向终结。但这可不会自动自发地出现。这些老的系统可能与业务及其他系统紧密交织在一起,因此淘汰它们可能需要很多年。好在我们并不需要一次就把所有东西都淘汰掉。
首先要执行的步骤是确定测试策略。应该为新系统中的新功能编写自动的单元测试,但逐步淘汰遗留系统还有一些特殊的测试要求。一些组织会在某段时间内同时运行新旧两个系统。在任何一次迭代中:
(1)确定遗留系统的哪个功能可以在一个迭代中被添加到某个新系统中;
(2)确定需要在ANTICORRUPTION LAYER中添加功能;
(3)实现;
(4)部署;
(5)找出ANTICORRUPTION LAYER中那些不必要的部分,并去掉它们;
(6)考虑删除遗留系统中目前未被使用的模块,虽然这种做法未必实际。
不断重复这几个步骤。遗留系统应该越来越少地参与业务,最终,替换工作会看到希望的曙光并完全停止遗留系统。
4、OPEN HOST SERVICE→PUBLISHED LANGUAGE
我们已经通过一系列特地的协议与其他系统进行了集成,但随着需要访问的系统逐渐增多,维护负担也不断增加,或者交互变得很难理解。我们需要通过PUBLISHED LANGUAGE来规范系统之间的关系。
(1)如果有一种行业标准语言可用,则尽可能评估并使用它。
(2)如果没有标准语言或预先公开发布的语言,则完善作为HOST的系统的CORE DOMAIN。
(3)使用CORE DOMAIN作为交换语言的基础,尽可能使用像XML这样的标准交互范式。
(4)(至少)向所有参与协作的各方发布语言。
(5)如果涉及新的系统架构,那么也要发布它。
(6)为每个协作系统构建转换层。
(7)切换。
现在,当加入更多协作系统时,对整个系统的破坏已经减至最小了。
【学习心得】:学以致用,具体问题具体分析。模式毕竟是巨人的肩膀,要学会站着巨人肩膀看事情,无论项目多大还是多下,又或者团队多大还是多小,总有属于当前自己的模式。结合自身情况,找准定位。我们所做的大部分事情几乎都有方法或模式借鉴,千万不要埋头单干。就像耗子叔所说,学会Evidence Driven:任何讨论和分析都要基于权威的证据、数据或是引用。在我们做设计的时候,或是有争论的时候,说服对方最好的方式就是拿出证据、数据或是权威引用。比如:我的XX设计参考了TCP协议中的XX设计,我的XX观点是基于XX开源软件的实现……如果争论不休就停止争论,然后各自收集和调查自己观点的佐证。
第15章:精炼
如何才能专注于核心问题而不被大量的次要问题淹没呢?LAYERED ARCHITECTURE可以把领域概念从技术逻辑中(技术逻辑确保了计算机系统能够运转)分离出来,但在大型系统中,即使领域被分离出来,它的复杂性也可能仍然难以管理。
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容,而这种形式将使它更有价值,也更有用。模型就是知识的精炼。通过每次重构所得到的更深层的理解,我们得以把关键的领域知识和优先级提取出来。
本章将展示对CORE DOMAIN进行战略精炼的系统性方法,解释如何在团队中有效地统一认识,并提供一种用于讨论工作的语言。
模式:CORE DOMAIN(核心领域)
在设计大型系统时,有非常多的组成部分——它们都很复杂而且对开发的功能也至关重要,到导致真正的业务资产——领域模型最为精华的部分——被掩盖和忽略了。
一个严峻的现实是我们不可能对所有设计部分进行同等的精化,而是必须分出优先级。为了使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建应用程序的功能。但本来就稀缺的高水平开发人员往往会把工作重点放在技术基础设施上,或者只是去解决那些不需要专门领域知识就能理解的领域问题(这些问题都已经有了很好的定义)。
因此:对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些去辅助作用的模型和代码分开。最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。在CORE DOMAIN中努力开发能够确保现实系统蓝图的深层模型和柔性设计。仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的CORE。
1、选择核心
我们需要关注的是那些能够表示业务领域并解决业务问题的模型部分。一个应用程序中的CORE DOMAIN在另一个应用程序中可能只是通用的支持组件。尽管如此,仍然可以在一个项目中(而且通常在一个公司中)定义一个一致的CORE。像其他设计部分一样,人们对CORE DOMAIN的认识也会随着迭代而发展。开始时,一些特殊关系可能显得不重要。而最初被认为是核心对象可能逐渐被证明只是起支持作用。
2、工作的分配
在项目团队中,技术能力最强的人员往往缺乏丰富的领域知识。这限制了他们的作用,并且更倾向于分派他们来开发一些支持组件,从而形成了一个恶性循环——知识的缺乏使他们远离了那些能够学到领域知识的工作。
打破这种恶心循环是很重要的,方法是建立一支由开发人员和一位或多位领域专家组成的联合团队,其中开发人员必须能力很强、能够长期稳定地工作并且学习领域知识非常感兴趣,而领域专家则要掌握深厚的业务知识。如果你认真对待领域设计,那么它就是一项有趣且充满技术挑战的工作。
3、精炼的逐步提升
本章接下来将要介绍各种精炼技术,它们在使用顺序上基本没什么要求,但对设计的改动却大不相同。请往下看:
模式:GENERIC SUBDOMIAN(通用子领域)
模型中有些部分除了增加复杂性以外并没有捕捉或传递任何专门的知识。任何外来因素都会是CORE DOMAIN愈发的难以分辨和理解。模型中充斥着大量众所周知的一般原则,或者专门的细节,这些细节并不是我们的主要关注点,而只是起到支持作用。然而,无论它们是多么通用的元素,它们对实现系统功能和充分表达模型都是极为重要的。
因此:识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独的MODULE中。任何专有的东西都不应该放在这些模块中。把它们分离出来以后,在继续开发的过程中,它们的优先级应低于CORE DOMAIN的优先级,并且不要分派核心开发人员来完成这些任务(因为他们很少能够从这些任务中获得领域知识)。此外,还可以考虑为这些GENERIC SUBDOMAIN使用现成的解决方案或“公开发布的模型”(PUBLISHED MODEL)。
当开发这样的软件包时,有以下几种选择:
1、现成的解决方案
2、公开发布的设计或模型
3、把实现外包出去
4、内部实现
模式:DOMAIN VISION STATEMENT(领域愿景说明)
在项目开始时,模型通常并不存在,但是模型开发的需求是早就确定下来的重点。在后面的开发阶段,我们需要解释清楚系统的价值,但这并不需要深入地分析模型。此外,领域模型的关键方面可能跨越多个BOUNDED CONTEXT,而且从定义上看,无法将这些彼此不同的模型组织起来表明其共同的关注点。
因此:写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是“价值主张”。那些不能将你的领域模型与其他领域模型区分开的方面就不要写了。展示出领域模型是如何实现和均衡各方面利益的。这份描述要尽量精简。尽早把它写出来,随着新的理解随时修改它。
DOMAIN VISION STATEMENT可以用作一个指南,它帮助开发团队在精炼模型和代码的过程中保持统一的方向。团队中的非技术成员,管理层甚至是客户也都可以共享领域愿景说明。
模式:HIGHLIGHTED CORE(突出核心)
DOMAIN VISION STATEMENT从宽泛的角度对CORE DOMAIN进行了说明,但它把什么是具体核心模型元素留给人们自己去解释和猜测。除
非团队的沟通极其充分,否则单靠VISION STATEMENT是很难产生什么效果的。
尽管团队成员可能大体上知道核心领域是由什么构成的,但CORE DOMIAN中到底包含哪些元素,不同的人会有不同的理解,甚至同一个人在不同的时间也有会不同的理解。如果我们总是要不断过滤模型以便识别出关键部分,那么就会分散本应该投入到设计上的精力,而且这还需要广泛的模型知识。因此,CORE DOMAIN必须要很容易被分辨出来。
对代码所做的重大结构性改动是识别CORE DOMAIN的理想方式,但这些改动往往无法在短期内完成。事实上,如果团队的认识还不够全面,这样的重大代码修改是很难进行的。
通过修改模型的组织结构(如划分GENERIC SUBDOMIAN和本章后面要介绍的一些改动),可以用MODULE表达出核心领域。但如果把它作为表达CORE DOMAIN的唯一方法,那么对模型的改动会很大,因此很难马上看到结果。
我们可能需要用一种轻量级的解决方案来补充这些激进的技术手段。可能有一些约束使你无法从物理上分离出CORE,或者你可能是从已有代码开始工作的,而这些代码并没有很好地区分出CORE,但你确实很需要知道什么是CORE并建立共识,以便有效地通过重构进行更好的精炼。我们可以通过以下两种典型的代表性技术来突出核心:
1、精炼文档
编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE元素之间的主要交互过程。但独立文档带来的所有常见风险也会在这里出现(如下所示),控制这些风险的最好方法是保持绝对的精简。
(1)文档可能得不到维护;
(2)文档可能没人阅读;
(3)由于多个信息来源,文档可能达不到简化复杂性的目的。
2、标明CORE
可能你会遇到一份数百页的“领域模型”文档等资料,但无需慌张。把模型的主要存储库中的CORE DOMAIN标记出来,不用特意去阐明其角色。是开发人员很容易就知道什么在核心内,什么在核心外。只需做很少的处理和维护工作,即可让处理模型的人员很清晰地看到CORE DOMAIN了。
因此:把精炼文档作为过程工具
如果精炼文档概括了CORE DOMAIN的核心元素,那么它就可以作为一个指示器——用以指示模型改变的重要程度。当模型或代码的修改影响到精炼文档时,需要与团队其他成员一起协商。当对精炼文档做出修改时,需要立即通知所有团队成员,而且要把心版本的文档分发给他们。CORE外部的修改或精炼文档外部的细节修改则无需协商或通知,可以直接把它们集成到系统中,其他成员在后续工作过程中自然会看到这些修改。这样开发人员就拥有了XP所建议的完全的自治性。
模式:COHESIVE MECHANISM(内聚机制)
计算有时会非常复杂,使设计开始变得膨胀。机械性的“如何做”大量增加,把概念性的“做什么”完全掩盖了。为了解决问题提供算法的大量方法掩盖了那些用于表达问题的方法。
因此:把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么)了,而把解决方案的复杂细节(如何做)转移给了框架。
GENERIC SUBDOMAIN和COHESIVE MECHANISM有什么不同?
GENERIC SUBDOMIAN与COHESIVE MECHANISM的动机是相同的——都是为CORE DOMAIN减负。区别在于二者所承担的职责的性质不同。GENERIC SUBDOMAIN是以描述性的模型作为基础的,它用这个模型表示出团队会如何看待领域的某个方面。在这一点上与CORE DOMIAN没什么区别,只是重要性和专门程度较低而已。COHESIVE MECHANISM并不表示领域,它的目的是解决描述性模型所提出来的一些复杂的计算问题。模型提出问题,COHESIVE MECHANISM解决问题。所以GENERIC SUBDOMAIN是模型级别维度,而COHESIVE MECHANISM是CORE DOMAIN的一部分。
模式提升:通过精炼得到声明式风格
声明式设计是一种精简的设计风格,在本书中也多处提及。精炼的价值在于使你能够看到自己正在做什么,不让无关细节分散你的注意力,并通过不断削减得到核心。如果领域中那些起到支持作用的部分提供了一种简练的语言,可用于表示CORE的概念和规则,同时又能够把计算或实施这些概念和规则的方式封装起来,那么CORE DOMAIN的重要部分就可以采用声明式设计。
COHESIVE MACHANISM用途最大的地方是它通过设计一个INTENTION-REVEALING INTERFACE来提供访问,并且具有概念上一致的ASSERTION和SIDE-EFFECT-FREE FUNCTION。利用这些MECHANISM和柔性设计,CORE DOMAIN可以使用有意义的声明,而不必调用难懂的函数。但最不同寻常的回报来自于使CORE DOMAIN的一部分产生突破,得到一个深层模型。
把GENERIC SUBDOMAIN提取出来可以减少混乱,而COHESIVE MECHANISM可以把复杂操作封装起来。这样可以得到一个更专注的模型,从而减少了那些对用户活动没什么价值、分散注意力的方面。但我们不太可能为领域模型中所有非CORE元素安排一个适当的去处。SEGREGATED CORE(分离的核心)采用直接的方法从结构上把CORE DOMAIN划分出来。
模式:SEGREGATED CORE(分离的核心)
模型中的元素可能有一部分属于CORE DOMAIN,而另一部分起支持作用。核心元素可能与一般元素紧密耦合在一起。CORE的概念内聚性可能不很强,看上去也不明显。这种混乱性和耦合关系抑制了CORE。设计人员如果无法清晰地看到最重要的关系,就会开发出脆弱的设计。
通过把GENERIC SUBDOMAIN提取出来,可以从领域中清除一些干扰性的细节,使CORE变得更清楚。但识别和澄清所有这些子领域是很困难的工作,而且有些工作看起来并不值得去做。同事,最重要的CORE DOMAIN仍然与剩下的那些元素纠缠在一起。
因此:对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取出来到其他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开。
这里基本上采用了与GENERIC SUBDOMAIN一样的原则,只是从另一个方向考虑而已。就目前来看,使用哪种简单解决方案都可以,只需把注意力集中在SEGREGATED CORE(分离的核心)上即可。
模式:ABSTRACT CORE
通常,即便是CORE DOMAIN模型也会包含太多的细节,以至于它很难表达出整体视图。当不同MODULE的子领域之间有大量交互时,要么需要在MODULE之间创建很多引用,这在很大程度上抵消了划分模块的价值;要么就必须间接地实现这些交互,而后者会使模型变得晦涩难度。
因此:把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型,使之能够表达重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中,而专用的、详细的实现类则留在由子领域定义的MODULE中。
【学习心得】:我很幸运,能遇到一个近做了近十年的大型应用项目,我记得从刚开始只有两台刀片机服务发展至目前百来台高配置级别的PC规模。虽然我现在才看到这本书,但这十年的摸索过程其实就是这章节的实现。实在是非常宝贵的经验。
第16章:大型结构
在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林”的境地。
“大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统。
设计一种应用于整个系统的规则(或角色和关系)模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的位置(即使是在不知道各个部分的详细职责的情况下)。本章将探讨一些能成功构建这种设计结构的模式。
模式:EVOLVING ORDER(演化有序)
一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但架构中早期的设计假设又会使项目变得束手无策,而且会极大地限制应用程序中某些部分的开发人员/设计人员的能力。很快,开发人员就会为适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上来。
因此:让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同结构的风格。不要依次过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细之后才能确定。
于CONTEXT MAP不同的是,大型结构是可选的。当发现一种大型结构可以明显使系统变得更加清晰,而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如不使用它,因此最好不要为了追求设计的完整性而勉强去使用一种结构,而应该找到尽可能精简的方式解决所出现问题。要记住宁缺毋滥的原则。
模式:SYSTEM METAPHOR(系统隐喻)
软件设计往往非常抽象且难于掌握。开发人员和用户都需要一些切实可行的方式来理解系统,并共享系统的一个整体视图。
因此:当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。围绕这个隐喻来组织设计,并把它吸收到UBIQUITOUS LANGUAGE中。SYSTEM METAPHOR应该既能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的BOUNDED CONTEXT。但所有隐喻都不是完全精确的,因此应不断检查隐喻是否过度或不恰当,当发现它起到妨碍作用时,要随时准备放弃它。
SYSTEM METAPHOR并不适用于所有项目。从总体上讲,大型结构不是必须要用的。在极限编程的12个实践中,SYSTEM METAPHOR的角色可以由UBIQUITOUS LANGUAGE来承担。当项目中发现一种非常适合的SYSTEM METAPHOR或其他大型结构时,应该用它来补充UBIQUITOUS LANGUAGE。
模式:RESPONSIBILITY LAYER(职责分层)
如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持大型模型的一致,有必要在职责分配上实施一定的结构化控制。
因此:注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象,AGGREGATE和MODULE的职责都清晰地位于一个职责层当中。
想要找到一种适当的RESPONSIBILITY LAYER或大比例结构,需要理解问题领域并反复进行实验。如果遵循EVOLVING ORDER,那么最初的起点并不是十分重要,尽管差劲的选择确实会加大工作量。结构可能最后演变得面目全非。因此,下面将给出一些指导方针,无论是刚开始选择一种结构,还是对已有结构进行转换,这些指导方针都适用。
当对层进行删除、合并、拆分和重新定义等操作时,应寻找并保留一下一些有用的特征:
□ 场景描述。层应该能够表达出领域的基本实现或优先级选择一种大比例结构与其说是一种技术决策,不如说是一种业务建模决策。
□ 概念依赖性。“较高”层概念的意义应该依赖“较低”层,而低层概念的意义应该独立于较高层。
□ CONCEPTUAL CONTOUR。如果不同层的对象必须具有不同的变化频率或原因,那么层应该能够容许它们之间的变化。
□ 潜能层。我们能够做什么?潜能层不关心我们打算做什么,而关心能够做什么。如企业的资源(包括人力资源)以及这些资源的组织方式是潜能层的核心。
□ 作业层。我们正在做什么?我们利用这些潜能做了什么事情?像潜能层一样,这个层也应该反映出现实情况,而不是我们设想的状况。如我们希望在这个层中看到自己的工作和活动:我们正在销售什么,而不是能够销售什么。通常来说,作业层对象可以引用潜能层对象,它甚至可以由潜能层对象组成,但潜能层对象不应该引用作业层对象。
□ 决策支持层。应该采取什么行动或制定什么策略?这个层是用来作出分析和制定决策的。它根据来自较低层(如潜能层或作业层)的信息进行分析。决策支持软件可以利用历史信息来主动寻找适用于当前和未来作业的机会
□ 策略层。规则和目标是什么?规则和目标主要是被动的,但它们约束着其他层的行为。这些交互的设计是一个微妙的问题。有时策略会作为一个参数传递给较低层的方法。有时会使用STRATEGY模式。策略层与决策支持层能够进行很好的协作,决策支持层提供了用于搜索策略层所设定的目标的方式,这些目标又受到策略层设定的规则约束。
□ 承诺层。我们承诺了什么?这个层具有策略层的性质,因为他表述了一些指导未来运营的目标;但它也有作业层的性质,因为承诺是作为后续因为活动的一部分而出现和变化的。
虽然这5个层对很多企业系统都适用,但并不是所有领域的主要概念都涵盖在这5个层中。有些情况下,在设计中生硬地套用这种形式反而会起反作用,而使用一组更自然的RESPONSIBILITY LAYER会更有效。
我们需要对分层结构进行调整和实验,但一定要使分层系统保持简单,如果层数超过4或5,就比较难处理了。层数越多将无法有效地描述领域,而且本来要使用大比例结构解决的复杂性问题又会以一种新的方式出现。我们必须对大比例结构进行严格的精简。
如果一个领域与上述讨论毫无关系,所有的分层可能都必须从头开始。最后,我们必须根据直觉选择一个起点,然后通过EVOLVING ORDER来改进它。
模式:KNOWLEGE LEVEL(知识级别)
如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。
因此:创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
如果得到合理的运用,KNOWLEDGE LEVEL能够解决一些其他方式很难解决的问题。如果系统中某些部分的定制非常关键,而要是不提供定制能力就会破坏掉整个设计,这时就可以利用知识级别来解决这一问题。
像其他大比例结构一样,KNOWLEDGE LEVEL也不是必须要使用的。没有它,对象照样能工作,而且团队可能仍然能够认识到他们需要将Employee与Payroll分离。当项目进行到某个时刻,这种结构看起来已经没什么用了,那么就可以放弃它。
咋看上去,KNOWLEDGE LEVEL像是RESPONSIBILITY LAYER(特别是策略层)的一个特例,但它并不是。首先,KNOWLEDGE LEVEL两个级别之间的依赖是双向的,而RESPONSIBILITY LAYER在层次结构中,较低的层不依赖于较高的层。实际上,RESPONSIBILITY LAYER可以与其他大部分的大比例结构共存,它提供了另一种用来组织模型的维度。
模式:PLUGGABLE COMPONENT FRAMEWORK(可插入式组件框架)
在深入理解和反复精炼基础上得到的成熟模型中,会出现很多机会。通常只有在同一个领域中实现了多个应用程序之后,才有机会使用PLUGGABLE COMPONENT FRAMEWORK(可插入式组件框架)。
当很多应用程序需要进行相互操作时,如果应用程序都基于相同的一些抽象,但它们是独立设计的,那么在多个BOUNDED CONTEXT之间的转换会限制它们的集成。各个团队之间如果不能紧密地协作,就无法形成一个SHARED KERNEL。重复和分裂将会增加开发和安装的成本,而且互操作会变得很难实现。
因此:从接口和交互中提炼一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接口进行操作,那么就可以允许它使用这些组件。
PLUGGABLE COMPONENT FRAMEWORK也有几个缺点:
□ 一个缺点是它是一种非常难以使用的模式。它需要高度精确的接口设计和一个非常深入的模型,以便把一些必要的行为捕获到ABSTRACT CORE中。
□ 另一个很大的缺点是它只为应用程序提供了有限的选择。如果一个应用程序需要对CORE DOMAIN使用一种非常不同的方法,那么可插入式组件框架将起到妨碍作用。
总结:通过重构得到更适当的结构
1、最小化
2、沟通和自律
3、通过重构得到柔性设计
4、通过精炼可以减轻负担
【学习心得】:如果是在持续用心做事的话,每一次重构都是为了比原来的更好,以上的几种模式多少都会触碰得到,如果系统足够“大且运行良好的话。当然,这本书会给了我更加宽广的视野。
第17章:领域驱动设计的综合运用
1、把大型结构与BOUNDED CONTEXT结合起来使用
2、将大型结构与精炼结合起来使用
大型结构和精炼的概念也是互为补充的。大型结构可以帮助解释CORE DOMAIN内部的关系以及GENERIC SUBDOMIAN之间的关系。同时大型结构本身可能也是CORE DOMAIN的一个重要部分。
3、首先评估
当对一个项目进行战略设计时,首先需要清洗地评估现状。
(1)画出CONTEXT MAP。你能画出一个一致的图吗?有没有一些模棱两可的情况?
(2)注意项目上的语言使用。有没有UBIQUITOUS LANGUAGE?这种语言是否足够丰富,以便帮助开发?
(3)理解重点所在。CORE DOMAIN被识别出来了吗?有没有DOMAIN VISION STATEMENT?你能写一个吗?
(4)项目所采用的技术是遵循MODEL-DRIVEN DESIGN,还是与之相悖?
(5)开发团队是否具备必要的技能?
(6)开发人员是否了解领域知识?他们对领域是否感兴趣?
当然,我们不会发现完美的答案。我们现在对项目的了解永远不如将来的了解深入。但这些问题为我们提供了一个可靠的起点。
4、有谁定制策略
传统上,架构是在应用程序开发开始之前建立的,并且在这种组织中,负责建立架构的团队比应用开发团队拥有更大的权利。但我们并不一定的遵循这种传统的方式,因为它并不总是十分有效。
战略设计必须明确地应用于整个项目。项目有很多组织方式,这一点我并不想做过多的说明。但是,要想使决策制定过程更有效,需要注意一些基本问题。
1、从应用程序开发自动得出的结构
一个非常善于沟通、懂得自律的团队在没有核心领导的情况下照样能够很好地工作,他们能够遵循EVOLVING ORDER来达成一组共同遵守的原则,这样就能够有机地形成一种秩序,而不用靠命令来约束。
2、以客户为中心的架构团队
当几个团队共用同一种策略时,确实需要集中制定一些决策。架构师如果脱离实际开发工作,就可能会设计出失败的模型,但这是完全可以避免的。架构团队可以把自己放在与应用开发团队平等的位置上,帮助他们协调大型架构、BOUNDED CONTEXT边界和其他一些跨团队的技术问题。为了在这个过程中发挥作用,架构团队必须把思考重点放在应用程序的开发上。
5、制定战略设计决策的6个要点
1、决策必须传达整个团队;
2、决策过程必须收集反馈意见;
3、计划必须允许演变;
4、架构团队不必把所有最好、最聪明的人员都吸收进来;
5、战略设计需要遵守简约和谦逊的原则;
6、对象的职责要专一,而开发人员应该是多面手。
技术框架同样如此
技术框架提供了基础设施层,从而使应用程序不必自己去实现基础服务,而且技术框架还能帮助把领域与其他关注点隔离开,因此它能够极大地加速应用程序(包括领域层)的开发。但技术框架也有风险的,那就是它会影响领域模型实现的表达能力,并妨碍领域模型的自由改变。
不要编写“傻瓜式”的框架。
在划分团队时,如果认为一些开发人员不够聪明,无法胜任设计工作,而让他们去做开发工作,那么这种态度可能会导致失败,因为他们低估了应用程序开发的难度。
注意总体规划
由Christopher Alexander领导的一群建筑师(设计大楼的建筑师)在建筑和城市规划领域中提倡“聚少成多地成长”(piecemeal growth)。他们非常好地解释了总体规划失败的原因。
如果没有某种规划过程,那么俄勒冈州大学的校园永远不会像剑桥大学校园那样庞大、和谐而井井有条。
总体规划是解决这种难题的传统方法。它试图建立足够多的指导方针,来保持整体环境的一致性,同时仍然为每幢建筑保留自由度,并为适应局部需要预留下广阔的空间。
······将来这所大学的所有部分将构成一致的整体,因为它们只是被“插入”到总体设计的各个位置中。
······实际上总体规划会失败,因为它只是建立了一种极权主义的秩序,而不是一种有机的秩序。它们过于生硬,因此不容易根据自然变化和不可预料的社会生活变化来做出调整。当这些变化发生时······总体规划就过时了,而且不再被人们遵守。即使人们遵守总体规划······它们也没有足够详细地指定建筑物之前的联系,人口规模、功能均衡等这些用来帮助每幢建筑的局部行为和设计很好地符合整体环境的方面。
······试图驾驭这种总体规划过程非常类似于在小孩的填色本上填充颜色······这个过程最多也不过是得到一种极为平常的秩序。
······因此,通过总体规划是无法得到一种有机的秩序的,因为这个规划既过于精确,又不够细致。它在整体上过于精确了,而细节上又不够细致。
······总体规划的存在疏远了用户[因为,从根本上讲]大部分重要决策已经确定下来,因此社区成员对社区未来的建设几乎没有什么影响了。
——摘自Oregon Experiment,PP. 16-28 [Alexander et al. 1975]
Alexander和他的同事倡议由社区成员共同制定一组原则,并在“聚少成多地成长”的每次行动中多应用这些原则,这样就会形成一种“有机秩序”,并且能够根据环境变化做出调整。
【学习心得】:由于我阅读的集中力不足,所以无法很好地从一次阅读中获取系统性的认知。因此,我必须用抄写去深入我心。特别是一些重要且很重要的知识,我必须这么做。虽然费时费力,但用未来的眼光去看,当下是值得的,再用当下的眼光看未来,原来我现在做的都是对的。笨一点没关系,时间就这么用的。