模型的复杂性必须通过重构和知识的消化才能把关键的领域、最有价值的部分(core domain)、优先级提取出来。让团队而把主要精力放在core domain上而不要为无关的细节分散注意力,这有益于:
- 帮助团队成员掌握系统的总体设计以便更好的协调工作;
- 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通;
- 专注于模型中最有价值的那部分;
- 指导外包、现成组件的使用以及任务委派;
- 指导重构;
精炼的目的是为核心域减负把模型设计的更明显,精炼的前提是对模型理解的深入,深层次的理解通过必须通过连续的重构得到(不能纸上谈兵),然后才有可能向深层模型和柔性设计推进。
在选择重构目标时,不能哪痛治哪,要观察一下根源问题是否涉及core domain或其与core domain的关系,如果确实涉及,那么重构它。重构的过程就是把core domain更好的提取出来,完善对core的隔离,并把支持性的子领域提炼成通用子领域。下图是精炼的通用策略:
一、核心域提炼
1.1、核心域的定义
什么是核心域?技术复杂有门槛==核心域,答案显然不是。但是事实是无论前期认知如何统一,实施时都可能陷入技术复杂==核心域这个道路上来,在落地的过程中高级的开发人员往往会分配到基础技术设施(复杂而且绝对有必要把它们做好)和通用问题的解决上,领域的核心往往是由初级开发人员来完成包括DB表的设计,这种恶性循环的结果会导致真正的业务资产被忽略,反正有可能沉淀下很多与业务无关的技术组件。
上述现象的产生不是个例,解决上述问题的对策不是把系统的所有部分精细化有针对性的安排工作,而是要分出优先级,梳理出模型的真正核心并根据核心来向外扩展功能,制定一个明确的整体设计视图,让团队所有成员都要理解项目中最关键的部分。这些关键的部分就称为core domain。
提炼core domain不容易,但做出决策并不难。对core domain的选择取决于看问题的角度。core domain的提炼是一种积累的过程,落地时这部分必须要自研,必要时可以请一些专家来培训。
领域分类:
- 核心域:唯一的、定义明确的领导模型是业务中最重要的部分,是核心竞争力的体现;在系统建议时对核心域要有资源倾向性;
- 支撑子域:核心域的成功离不开它,但它又不是业务的核心部分,在系统建设时一般提供定制开发,架构设计时需要考虑其可替换性;
- 通用子域:指通用功能,一般来讲市场上已经有很多成熟的产品了,在系统建设时往往倾向于采购;
1.1.1、领域前景说明domain vision statement,阐明价值主张
前景说明是一份文档,主要描述的是如何为企业带来价值。为团队提供了统一的方向,通俗来讲,就是一份阶段性的认知合同,设计者要先想明白,然后写一份core domain的简短描述以及它将会创建的价值,也就是”价值主张”。展示出领域模型是如何 实现和均衡各方利益的。这价描述要尽量精简。尽早把它写出来,等到获得新的理解后再修改它。
1.1.2、突出核心highlighted core,改善沟通指导决策过程
尽管团队成员可能大体上知道核心领域是由什么构成的,但core domain到底包含哪些元素,不同的人会有不同的理解,甚至同一个人在不同的时间也会有不同的理解。如果我们总是要不断过滤模型以便识别出关键部分,那么就会分散本应该投入到设计上的精力,而且这还需要广泛的模型知识。
因此有必要编写一份补充文档,用于描述core domain及core元素之间的主要交互过程。它可以作为一个指标器用来指示模型改变的重要程序。当模型或代码的修改影响到精炼文档时,需要与团队其他成员一起协商。当对精炼文档做出修改时,需要立即通知所有团队成员,而且要把新版本的文档分发给他们。
1.2、精炼策略
1.2.1、通用子领域generic subdomain,从定制到组件化的过渡
实际上,在软件开发过程中大部分模型都需要自研很少集成商业产品,但是模型中包含的大量一般原则和专门的细节并不是主要关注点,而只是起到支持作用。这些部分有可能很重要,但它们不是核心部分,很大可能性还会混淆core,因此是有必要把内聚的子领域识别出来,分离到单独一个单独子域中。
分离后,除低优先级在core domain之下,同时把分离出的子领域发布出去,使其成为一个专门的通用子领域,抽离核心开发人员到core domain开发上,只在有必要的时候才在支持性的子领域中投入工作。
注意通用不等于可以重用,通用子领域的设计必须严格地限定在通用概念的范围之内。专用的概念要么属于core,要么属于更专业的子领域。不应该属于通用子领域。
1.2.2、内聚机制cohesive mechanism,解决复杂计算掩盖了模型表达的问题
计算服务有时会很复杂,当对业务模型做大量计算时就有可能把模型概念掩盖。此时我们要做的并不是改变算法,而需要从模型上下手分析,把概念上的cohesive mechanism分离到一个单独的轻量级框架中,用intentetion-revealing interface来分开这个框架的功能,从而把解决方案的复杂性转移给了框架,这些被分离出来的机制承担起了支持的任务,从而留下了一个更小的,表达更清楚的core domain。
这种框架应该专注于计算,避免模型的概念。通常我们要分离core和mechanism,二者的职责应该分离,避免产生耦合,限制将来的改进,也会使模型复杂化。子领域和内聚机制都是为core domain减负,但前者是以一个描述问题的模型作为基础,后者与模型没有关系专注于计算(模型提出问题,cohesive mechanism解决问题)。有一种例外情况是计算本身就是core domain,此时算法要做严格的封装。
1.2.3、隔离核心segreated core,解决core内的耦合
模型中的元素可能有一部分属于core domain,而另一部分起支持作用。核心元素可能与一般元素紧密耦合在一起。core的概念内聚性可能不是很强,看上去也不明显,这种混乱性和耦合关系抑制了core的分离。设计人员如果无法清晰地看到最重要的关系,就会开出一个脆弱的设计。
虽然通过把generic subdomain分离出来可以从领域中清除一些干扰性的细节,使core变的更清楚,但识别和澄清所有这些子领域是很困难的,而且有些工作看起来并不值得去做,最重要的core domain仍然与剩下的那些元素纠缠在一起。
此时可以从只从代码结构上对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强core的内聚性,同时减少它与其它代码的耦合。把所有通用元素或支持性元素提取到其他对象中并把这些对象放到其他的包中,即使这会把一些紧密耦合的元素分开;
1.2.4、抽象核心abstract core,解决子域的复杂引用
当不同的子领域之间有大量交互时,一般会有两种解决方案:1、在模型间创建很多引用;2、创建一个中间层间接地实现这些交互。但这样就会使模型变得难懂。此时可以设计一个抽象模型,使之能够表达出重要组件之间的大部分交互,把模型中最基本的概念识别出来,并分离到不同的类、抽象类和接口中。把这个完整的抽象模型放到它自己的mudule中,而专用的、详细的实现类则留在由子领域定义的module中。从而解决复杂交互的问题,在抽象时要注意:
- 注意抽象出的通用语言可理解,并且一定要符合业务专家的心智模型;
- 建立抽象体系模型使差异化定义在层级上,防止以实现体现差异化的情况发生;
- 防止实现类时解决非核心问题时会产生大量的代码的问题;
二、大比例设计
在大型系统中,如果由于缺少一种全局性的原则而使团队成员无法根据元素在模型中的角色来解释这些元素,那么开发人员就会产生局限性,过多的预先规定的假设又会使项目变得束缚,而且会极大地限制应用程序中某些特定部分的开发人员/设计人员的能力。很快,开发人员就会为适应结构而不得不在应用程序的开发上妥协,要么完全推翻要么回到老路上来。
大比例结构是一种语言,可以用它从大局上讨论和理解系统,起到即能指导设计、又能指导理解的作用,大比例语言也是对精炼的一种补充。在实施时应该允许这种概念上的大比例结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。有些设计决策和模型决策必须在掌握了详细知识之后才能确定。不要为了追求设计的完整性而勉强去使用一种结构,而应该找到能够最精简地解决所出现问题的方案后再进行决策。
2.1、设计模式
2.1.1、系统隐喻system metaphor,用类比来描述系统
软件设计往往非常抽象且难于掌握,开发人员和用户都需要一些切实可行的方式来理解系统并共享系统的一个整体视图。当概念理解起来非常困难时,一个具体的类比正好符合团队成员对系统的想像,并能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大比例结构。围绕这个隐喻来组织设计,并把它吸收到ubiquitous language中。隐喻即能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的限界上下文。
隐喻并不是一定存在的,它其实是对ubiquitous language的一种补充。
2.1.2、职责分层responsibility layer,用分层来描述系统
如果每个对象的职责都是手工分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持模型的一致性,有必要在职责分配上实施一定的结构化控制。最简单的就是分层,在实现时,层的数量要适中经验值是最好不要超过4层。
层的特征
- 场景描述:层应该能表达出领域的基本实现和优先级;
- 概念依赖性:较高层应该依赖较低层,而低层应该独立于较高层;
- 伸缩性:不同的层必须具有不同的变化频率和原因;
层的分类
- 能力层:我们能做什么;
- 作业层:我们正在做什么有时也会和潜能层合并成一个层;
- 决策层:应该采取什么行动或制定什么策略,类似于智能路由;
- 策略层:为决策支持层提供了规则和目标,类似策略模式;
- 承诺层:我们承诺了什么,一般表示协议;
2.1.3、知识级别knowledge level,开放能力,组件配置化
当需要让用户对模型的一部分有所控制,而模型又必须满足更大的一组规则时,可以利用知识级别来处理这种情况,它可以使系统具有可配置的行为。这种配置可以在启动或运行时进行修改。系统的规则不能过于灵活。这种分离出去的部分同样也需要一个模型的支撑。
如果在一个应用程序中,entity的角色和它们之间的关系在不同的情况下有很大的变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具务一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。
因此,创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个级别,一个是非常具体的,另一个则提供了一些可供用户定制的规则和知识。知识级别是在同一层次上分离的,并不是分层的上下级关系。
2.1.4、可插入组件pluggable component framework
当很多应用需要进行互操作时,如果所有应用程序都基于相同的一些抽象,但它们是独立设计的,那么在多个context之间的转换会限制它们的集成。各个团队之间如果不能紧密协作,就会导致重复和分裂进而增加开发和安装的成本,交互也会难于实现。
因此,通用的作法是封装成单独的组件,插入到一个中央的hub上,这个hub支持组件所需的所有协议,并且知道如何与它们所提供的接口进行对话。从接口和交互中提炼出一个Abstract core,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。无论是什么应用程序,只要它严格地通过abs core的接口进行操作,就允许它使用这些组件。这种模式最大的问题是难以使用、编码限制、扩展性差。一般只有要成功集成多个专门的应用后再采用这种结构。
End,后续笔者将持续更新与战术设计相关的知识细节。