第四部分 战略设计
随着功能越来越多,系统会越来越复杂。当系统大到一定程度,就需要对系统进行拆分。系统大到一定程度,还需要多个团队共同维护。这部分讲的是更宏观层面的设计,所以叫战略设计。第14章讲的是“分”,即如何对结构进行划分,各个结构之间用BOUNDED CONTEXT分隔开,划分之后,如何处理各个部分之间的关系;第15章讲的是精炼,是指要突出主要矛盾,对划分出的各个部分,重点关注核心领域的那部分;第16章讲的是“合”,即如何保证大型系统各个部分能统一朝着正确的方向发展。
第14章 保持模型的完整性
这章在讲如何创建出一个个独立的模型(即题目中提到的“完整性”),各个模型之间不能相互“污染”,如果想要交互,要遵循一定的规则。
14.1 BOUNDED CONTEXT
各个模型,要有自己的BOUNDED CONTEXT(界限上下文)。BOUNDED CONTEXT就像细胞膜一样,将模型与其他模型隔离开。在BOUNDED CONTEXT之内,各个子系统的命名应该有自己的规划,并且不能有重复的元素。如果不注意划分界限上下文,将不同模块的元素组合到一起可能会引发两类问题:重复的概念和假同源。
重复的概念是指两个模型元素实际上表示同一个概念。每当这个概念相关的内容发生变化时,都必须更新两个地方。比如同一个功能在系统的两个地方都用了,这两个地方都各自的代码实现。当这个功能的规则发生变化时,要同时修改这两个地方。
假同源是指相同的术语,两个人认为他们在讨论同一件事情,其实不是。比如A和B都在说概念“说英语”,而A理解的“说英语”是说的“英式英语”;而B理解的“说英语”是说的“美式英语”。
14.2 CONTINUOUS INTEGRATION
CONTINUOUS INTEGRATION(持续集成) 是指把一个上下文中的所有工作频繁地合到一起,并使他们保持一致,以便当模型分裂时,可以迅速发现并纠正问题。
我们在开发过程中,经常发现我们已经有一些概念或者功能已经有了,但是开发人员不知道,造成了重复“造轮子”;还有一种情况是,知道有现有模块有这个功能,但是不知道怎么使用,不敢用,也不敢在原有的功能基础上更新增加自己所需要的实现。
那我们应该怎么才能做到“持续集成”呢?
- 保持高水平的沟通;
- 多加锤炼UBIQUITOUS LANGUAGE
这样能尽早暴露出模型的分裂问题,并加以纠正。
14.3 CONTEXT MAP
通过定义不同上下文之间的关系,并在项目中创建一个所有模型上下文的全局视图,可以减少混乱,这个视图就叫CONTEXT MAP (上下文地图)。
CONTEXT MAP位于项目管理和软件设计的重叠部分。按照常规,人们往往按照团队组织的轮廓来划定边界。紧密协作的人会很自然地共享一个模型上下文。不同团队的人员(或者在同一个团队中但从不交流的人)将使用不同的上下文。
不管CONTEXT MAP采用什么形式,它必须在所有项目人员之间共享,并被他们理解。它必须为每个BOUNDED CONTEXT提供一个明确的名称,并且命名要符合一定的规范。
14.4 BOUDED CONTEX之间的关系
下边几节,我就不按照书中的章节进行解读了,因为我觉得书中的章节分的有点乱,在这里我重新规整下。
14.4.1-14.4.5在讲1对1的关系处理;14.4.6在讲1对N的关系处理;14.4.7-14.4.8在讲N对N的关系处理;
14.4.1 SEPARATE WAY
如果两组功能之间,没什么关系,就采用SEPARATE WAY(各行其道)的方式。
14.4.2 SHARED KERNEL
如果两个团队之间开发的内容有重合的部分,就可以采用SHARED KERNEL(共享内核)模式。从领域模型中选出两个团队都愿意共享的一个子集。当然,除了这个模型自己以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位。一个团队在没有与另一个团队商量的情况下,不能擅自更改它。
注:第15章会讲到CORE DOMAIN(核心领域) 和 GENERIC SUBDOMAIN(通用子领域)的概念。SHARED KERNEL通常是CORE DOMAIN,或是一组GENERIC SUBDOMAIN,也可能两者兼有。
14.4.3 CUSTOMER/SUPPLIER DEVELOPMENT TEAM
CUSTOMER/SUPPLIER DEVELOPMENT TEAM(客户/供应商 开发团队)是指在在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并未这些任务做预算,以便每个人都知道双方的约定和进度。在这里客户和供应商的地位相对平等。供应商的地位相对高一些,因为虽然有协商机制,但是最终还是供应商决定着是否修改对下游客户提供的服务是否修改。
14.4.4 CONFORMIST
当上游团队不愿意按照下游团队的意愿去修改,下游只能追随上游的模型去构建自己的模型。这种下游强依赖于上游的情况,叫做CONFORMIST(跟随者)。该模式与CUSTOMER/SUPPLIER DEVELOPMENT TEAM的区别是,上游的话语权更大。下游对上游的依赖性更强。
14.4.5 ANTICORRUPTION LAYER
当两个团队互相之间都不妥协,都不想修改自身的模型来适应对方的模型,又想进行数据交互,就成了僵局。打破这种僵局的方式是在两个团队负责的模型之间,建立ANTICORRUPTION LAYER(防腐层)。双方通过防腐层进行交互,而各自的BOUNDED CONTEXT内保持相对稳定。当然防腐层可以规划在其中一方的模块内,具体要根据实际情况进行设计。防腐层通过另一个系统现有接口与其进行对话,而只需对对方系统做很小的修改,甚至无需修改。防腐层在两个模型之间进行双向转换。
ANTICORRUPTION LAYER是连接两个BOUNDED CONTEXT的一种方式。我们常常需要使用别人创建的系统,或者和一些遗留系统交互,然而我们并未完全理解这些系统,并且也无法控制他们。
如何构建防腐层呢?
- 通过设计模式中的外观模式,A与B交互,在B模块前加一层外观层,外观层对外按照A的标准构建交互逻辑;
- 通过设计模式中的适配器模式,A与B交互,在A调用B的服务前,通过防腐层的适配器改成B标准协议下的数据,与B进行交互;
- 将外观模式和适配器模式结合起来使用。
14.4.6 OPEN HOST SERVICE
某个应用,本来与其他应用SEPARATE WAY(各行其道),但是突然需要这个应用与多个系统集成,为多个系统提供服务。此时可以采用 OPEN HOST SERVICE(开放主机服务)模式。应用A对外发布一个交互协议。对接方需要依据此协议与A进行交互。这个协议的标准由A来指定。尽量让这个协议更通用以支持更多的对接方。如果某些对接方有特殊的对接需求,就要做特例的开发。这个是1对N的方式。
14.4.7 PUBLISHED LANGUAGE
本来SEPARATE WAY(各行其道)多个应用,需要相互通信,可以定义一种PUBLISHED LANGUAGE(公共语言)。各个应用先把各自模块内的数据转换为PUBLISHED LANGUAGE再与其他应用进行交互。就像各个国家都有自己的语言,为了方便各个国家之间沟通,发明了一个公共语言:世界语。各个国家先把各自国家的语言翻译成世界语,再与其他国家沟通。其实webservice的soup包对应xml也是遵循了soup标准的公共语言。作者在书中举了一个CML(化学标记语言)的例子,其实就是依据化学领域的公共交流语言开发出来的专用XML。
14.4.8 从整体视角“统一”各个系统
本来SEPARATE WAY(各行其道)多个应用如何统一成一个整体?
作者举了一个盲人摸象的例子:四个盲人都只能摸到大象的一部分,就认为大象就是自己摸到的那样。摸到尾巴的人,认为和蛇一样;摸到鼻子的人认为和蛇一样;摸到腿的认为和大树一样;摸到身子的以为和墙一样。
“统一”各个系统的前提是:承认每个部分都不是完整的。
第一步,弄清楚各个部分是如何连接的。可以把大象看成是一堵墙,由树干支撑的,一头而是绳子,一头是蛇。在整体性上,已经更进一步了。
第二步,去掉各个模型中那些偶然或者不正确的方面。对每个模型的定义尽量精简,做宁“少”勿“多”的描述。比如把象鼻子比喻成蛇就不恰当,因为蛇可能有毒牙,而象鼻子没有。
后续,通过进一步理解及沟通,模型就可以得到精化。
14.5 划分BOUDED CONTEXT的一些指导原则
14.5.1 团队决策或更高层决策
BOUDED CONTEXT的范围和之间的关系必须由团队商定得出,或者至少传达到整个团队,被团队的每个人都理解。团队的行政关系对BOUDED CONTEXT划分也会由一些影响。从管理层视角看待的划分,可能不适用。要结合具体组织架构现状和系统现状统筹考虑。
14.5.2 参考技术团队工作范围划分边界
开发软件项目时,各个开发团队的主力讲起支撑作用的内容划分到自己的CONTEXT,然后分析下这些CONTEXT与外部系统之间的关系。这样可以让你对现有的情况有个粗略的了解。
14.5.3 CONTEXT的大小的选择
下述情形适合将CONTEXT划分的大一些:
- 当用一个统一的模型来处理更多任务时,用户任务之间的流动更顺畅了;
- 一个内聚模型比两个不同模型再加它们之间的映射更容易理解;
- 两个模型之间的转换可能会很难(有时甚至时不可能的);
- 共享语言可以使团队沟通起来更清楚。
下述情形适合将CONTEXT划分的小一些:
- 开发人员之间沟通比较顺畅;
- 由于团队和代码规模较小,CONTINUOUS INTEGRATION更容易;
- 较大的上下文要求更加通用的抽象模型,但缺乏这方面的人才;
- 不同的模型可以满足一些特殊需求,或是能够把一些特殊用户群的专门术语和UBIQUITIOUS LANGUAGE的专门术语包括进来。
14.5.4 将“外部系统/遗留系统”隔离到CONTEXT之外
我们需要识别“一些无法立即淘汰的大型遗留系统”和“那些提供所需服务的外部系统”,并把他们与你的设计隔离开。
14.5.5 处理好与外部系统的关系
- 完全不需要集成外部系统时,采用SEPARATE WAY模式;
- 如果感觉有必要集成的话,采用CONFORMIST模式或者ANTICORRUPTION LAYER模式。
14.5.6 设计中的系统先使用一个BOUNDED CONTEXT
设计中的整个系统使用一个BOUNDED CONTEXT。尤其是团队成员较少时(一般小于10人),这可能就是一个很好的选择。
随着团队规模扩大,CONTINUOUS INTEGRATION可能会变得困难起来。此时可以试着采用SHARED KERNEL模式,使得在每个BOUNDED CONTEXT中工作的人员少于10人。如果两个上线文之间的所有依赖都是单向的,就可以建成CUSTOMER/SUPPLIER DEVELOPMENT TEAM。
一般来说,每个BOUNDED CONTEXT对应一个团队。一个团队也可以维护多个BOUNDED CONTEXT,但多个团队在一个上下文中的工作确实比较难的。
14.5.7 用不同的模型满足特殊需求
通过不同的BOUNDED CONTEXT来满足各种特殊需求,采用SEPARATE WAY模式。如果特殊需求中有些术语有重叠,可以采用SHARED KERNEL模式。
14.5.8 考虑部署的因素
部署的情况会影响对CONTEXT划分。比如采用CUSTOMER/SUPPLIER DEVELOPMENT TEAM模式是,客户和供应商的应用需要同时更新代码。而SEPARATE WAY模式就不用了考虑这么多的问题。
14.5.9 权衡好沟通与控制的关系
CONTEXT划分的太细,沟通就会比较繁琐;划分的太粗就需要统筹和控制更多系统。要根据沟通成本和系统情况整体考虑。
14.5.10 系统正在开发中时如何划分CONTEXT
- 描述当前真实的BOUNDED CONTEXT以及它们的关系;
- 进行优化:在CONTEXT加强CONTINUOUS INTEGRATION;把分散的转换代码重构到ANTICORRUPTION LAYER中;命名现有的BOUNDED CONTEXT,并确保它们处于项目的UBIQUITIOUS LANGUAGE中。优化时要注意“从小处着手”,通过花费最少的工作和对模型产生最小破坏的前提下创造最大的价值。
14.6 CONTEXT之间关系的调整
分割CONTEXT是相对容易的,但合并它们或者改变它们的关系却很难。下面介绍几种调整CONTEXT关系的思路。
14.6.1 合并CONTEXT:SEPARATE WAY→SHARED KERNEL→CONTINUOUS INTEGRATION
合并CONTEXT的动机:翻译沟通开销过高、重复现象严重。
合并的建议:
- 最好选择一些简单且相对通用或不重要的部分进行合并,先把合并的机制和框架建立起来;
- 可以选择一个模型进行重构,让另一个模型兼容该模型的某个模块;或者单独做一个新模块,承担二者的部分职责。
- 合并之前,两个团队应该有一大批同时理解两个模块的人员,以减低合并风险;
- 最好快速完成,因为这是一个开销高且易出错的阶段。
14.6.2 逐步淘汰遗留系统
新模块通过ANTICORRUPTION LAYER模式与旧系统交互,逐步淘汰旧系统需要一个过程;让遗留系统越来越少的参与业务;遗留系统中未被使用的功能,建议直接删除;首先迁移哪些对ANTICORRUPTION LAYER依赖较少的功能,这样能减少对ANTICORRUPTION LAYER的修改。
14.6.3 OPEN HOST SERVICE →PUBLISHED LANGUAGE
某个模块通过OPEN HOST SERVICE与其他系统集成,如果系统越来越多,维护负担不断增大,就需要考虑制定统一的标准PUBLISHED LANGUAGE来规范系统之间的交互。如果有一种行业标准语言可用,则可以评估并使用它。如果没有的话,就在OPEN HOST SERVICE基础上,设计PUBLISHED LANGUAGE。俗话说“一流的公司定标准”。当模块的话语权足够大时,可以考虑这种转换方式。