本章主要讲解一个真实案例中的领域驱动的建模,逐步去突破深层次的模型,一步步的修改设计,慢慢优化。
重构的投入与回报并非呈线性关系。通常,小的调整会带来小的回报,小的改进也会积少成多。小改进可防止系统退化,成为避免模型变得陈腐的第一道防线。但是,有些最重要的理解也会突然出现,给整个项目带来巨大的冲击。
可以确定的是,项目团队会积累、消化知识,并将其转化成模型。微小的重构可能每次只涉及一个对象,在这里加上一个关联,在那里转移一项职责。然而,一系列微小的重构会逐渐汇聚成深层模型。
- 一般来说,持续重构让事物逐步变得有序。代码和模型的每一次精化都让开发人员有了更加清晰的认识。这使得理解上的突破成为可能。之后,一系列快速的改变得到了更符合用户需要并更加切合实际的模型。其功能性及说明性急速增强,而复杂性却随之消失。
这种突破不是某种技巧,而是一个事件。它的困难之处在于你需要判断发生了什么,然后再决定如何处理。为了说明这是种什么样的经历,这里将展示讲述一个笔者几年前参与过的真实项目,以及他们是如何获得一个宝贵的深层模型的。
一、一个关于突破的故事
背景:这个故事发生在纽约,经过一个冬天的漫长重构之后,我们最终得到了能够捕捉到一些领域关键知识的模型和一个确实能在应用程序中发挥作用的设计。当时我们正在给一家投资银行开发一个大型应用程序的核心部分,该程序用于管理银团贷款。
假如Intel想要建造一座价值10亿美元的工厂,就需要申请贷款,但贷款的额度太大,以至于任何一家借贷公司(lending company)都无法独立承担,于是这些公司就组成银团①,集中它们的资源,以此来支持这种巨额信贷。投资银行通常在银团里担当领导者的角色,负责协调各种交易和其他服务。我们的项目就是要开发这样一个用于跟踪和支持以上整个过程的软件。
当时,我们对于自己的成果感觉相当不错。4个月前,我们还身陷困境,因为之前留下的代码完全不可行,从那时开始我们就竭尽所能将其迁移到一个一致的MODEL-DRIVEN DESIGN中。
图8-1中展示的模型对常见业务进行了大幅简化。Loan Investment(贷款投资)是一个派生对象,用来表示某一投资者在Loan(贷款)中所承担的股份,它与投资者在Facility(信贷)中所持有的股份成正比。inverstment(投资)
investor (投资者)
① 借贷公司(lending company)对于借款者(borrower)而言是放贷方(lender),对于银团而言是投资者(investor)。
但是这个模型已显露出了一些令人担忧的迹象。各种意料之外的需求一直困扰着我们,也使设计更加复杂。最突出的例子就是对Facility股份的理解不断深入,在提取(Drawdown)贷款时,信贷股份仅仅是放贷方投入金额的指导原则。当借款者(borrower)要求提取贷款时,银团领导者会通知所有成员按各自的股份进行支付。
收到通知后,投资者通常会按自己的股份来支付,但是有时他们也会与银团其他成员协商,以求少投入(或多投入)一些。于是我们在模型中添加了Loan Adjustment(贷款调整)以反映这一事实。
这种类型的精化使我们能够越来越清楚地理解各种交易规则。但同时,模型的复杂度也在不断增加,并且看起来我们无法很快从模型中提炼出真正健壮的功能。
更麻烦的是尽管算法越来越复杂,我们却无法解决舍入运算所带来的细微差别。在1亿美元的交易中,确实没有人会在意几美分的去向,但是银行家是不会信任无法精确计算到美分的软件的。我们开始怀疑我们所遇到的难题可能是因为基本设计存在问题。
一、2 突破
在项目进行过程中,我们突然领悟到了问题的所在。我们在模型中把Facility的股份和Loan的股份绑定到一起,而这种方式并不适用于实际的业务。这一发现得到了广泛的认可。业务专家点头称是,并开始热情地给予帮助,我们迅速在白板上创建了一个新模型。虽然细节问题尚未确定,但是我们已经知道新模型的关键特征了:
Loan的股份和Facility的股份可以在互不影响的前提下独立发生改变。
有了这层认知,我们利用与下图类似的模型图走查了许多场景:
这张图表明Facility的总额是1亿美元,而借款者选择从中提取的第一笔Loan金额是5000万美元。3个放贷方按照各自原先承诺的Facility的股份来支付,这样5000万的Loan就被分配到了这3个放贷方头上。
随后,借款者又提取了另一笔3000万美元的货款(如图8-4所示),这样他的未偿Loan就达到了8000万美元,依然在Facility的1亿美元限额之内。这次,公司B决定不参与Loan,而由公司A来承担这部分额外的股份。各个放贷方在借款者提取贷款的过程中所支付的股份反映了它们的投资选择。当Loan的提取额不断增加时,Loan的股份份额就不再与Facility的股份成比例了。这种现象很普遍。当借款者偿还Loan时,所偿还的金额会根据Loan的股份分配给各放贷方,而不是根据Facility的股份来划分。同样,利息支付也会按照Loan的股份进行分配。
另一方面,当借款者为享有Facility权而支付费用时,这笔钱是按照Facility的股份划分的, 而不考虑放贷方是否借出了钱。Loan不会因费用支付而发生变化。甚至还有这种情况,放贷方单独交易费用股份,与利息股份等无关。
一、3、更深层模型
我们得到了两个深层理解。其一是意识到“投资”和“Loan投资”是“股份”这个常规基础概念的两种特例。信贷股份、货款股份、支付比例股份,这些都是股份,股份无处不在。任何可分配的价值都是股份。
Share Pie() percent pie
我同时也草绘了一个与股份模型搭配的新贷款模型。
现在Facility股份和Loan股份不再由专用对象来表示了。它们都被分解成了更直观的Share Pie。这种泛化引入了“股份数学”的概念,极大地简化了所有交易中的股份计算,同时也使这些计算更富有表达力、更简洁且更易于组合。
但最重要的是,新模型删除了不恰当的约束,这使得我们的问题迎刃而解。Loan的Share因而无需与Facility 的Share成比例,同时新模型仍然保留着对总额、费用分配等的有效约束。我们可以直接调整Loan的Share Pie,因此新模型也不再需要Loan Adjustment和大量处理特殊情况的逻
辑了。
新模型中不再包含Loan Investment对象,我们此时才意识到“贷款投资”并不是一个银行业术语。事实上,业务专家早已多次告诉我们,他们不明白“贷款投资”是什么意思。但是他们还是尊重我们在软件方面的知识,并假定它对技术方面的设计是有所帮助的。而事实上,我们之所以创建它是因为没有完全理解领域。
这种看待领域的新方法使我们立即就可以毫不费力地处理之前遇到的所有场景,处理过程要比以往简单许多。业务专家认为我们的模型图非常合理,他们曾经指出我们之前的模型图对他们来说“技术性太强”。即使只是在白板上画出草图,我们也能看出新模型能够彻底解决长期困扰我们的舍入计算问题,我们可以不再使用复杂的舍入代码了。
一、4. 冷静决策
重构的原则是始终小步前进,始终保持系统正常运转。但是要按照这个新模型来重构则需要修改大量的支持代码,在重构的过程中,系统几乎无法正常运转。我们能够看出一些力所能及的微小改进,但这些改进无法让我们实现新的领域概念。我们也知道通过一系列小改动可以实现新模型,但是在这个过程中必然会导致程序的一部分功能无法正常工作。而且在当时,自动化测试还没有广泛应用于这种项目。我们一无所有,所以肯定会出现一些让我们始料不及的破坏。
此外,重构是需要花费精力去实现的。这时,我们与项目经理开了一次会,这次会议令我终生难忘。我们的项目经理是个睿智而勇敢的人。他问了我们许多问题:
他给我们开了绿灯,并告诉我们他会控制好局面。做出这种决定需要无比的勇气和信心,这使我一直对他钦佩不已。
我们全力以赴,在3个星期内完成了任务。这是个巨大的工程,但是却进展得异常顺利。
一、5、成果
项目需求不再有意外的、难以捉摸的改变了。舍入逻辑的实现虽然并不简单,却很稳定并且合理。我们交付了软件的第一个版本,第二个版本的开发思路也很清晰了。
在进行第二个版本的开发时,Share Pie成了整个程序的统一主题。技术人员和业务专家利用它来对系统进行讨论。市场人员使用它来向预期客户解释系统特性。这些预期客户和其他的客户都能立刻理解它,并且可以马上用它来讨论特性。由于它抓住了银团货款的核心问题,所以它真
正成为了UBIQUITOUS LANGUAGE的一部分。
二、 机遇
当突破带来更深层的模型时,通常会令人感到不安。与大部分重构相比,这种变化的回报更多,风险也更高。而且突破出现的时机可能很不合时宜。
尽管我们希望进展顺利,但往往事与愿违。过渡到真正的深层模型需要从根本上调整思路,并且对设计做大幅修改。在很多项目中,建模和设计工作最重要的进展都来自于突破。
三、 关注根本
不要试图去制造突破,那只会使项目陷入困境。通常,只有在实现了许多适度的重构后才有可能出现突破。在大部分时间里,我们都在进行微小的改进,而在这种连续的改进中模型深层含义也会逐渐显现。
要为突破做好准备,应专注于知识消化过程,同时也要逐渐建立健壮的UBIQUITOUS LANGUAGE。寻找那些重要的领域概念,并在模型中清晰地表达出来(参见第9章)。精化模型,使其更具柔性(参见第10章)。提炼模型(参见第15章)。利用这些更容易掌握的手段使模型变得
更清晰,这通常会带来突破。不要犹豫着不去做小的改进,这些改进即使脱离不开常规的概念框架,也可以逐渐加深我们对模型理解。不要因为好高骛远而使项目陷入困境。只要随时注意可能出现的机会就够了。
四、后记:越来越多的新理解
突破使我们走出了困境,但故事并没有就此结束。更深层次的模型为我们带来了意想不到的机会,它使应用程序的功能更加丰富,设计也更加清晰。
在Share Pie版本的程序发布几周之后,我们注意到在模型中还存在一个使设计变得复杂的地方。我们漏掉了一个重要的ENTITY,结果是本来应该由它承担的职责不得不由其他对象来完成。
具体来说就是提取货款、缴纳费用等业务是由一些重要的规则控制的,而所有这些逻辑都分散在Facility和Loan中的各种方法里了。这些设计问题在Share Pie突破出现之前几乎没有引起我们的注意,但是随着我们对领域的理解日渐清晰,它们也变得明显起来。现在我们开始注意到,那些经常在讨论中出现的术语(如“交易”,代表一次金融交易)并没有体现在模型中,反而隐含在了那些复杂的方法里。
经过与之前类似的过程(但所幸的是,我们有了更加充足的时间),我们对领域的理解又向前迈进了一步,并获得了一个更深层次的模型。这个新模型不但使所有隐含的概念显现了出来,如Transaction(事务),以及一个简化的Position(包括Facility和Loan的抽象类)。于是定义各种交易、交易规则、协商程序和审批流程就变得轻而易举了,而且实现这些概念的代码也相对来说变得更好理解了。通常,在经过一次真正的突破并获得了深层模型之后,所获得的新设计变得更加清晰简单,新的UBIQUITOUS LANGUAGE也会增进沟通,于是又促成了下一次建模突破。
当大部分项目由于规模和复杂度的累积而举步维艰时,我们的项目却正在加速前进。