前文回顾
上一篇介绍了该书的第四部分“战略设计”,我们学习了限界上下文,以及各种上下文策略,另外还分析了如何在不同策略之间进行转换。
这一篇,我们继续学习该书的第四部分。
什么是精炼
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容,而这种形式将使它更有价值,也更有用。就比如经典电磁学的全部内涵可以精炼为以下四个方程:
而模型就是知识的精练。
精炼的主要动机是把最有价值的那部分提取出来,正是这个部分使我们的软件区别于其他软件,并让其物有所值,这个部分就是CORE DOMAIN。领域模型的战略精炼包括以下部分:
- 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;
- 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通;
- 指导重构;
- 专注于模型中最有价值的那部分;
- 指导外包、现成组件的使用以及任务委派。
模式:CORE DOMAIN
在大型系统的设计中,往往有非常多的组成部分,它们都很复杂而且对开发的成功也至关重要,但这导致真正的业务资产(领域模型最为精华的部分)被掩盖和忽略了。
一个严峻的现实是:我们不可能对所有的部分进行同等的精化,而是必须分出优先级。为了使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建应用程序的功能。
但现实中经常出现一种错误现象,那就是:本来就稀缺的高水平开发人员往往会把工作重点放在技术基础设施上,或者只是去解决那些不需要专门领域知识就能理解的领域问题。高水平的开发人员对这些专业问题更有兴趣,这些工作可以让他们获得一些在其他地方也能用上的专业技能。而真正体现软件价值,并且可成为业务资产的领域核心却是由一些水平稍差的人员完成的。这个严重问题的根源在于项目没有一个明确的整体设计视图,而且也没有认清各个部分的相对重要性。
因此:
- 对模型进行提炼,找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。
- 最有价值和最专业的概念要轮廓分明。
- 尽量压缩CORE DOMAIN。
- 让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。
- 在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。
- 仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的CORE。
技术能力最强的成员往往缺乏领域知识,为了充分发挥他们的技术能力,容易将支持性组件的开发工作交给他们,而这样做又会让他们远离核心领域,从而形成恶性循环。打破这种恶性循环是很重要的,方法是建立一支由开发人员和一位或多位领域专家组成的联合团队,其中开发人员必须能力很强、能够长期稳定地工作并且对学习领域知识非常感兴趣,而领域专家则要掌握深厚的业务知识,特别是充当培训和指导业务的专家可能非常有价值。
模式:GENERIC SUBDOMAIN
模型中有些部分除了增加复杂性以外并没有捕捉或传递任何专门的知识。任何外来因素都会使CORE DOMAIN 愈发的难以分辨和理解。模型中充斥着大量众所周知的一般原则,或者是专门的细节,这些细节并不是我们的主要关注点,而只是起到支持作用。
识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独的MODULE中。任何专有的东西都不应放在这些模块中。
把它们分离出来以后,在继续开发的过程中,它们的优先级应低于CORE DOMAIN的优先级,并且不要分派核心开发人员来完成这些任务(因为他们很少能够从这些任务中获得领域知识)。此外,还可以考虑为这些GENERIC SUBDOMAIN使用现成的解决方案或“公开发布的模型”(PUBLISHED MODEL)。
作者 Eric 用亲历的两个项目举例,两个项目中最好的开发人员花了很长时间解决各时区的时间存储和转换问题。
一个是运输项目,为货物运输安排日程,而对于日程安排来说,准确的时间计算是非常必要的,而日程安排是按当地时间计算,所以需要进行时间转换。项目组配了一个能力优秀的零时程序员解决该问题。虽然问题实际上要困难的多,但他还是完成了代码并且和 CORE 集成。
另一个是保险项目,为保险公司开发新的理赔处理系统。需要把各种事件(发生车祸,下冰雹)的时间记录下来,也是记录的当地时间。项目组开始安排了一个初级程序员,后来又安排了一个高级开发人员来帮助他。项目最终因为种种原因搁浅,时区代码没有派上用场。
技术人员喜欢处理那些可定义的问题(如时区转换),而且很容易就能证明他们花时间做这些工作是值得的。但严格地从优先级角度来看,他们应该先去完成CORE DOMAIN的工作。
GENERIC 表示通用,但通用不等于可重用。重用确实会发生,但不一定总是代码重用。模型重用通常是更高级的重用。
从风险管理的角度看系统雏形
敏捷过程通常要求通过尽早解决最具风险的任务来管理风险。特别是XP过程,它要求迅速建立并运行一个端到端的系统。这种初步的系统通常用来检验某种技术架构。而且人们会建立一个外围系统,用来处理一些支持性GENERIC SUBDOMAIN,因为这些子领域更易于分析。但这样做不利于风险管理。
第一个雏形系统应该以CORE DOMAIN的某个部分作为基础,不管它有多么简单。
模式:DOMAIN VISION STATEMENT
写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是“价值主张”。那些不能将你的领域模型与其他领域模型区分开的方面就不要写了。展示出领域模型是如何实现和均衡各方利益的。这份描述要尽量精简。尽早把它写出来,随着新的理解随时修改它。
以下是几个样例,左侧部分是DOMAIN VISION STATEMENT的一部分,而右侧部分虽然很重要,但不属于DOMAIN VISION STATEMENT。
模式: HIGHLIGHTED CORE
CORE DOMAIN必须能很容易被分辨出来。
精炼文档可以为读者提供一个总体视图,指出各个部分是如何组合在一起的,并且指导读者对应到响应的代码寻找更多细节。因此,作为 HIGHLIGHTED CORE(突出核心)的一种形式:编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE元素之间的主要交互过程。
独立文档带来的所有常见风险也会在这里出现:
- 文档可能得不到维护;
- 文档可能没人阅读;
- 由于有多个信息来源,文档可能达不到简化复杂性的目的。
控制这些风险的最好方法是保持绝对的精简。
作为另一种形式的HIGHLIGHTED CORE:在模型的主要存储库(Repository)中,把CORE DOMAIN标记出来,不用特意去阐明其角色。使开发人员很容易就知道什么在核心内,什么在核心外。
如果精炼文档概括了CORE DOMAIN的核心元素,那么它就可以作为一个指示器——用以指示模型改变的重要程度。
当模型或代码的修改影响到精炼文档时,需要与团队其他成员一起协商。当对精炼文档做出修改时,需要立即通知所有团队成员,而且要把新版本的文档分发给他们。CORE外部的修改或精炼文档外部的细节修改则无需协商或通知,可以直接把它们集成到系统中,其他成员在后续工作过程中自然会看到这些修改。
这样开发人员就拥有了XP所建议的完全自治性。
模式:COHESIVE MECHANISM
封装机制是面向对象设计的一个基本原则。把复杂算法隐藏到方法中,再为方法起一个一看就知道其用途的名字,这样就把“做什么”和“如何做”分开了。这种技术使设计更易于理解和使用。然而它也有一些先天的局限性。
计算有时会非常复杂,使设计开始变得膨胀。机械性的“如何做”大量增加,把概念性的“做什么”完全掩盖了。为解决问题提供算法的大量方法掩盖了那些用于表达问题的方法。
把概念上的COHESIVE MECHANISM(内聚机制) 分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION REVEALING INTERFACE来暴露这个框架的功能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么) 了,而把解决方案的复杂细节(如何做)转移给了框架。
CORE DOMAIN或GENERIC SUBDOMAIN的模型描述的是事实、规则或问题。而COHESIVE MECHANISM则用来满足规则或者用来完成模型指定的计算。
把GENERIC SUBDOMAIN提取出来可以减少混乱,而COHESIVE MECHANISM可以把复杂操作封装起来。这样可以得到一个更专注的模型,从而减少了那些对用户活动没什么价值的、分散注意力的方面。
模式: SEGREGATED CORE
对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取到其他对象中,并把这些对象放到其他的包中 —— 即使这会把一些紧密耦合的元素分开。
通过重构得到SEGREGATED CORE的一般步骤如下所示。
- 识别出一个CORE子领域(可能是从精炼文档中得到的)。
- 把相关的类移到新的MODULE中,并根据与这些类有关的概念为模块命名。
- 对代码进行重构,把那些不直接表示概念的数据和功能分离出来。把分离出来的元素放到其他包的类中(可以是新的类)。
- 对新的SEGREGATED CORE MODULE进行重构,使其中的关系和交互变得更简单、表达得更清楚,并且最大限度地减少并澄清它与其他MODULE的关系(这将是一个持续进行的重构目标)。
- 对另一个CORE子领域重复这个过程,直到完成SEGREGATED CORE的工作。
模式: ABSTRACT CORE
当不同MODULE的子领域之间有大量交互时,要么需要在MODULE之间创建很多引用,这在很大程度上抵消了划分模块的价值;要么就必须间接地实现这些交互,而后者会使模型变得晦涩难懂。
把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型,使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中,而专用的、详细的实现类则留在由子领域定义的MODULE中。
总结
尽管任何带来深层模型的突破都有价值,但只有CORE DOMAIN中的突破才能改变整个项目的轨道。
系列文章
- [DDD读书笔记] 运用模型①什么是领域模型
- [DDD读书笔记] 运用模型②通用语言
- [DDD读书笔记] 构造块①分离领域层
- [DDD读书笔记] 构造块②实体、值对象和服务
- [DDD读书笔记] 构造块③模块
- [DDD读书笔记] 构造块④聚合与工厂
- [DDD读书笔记] 构造块⑤仓库
- [DDD读书笔记] 构造块⑥实战模拟
- [DDD读书笔记] 重构①突破
- [DDD读书笔记] 重构②SPECIFICATION模式
- [DDD读书笔记] 重构③柔性设计
- [DDD读书笔记] 重构④使用分析模式和设计模式建模
- [DDD读书笔记] 战略设计①模型上下文策略
- [DDD读书笔记] 战略设计②精炼
- [DDD读书笔记] 战略设计③大型结构