Acknowledgements
四年多来,我一直在写这本书,以这样或那样的形式,一路上有很多人帮助和支持我。
我感谢许多阅读手稿和评论的人。如果没有这些反馈,这本书是不可能完成的。有几家对他们的评论给予了特别慷慨的关注。由Russ Rufer和Tracy Bialek领导的硅谷模式小组(Silicon Valley Patterns Group)花了七周时间仔细审阅这本书的第一份完整草稿。由拉尔夫·约翰逊(Ralph Johnson)领导的伊利诺伊大学读书小组也花了几周时间仔细审查后来的草稿。听这些小组长时间、生动的讨论,产生了深远的影响。Kyle Brown和Martin Fowler提供了详细的反馈,有价值的见解和无价的道德支持(坐在一条鱼上)。沃德·坎宁安的反馈很重要。Alistair Cockburn鼓励我,帮助我在出版过程中找到自己的方向。大卫·西格尔(David Siegel)和尤金·沃林福德(Eugene Wallingford)帮我避免了在技术层面上的尴尬。Vibhu Mohindra和Vladimir Gitlevitch煞费苦心地检查了所有的代码示例。
Rob Mee阅读了我最早的一些探索材料,并与我一起进行头脑风暴,当我在探索某种方式来传达这种设计风格时。然后他给我灌了一份很久以后的草稿。
Josh Kerievsky负责了这本书发展的一个主要转折点:他说服我尝试“亚历山大”模式格式,这成为了这本书的组织支柱。在1999年的PLoP会议之前的密集“指导”过程中,他还帮助我将第二部分中的一些材料第一次整合成一个连贯的形式。这成了本书其余大部分内容的基础。
在构思这本书之前,我必须先形成自己对软件开发的看法和理解。这一发展在很大程度上要归功于一些才华横溢的人的慷慨,他们是我的非正式导师,也是我的朋友。David Siegel, Eric Gold和Iseult White,以不同的方式帮助我形成了软件设计的思维方式。几乎同时,Bruce Gordon, Richard Freyberg和Judith Segal也以不同的方式帮助我在成功的项目工作中找到自己的道路。
在这个关键时期,我也有很多想法,这些想法形成了我自己技术观点的基础。其中一些贡献将在主要文本中清楚说明,并在可能时加以引用。其他的是如此的基本,我甚至没有意识到他们对我的影响。
我的硕士论文导师,Bala Subramanium博士,让我对数学建模产生了兴趣,我们把它应用到化学反应动力学中,但建模就是建模,而这项工作正是我写这本书的原因之一。
甚至在此之前,我的思维方式的形成多亏了我的父母,Carol 和 Gary Evans,以及一些特殊的老师,特别是 Dale Courier(一所高中的数学老师),Mary Brown(高中英语作文老师),和一个六年级的科学课老师的名字,我很抱歉已经遗忘了。
最后,我要感谢我的朋友和家人,还有 Fernando De Leon,感谢他们在这漫长的过程中给予我的鼓励。
Preface
领先的软件设计师已经认识到领域建模和设计(domain modeling and design)作为关键的主题至少已经有 20 年了,但是令人惊讶的是,几乎没有人写过需要做什么或者如何做。虽然它从来没有被清晰的表述出来,但一种哲学已经发展成为对象社区中的一股暗流,我称之为“领域驱动设计(domain-driven design)”。
在过去的十年里,我专注于在几个业务和技术领域开发复杂的系统。我尝试过设计和开发过程中的最佳实践,因为它们已经从面向对象开发社区的领导者那里出现了。我的一些项目非常成功;一些失败了。成功案例的共同特征是丰富的领域模型,它通过设计的迭代不断发展,并成为项目结构的一部分。
本书提供了一个设计决策的框架和一个讨论领域设计的技术词汇表。它综合了广泛接受的最佳实践,以及我自己的见解和经验。面对复杂领域的项目可以使用这个框架,系统地实现领域驱动设计。
Contrasting Tree Projects
我看到一个项目用一个有用的、简单的基于网络的交易系统很快就完成了。开发人员凭自己的感觉,但简单的软件也可以在不太关注设计的情况下编写出来。由于这一初步成功,人们对未来发展的期望很高。就在这个时候,有人找我做第二个版本的工作。当我仔细观察的时候,我发现他们缺少一个领域模型,甚至在项目上没有通用的语言,并且被强加于非结构化的设计。所以当项目领导不同意我的评估时,我拒绝了这份工作。一年后,他们发现自己陷入了困境,无法发布第二个版本。尽管他们对技术的使用不是典范,但业务逻辑战胜了他们。他们的第一个版本过早地僵化为一个高维护的遗产。
要想提高复杂性的上限,需要一种更严肃的领域逻辑设计方法。在我职业生涯的早期,我很幸运地完成了一个强调领域设计的项目。这个项目,在一个至少和上面一样复杂的领域,也开始了一个适度的初步成功,为机构交易者交付一个简单的应用程序。但这一交付之后,开发的速度连续加快。每次后续的迭代都为集成和细化功能打开了令人兴奋的新选项。团队的方式能够灵活地响应交易员的需求和扩展能力。这种向上的轨迹直接归因于一个尖锐的领域模型,它在代码中反复的细化和表达。随着团队对该领域有了新的认识,模型也加深了。开发人员之间以及开发人员与领域专家之间的交流质量得到了提高,而且设计非但没有增加越来越重的维护负担,反而变得更容易修改和扩展。
不幸的是,并非所有以这种意图开始的项目都能达到这种良性循环。我参加的一个项目一开始有一个远大的志向,那就是建立一个基于领域模型的全球企业系统,但最后却有一个失望的结果。这个团队有很好的工具,对业务有很好的理解,并且非常重视建模。但是开发人员角色的分离导致了模型和实现之间的脱节,因此设计没有反映正在进行的深入分析。在任何情况下,详细业务对象的设计都不够严格,无法支持在详细的应用程序中组合它们。由于开发人员的技能水平参差不齐,且对所需的特定类型的严格性没有清晰的理解,重复的迭代并没有产生代码的改进。几个月过去了,开发工作陷入了复杂性的泥潭,团队是去了系统的凝聚力。经过多年的努力,该项目确实生产处了规模不大、有用的软件,但已经放弃了早期的雄心壮志和模型重点。
当然,很多事情会让项目偏离轨道,比如官僚主义、目标不明确、资源缺乏等,但是设计方法在很大程度上决定了软件的复杂程度。当复杂性失去控制时,软件就不能再被很好地理解以方便地更改或扩展。相比之下,一个好的设计可以从这些复杂的功能中创造机会。
其中一些设计因素是技术性的,大量的工作已经投入到网络、数据库和软件的其他技术层面的设计中。关于如何解决这些问题的书已经出版了。开发人员已经培养了他们的技能。
然而,许多应用程序最显著的复杂性并不是技术上的。它属于领域本身,用户的活动或业务。如果在设计中没有处理这个领域的复杂性,那么基础设施技术是否经过了良好的考虑就无关紧要了。一个成功的设计必须系统地处理软件的这个核心方面。
这本书的前提是:
- 对大多数软件项目,主要关注点应该放在域和域逻辑(domain and domain logic)上。
- 复杂的领域涉及应该基于模型。
领域驱动设计是一种思维方式和一组优先级,旨在加速必须处理复杂领域的软件项目。为了实现这个目标,这本书提出了一套管饭的设计实践,技术和原则。
Design vs. Development Process
设计的书。过程的书。它们甚至很少相互引用。每一个都是一个复杂的主题。这是一本设计的书籍。但我认为,要想将设计理念成功地付诸实践,而不是在学术讨论中枯竭,这两个问题是不可分割的。当人们学习设计技术时,他们会面对真正项目的混乱现实。他们不知道什么时候该考虑某个特定的设计方面,什么时候该放弃以节省时间。虽然我们可以与其他团队成员讨论抽象设计原则的应用,但更自然的做法是讨论我们一起做的事情。所以,虽然这是一本书,但我要在需要的时候,打破这个人为的界限。这将把设计至于开发过程的上下文中。
这本书并不是针对特定的方法论,而是面向“敏捷开发过程”的新系列。具体的说,它假定在项目中有一些过程实践。这两个实践是应用本书中的方法的先决条件。
- 迭代开发。迭代开发的实践已经被提倡和实践了几十年,并且是敏捷开发方法的基石。关于敏捷开发和极限编程的文献中有很多很好的讨论,其中有 [Cockburn1998] 和 [Beck 1999]。
- 开发人员和领域专家之间的密切关系。领域驱动的设计将大量的知识转化为一个模型,该模型反映了对领域的深刻理解和对关键概念的关注。这是一个了解领域的人和知道如何构建软件的人之间的协作。因为它是迭代的,所以这种协作必须在项目的整个生命周期中继续进行。
极限编程(Extreme Programming, XP)是由 Kent Beck,Ward Cunningham 和其他人 [Beck 2000] 构想的,它是敏捷过程中最杰出的,也是我接触最多的。为了使讨论更加具体,我将在整本书中使用 XP 作为讨论设计和过程交互的基础。说明的原则很容易适用于其他敏捷过程。
近年来,人们开始反对复杂的开发方法,这种方法是无用的、静态的文档、强迫性的前期计划和设计来给项目增加负担。相反,敏捷过程(如 XP)强调的是应对变化和不确定性的能力。
XP 认识到设计决策的重要性,但强烈反对预先设计。相反,它投入了令人钦佩的努力来增加沟通,并增加项目快速改变方向的能力。有了这种反应能力,开发人员可以在项目的任何阶段使用“最简单的可以工作的东西”,然后不断地重构,进行许多小的设计改进,最终达到符合客户真正需求的设计。
对于一些设计狂热者来说,这是一剂继续的解毒剂。项目陷入了没有价值的繁琐文档中。他们遭受了“分析麻痹”,如此害怕一个不完美的设计,以至于他们没有取得任何进展。必须有所改变。
不幸的是,其中一些新的过程思想很容易被误解。每个人对“最简单”都有不同的定义。没有设计原则来指导这些小的重新设计的持续重构,会产生难以理解或更改的代码库——这与敏捷性背道而驰。而且,尽管对未来预料到的需求的恐惧尝尝导致过度工程化,但试图避免过度工程化可能会发展成另一种恐惧:对任何深度设计思考的恐惧。
事实上,XP 最适合具有敏锐设计意识的开发人员。XP 过程假设您可以通过重构来改进设计,并且您经常和迅速地这样做。但是设计选择使重构本身变得更容易或更困难。XP 过程试图增加团队交流。但是模型和设计的选择会澄清或混淆沟通。我们需要的是一种能够发挥作用的领域建模和设计方法。
这本书交织了设计和开发实践,并说明了领域驱动的设计和敏捷开发是如何相互加强的。在敏捷开发过程的上下文中,一种复杂的领域建模方法将加速开发。过程与领域开发的相互关系使这种方法比任何真空中的“纯”设计处理更实用。
The Structure of This Book
本书分为四个主要部分:
第一部分:Putting the Domain Model to Work 中提出了领域驱动开发的基本目标,这些开发将在后面的部分中激励实践。由于软件开发有很多方法,第一部分定义了术语,并概述了领域模型置于驱动通信和设计的角色中的含义。
第二部分:The Building Blocks of Model-driven Design 将面向对象领域建模的最佳时间核心浓缩为一组基本模块。本节的重点是弥合模型和实际运行的软件之间的差距。共享这些标准模式为设计带来的秩序,并使团队成员很容易理解彼此的工作。使用标准模式还可以建立一种公共语言,所有团队成员都可以使用它来讨论模型和设计决策。
但是,本节的主要观点是关于保持模型和实现相互对齐的那种决策、增强彼此的有效性。这种对齐需要注意单个元素的细节。在这种小范围内的精心制作为开发人员提供了一个稳定的平台来应用第三部分和第四部分中的建模方法。
第三部分:Refactoring Toward Deeper Insight 超越了构建模块的挑战,将它们组装成提供回报的实际模型。本节强调的是发现过程,而不是直接跳到深奥的设计原则。有价值的模型不会马上出现。它们要求对该领域有深刻的理解。这种理解来自于深入,实现一个基于 天真的(naïve) 模型的初始设计,然后一次又一次地转换它。每次团队获得洞察力时,模型都会被转换为揭示更丰富的知识,代码也会被重构,以反映更深层的模型,并使其潜能可用于应用程序。然后,有时,这种洋葱式的剥离会带来一个突破更深层模型的机会,伴随着一系列深刻的设计变化。
探索本质上是开放式的,但并不一定是随机的。第三部分深入研究了可以指导选择的建模原则,以及帮助知道搜索的技术。
第四部分:Strategic Design 处理出现在复杂系统、大型组织、与外部系统和遗留系统的交互中的情况。本节讨论了适用于系统作为一个整体的三个原则:有界上下文(Bounded Context)、蒸馏(Distillation)、大规模结构(Large-Scale Structure)。战略设计决策有团队,甚至在团队之间制作。战略设计使我能够以更大的规模实现第一部分的目标,适用于大型系统或适合企业范围内的应用程序。
整本书的讨论都是用显示的例子来说明的,这些例子来自实际的项目,而不是过于简化的“玩具”问题。
这本书的大部分内容都是作为一套“模式”而写的。读者应该能够完全理解材料而不关心这个设备,但是那些对模式的样式和格式感兴趣的人可以阅读附录1。
Who This Book is Written For
这本书主要是为面向对软件的开发人员编写的。软件项目团队的大多数成员都可以从它的某些部分中受益。这对于哪些正在进行项目的人来说是最有意义的,他们尝试着去做这些事情,或者那些已经有了与之相关的深刻经验的人。
本书需要一些面向对象建模的只是。这些例子包括 UML 图和 Java 代码,因此在基本层面上阅读这些语言的能力很重要,但是没有必要掌握 UML 或 Java 的细节。极限编程的知识将为开发过程的讨论增加视角,但是没有背景知识的讨论应该是可以理解的。
对于一个中级软件开发人员,一个已经知道一些面向对象设计的读者,可能已经读过一到两本软件设计书籍,这本书将填补空白,并提供了如何在软件项目的现实生活中使用对象建模的观点。它将帮助中级开发人员将复杂的建模和设计技能应用到实际问题。
高级或专业的软件开发人员应该对处理该领域的综合框架感兴趣。系统的设计方法将帮助他们带领团队走这条路。连贯的属于将有助于他们与同行交流。
不同背景的读者可能希望通过本书采取不同的路径,将重点转移到不同的点上。我建议所有读者从第一部分和第一章的介绍开始。这本书是一本叙事性的书,可以从头读到尾,也可以从任何一章的开头读。一个已经对某个主题有所了解的浏览者应该能够通过阅读标题和粗体文本来抓住要点。一个非常高级的读者可能想要浏览第一部分和第二部分,并且可能对第三部分和第四部分最感兴趣。
除了这些核心读者外,这本书还会引起分析人员和相对技术性的项目经理的兴趣。分析人员可以利用模型和设计之间的练习,在“敏捷”项目的上下文中做出更有效的贡献。分析师也可以使用一些战略设计原来来更好地集中和组织他们的工作。
项目经理应该关注如何使团队更有效,以及如何设计对业务专家和用户有意义的软件。而且,由于战略设计决策与团队组织和工作风格有关,这些设计决策必然涉及到项目的领导,并对项目的发展轨迹产生重大影响。
虽然理解领域驱动设计的开发人员将获得有价值的设计技术和观点,但当团队应用领域驱动的设计方法并将领域模型移到项目讨论的中心时,将获得最大的收益。团队成员将共享一种语言,这种语言丰富了他们的交流,并使其与软件保持联系。它们将生成与模型同步的实现,从而为应用程序开发提供优势。他们将分享不同团队的设计工作如何关联的映射,并将系统地关注对组织最有特色和最有价值的功能。
领域驱动的设计是一个困难的技术挑战,它可以带来巨大的回报,在大多数软件项目开始僵化为遗留项目的阶段,它打开了机会。
Eric Evans, San Francisco, California, March 2003
http://domainlanguage.com
Part I. Putting the Domain Model to Work
上面的 18 世纪中国地图代表了整个世界。在中心占据了大部分空间的是中国,周围是敷衍了事的其他国家的代表。这是一个适合那个故意向内转的社会的世界模式。这幅地图所代表的的世界观在与外国人打交道时一定没有帮助。当然,它根本不会为现代中国服务。地图是模型,每一个模型都代表了现实的某些方面或一个有趣的想法。这是一种简化。这是一种对现实的解释,它抽象了与解决手头问题有关的方面,而忽略了无关的细节。
每个软件程序都与用户的某些活动或兴趣有关。用户应用程序的主题区域就是软件的“领域(domain)”。有些领域涉及物理世界。航空公司预定程序的领域涉及到真实的人称作真实的飞机。有些领域是无形的。会计程序的领域是货币和金融。软件领域通常与计算机没有什么关系,尽管也有例外。源代码控制系统的领域是软件开发本身。
要创建有价值的软件,我们必须提供与软件将涉及的活动相关的知识体系。所需的知识量可能令人生畏。信息的数量和复杂性可能是压倒性的。这时,开发团队可以使用建模来处理过载问题。模型是一种有选择地简化和有意识地结构化的知识形式。一个合适的模型可以使信息有意义,并使其与问题相关。
领域模型不是一个特定的图:这就是图所要传达的思想。它不仅仅是领域专家头脑中的知识;它是对知识的严格阻止和选择性的抽象。图可以表示和交流模型,也可以是精心编写的代码,也可以是英语句子。
领域建模不是使模型尽可能“真实”的问题。即使在有形的现实世界中,我们的模型也是人为创造的。也不只是构建一个软件机制来提供必要的结果。它更像电影制作,松散地代表现实以达到特定的目的。即使是异步纪录片也不会展现未经编辑的真实生活。正如电影制作人员选择体验的各个方面,并以一种特殊的方式来讲述一个故事或表达一个观点一样,选择领域模型是为了它的实用性。
The Utility of a Model in Domain-Driven Design
在领域驱动的设计中,有三个基本的用途决定模型的选择。
- 该模型规定了软件核心的设计形式。正是模型和实现之间的密切关系,使模型具有相关性,并确保进入模型的分析适用于最终产品,即运行的程序。这种模型和实现的绑定还有助于维护和继续开发,因为可以根据对模型的理解来解释代码。(第三章)
- 模型是所有团队成员使用的语言的主干。由于模型和实现的绑定,开发人员可以用这种语言来讨论程序。他们无需翻译就可以与领域专家交流。因为语言是基于模型的,我们的自然语言能力可以被用来完善模型本身。(第二章)
- 模型是对知识的提炼。模型是团队一致同意的构建领域知识和区分最感兴趣的元素的方法。当我们选择术语、分解概念并将它们联系起来时,模型捕获了我们如何选择考虑这个领域。共享语言允许开发人员和领域专家有效地协作,将信息转换成这种形式。模型和实现的绑定意味着使用软件早期版本的经验也是对模型过程的有效反馈。(第一章)
接下来的三章依次探讨这些贡献的意义和价值,以及它们互相交织的方式。以这些方式使用模型可以支持具有丰富功能的软件开发,否则将需要大量的临时开发投资。
然而,大多数项目从他们的领域模型中得到的很少。这本书将研究有效领域开发的一系列潜在障碍,以及通过设计原则(从高级概念到具体技术)客服这些障碍的方法。
The Centrality of Ddomain Functionality
软件的核心是为用户解决领域相关问题的能力。所有其他功能,尽管可能很重要,都支持这一基本目的。当领域复杂时,这是一项艰巨的任务,需要有才能和技能的人集中努力。开发人员必须深入该领域以积累业务知识。他们必须磨炼自己的建模技能并掌握领域设计。
然而,这并不是大多数软件项目的优先级。大多数有才华的开发人员对他们工作的特定领域并没有太多的兴趣,更不用说对扩展他们的领域建模技能做出重大承诺了。技术人员喜欢可量化的技术问题,以锻炼他们的技术能力。这个领域很混乱,需要大量复杂的新知识,而这些知识似乎并不能发展一个计算机科学家的能力。
取而代之的是,技术人才致力于复杂的框架,试图用技术解决领域问题。软件核心的复杂性必须迎面解决。没有这个重点是一个项目风险。
在一次电视访谈节目中,喜剧演员 John Cleese 讲述了《Monty Python and the Holy Grail》拍摄期间的一个故事。他们一遍又一遍地拍摄一个特定的场景,但不知怎么的,这并不有趣。最后,他休息了一下,和他的喜剧伙伴 Michael Palin (现场的另一个演员)商量了一下,他们想出了一个轻微的变化。他们又拍了一集,所结果很有趣,所以就收工了。
第二天早上,Mr. Cleese 正在看电影剪辑师从前一天的工作中整理出来的粗剪。回到他们苦苦挣扎过的场面,他发现这一点也不好玩。一个早期的镜头被使用过。
他问电影编辑,为什么他没有按照导演的指示使用最后的镜头。“用不上。有人走了进来。”编辑回答。Mr. Cleese 看了一遍又一遍。他仍然看不出有什么不对。最后,剪辑员停下胶卷,指着照片边缘的一个外套袖子,在图片的边缘可以看到一会儿。
电影编辑担心其他看过这部电影的电影编辑会根据他作品的技术完美程度来评判他的作品。他专注于自己专业的精确执行,在这个过程中,场景的核心已经丢失。[“The Late Late Show with Craig Kilborn”, CBS, September, 2001]
幸运的是,一个懂喜剧的导演恢复了滑稽的场景。同样地,当热情的开发人员忙于开发复杂的技术框架,而这些技术框架并不服务于领域开发,或者实际上阻碍了领域开发,而反应了对领域深刻理解的模型的开发却在混乱中丢失时,理解领域中心的团队领导可以让他们的软件项目回到正规。
这本书将说明领域开发提供了培养非常复杂的设计技能的机会。大多数业务领域的混乱是一个有趣的技术挑战。事实上,在许多科学学科中,当研究人员试图解决现实世界的混乱时,“复杂性”是当前最令人兴奋的话题之一。当软件开发人员面对一个从未形式化的复杂领域时,也会有同样的前景。创建一个清晰的模型,并通过复杂的过程来解决问题是令人兴奋的。
开发者可以使用系统的思维方式来寻找洞察力并生成有效的模型。有一些设计技术可以为庞大的软件应用程序带来秩序。这些技能的培养使开发人员更有价值,即使是在最初不熟悉的领域。
1. Crunching Knowledge
几年前,我开始为印刷电路板(Printed Circuit Board, PCB)设计一个专门的软件工具。有一个问题:我对电子硬件一无所知。当然,我接触到了一些 PCB 设计师,但他们通常在三分钟内就让我晕头转向。我怎样才能充分理解编写这个软件呢?我当然不会在截止日期之前成为一名电气工程师!
我们尝试让他们确切地告诉我软件该做什么。坏主意。他们是伟大的电路设计师,但他们的软件理念通常涉及阅读 ASCII 文件,对其进行分类,用一些注释将其写出来,然后生成一份报告。这显然不会带来他们所期望的生产力的飞跃。
最初的几次会议令人沮丧,但在他们要求的报告中有一丝希望。他们总是设计“网(nets)”和各种各样的是细节。在这个领域,网络本质上是一种导线,它可以连接 PCB 上任意数量的组件,并将电子信号传输到与之相连的任何东西上。我们有了领域模型的第一个元素。
当我们讨论他们希望软件做的事情时,我开始为他们绘制图表。我使用了对象交互图的非正式变体来演示场景。
PCB 专家 1:组件(components)不必是芯片。
开发人员(我):所以我应该称它们为组件(components)?
专家 1:我们称它们为“组件实例(component instances)”。可能有许多相同的组件。
专家 2:“网络(net)” box 看起来就像一个组件实例。
专家 1:他没有用我们的符号。我想,对他们来说,一切都是一个 box。
开发人员:很抱歉,是的。我想我最好再多解释一下这个符号。
他们不断地纠正我,这样我才开始学习。我们消除了他们术语中的冲突和歧义,以及他们技术观点的差异,他们从中学习。他们开始更精确和一致地解释事物,我们开始一起开发一个模型。
专家 1:仅仅说一个信号到达一个 ref-des 是不够的,我们必须知道引脚(pin)。
开发人员:Ref-des ?
专家 2:和组件实例一样。Ref-des 是我们使用的一种特殊工具。
专家 1:无论如何,一个 net 将一个实例的特定引脚连接到另一个实例的特定引脚。
开发人员:你是说一个引脚只属于一个组件实例并连接到只有一个网络(net)?
专家 1:是的,没错。
专家 2:而且,每个网络(net)都有一个拓扑结构,一种决定网络(net)元素连接方式的排列。
开发人员:好的,这个怎么样?
为了专注于我们的探索,我们暂时把自己限制在研究一个特定的功能上。“探测模拟(probe simulation)”将跟踪信号的转播,以检测设计中可能存在的某些类型的问题。
开发人员:我明白信号是如何通过网络(Net)传输到所有连接的引脚上(Pins)的,但它是如何进一步传播的呢?拓扑(Topology)和它有关系吗?
专家 2:没有。组件推动信号通过。
开发人员:我们当然不能模拟芯片内部的行为。这太复杂了。
专家 2:我们不需要。我们可以简化一下。只是将组件从某些引脚(Pins)推到某些其他引脚的列表。
开发人员:像这样?
[经过大量的反复试验,我们一起勾勒出了一个场景]
开发人员:但是您需要从这次计算中了解什么呢?
专家 2:我们要找的是长时间的信号延迟——比如说,任何超过两到三跳(hops)的信号路径。这是经验法则。如果路径太长,信号可能在时钟周期内无法到达。
开发人员:超过三跳(hops)......我们需要计算路径长度。什么算跳(hop)呢?
专家 2:每次信号通过网络(Net),就是一次跳跃(hop)。
开发人员:所以我们可以传递跳(hops)数,没经过一个网络(Net)可以增加它,就像这样。
开发人员:我唯一不清楚的部分是“推送(pushes)”从何而来。我们是否为每个组件实例存储这些数据?
专家 2:推送(pushes)对于组件的所有实例来说都是一样的。
开发人员:所以组件的类型决定推送(pushes)。它们对每一个都是实例都是一样的?
专家 2:我不确定这到底意味着什么,但我想每个组件的推送存储(push-throughs)应该是这样的。
开发人员:对不起,我说得有点太详细了。我只是在思考它。
开发人员:所以,现在,拓扑(Topology)在哪里发挥作用呢?
专家 1:这不是用来模拟探针的。
开发人员:那我现在就退出,好吗?我们可以在讲到这些功能的时候再讲。
事情就这样发生了(比这里显示的更加磕磕绊绊)。头脑风暴和精炼;质疑和解释。随着我对领域的理解,以及他们对模型将如何在解决方案中发挥作用的理解,模型得到了开发。表示早期模型的类图如下所示。
在做了几天的业余工作后,我觉得自己已经能够理解一些代码了。我写了一个非常简单的原型,有一个自动化测试框架驱动。我避开了所有的基础设施。没有持久性,也没有 UI,这让我能够专注于行为。我能够在短短几天内演示一个简单的探测模拟。尽管他使用虚拟数据并将原始文本写入控制台,但它仍然适用 Java 对象进行路径长度的实际计算。这些 Java 对象反应了领域专家和我共享的模型。
这个原型的具体性使他们更清楚模型的含义,以及它如何与功能软件相关。从那时起,我们的模型讨论变得更有互动性,因为他们可以看到我如何将我新获得的知识整合到模型中,然后再到软件中。他们从原型中得到具体的反馈来评估自己的想法。
在这个模型中嵌入的是与我们正在解决的问题相关的 PCB 领域的知识,这个模型自然变得比这里展示的要复杂得多。它巩固了许多同义词和描述上的细微变化。它排除了数百个工程师理解但不直接相关的事实,比如组件的实际数字特征。像我这样的软件专家可以通过查看图表,在几分钟内就开始了解软件是关于什么的。他或她将有一个框架来组织新信息,并更快速地学习,更好地猜测什么是重要的,什么不是,并更好地与 PCB 工程师沟通。
当工程师们描述他们需要的新功能时,我让他们向我介绍这些物体相互作用的场景。当模型对象不能带我们通过一个重要的场景时,我们将新的内容或旧的更改进行了头脑风暴或改变。我们完善了模型:协同进化的代码。几个月后,他们有了超出预期的丰富工具。
Why It Worked
我们做的一些事情导致了这次成功。
- 尽快绑定模型和实现。这个粗糙的原型铸就了关键的环节。
- 培养一种基于模型的语言。一开始,他们必须像我解释基本的 PCB 问题,而我必须解释类图的含义。但随着研究的进行,从模型中直接取出的术语,组织成与模型结构一致的句子,在他们或我听到后,立即测试了模型的可行性。
- 知识丰富的模型。对象有行为和强制的规则。这个模型不仅仅是一个数据模式,它对于解决一个复杂的问题是不可或缺的。它捕捉了各种各样的知识。
- 知识经过提炼。随着模型变得更完整,重要的概念被添加到模型中,但同样重要的是,当概念被证明没有用处或中心时,它们就会被丢弃。当一个不需要的概念被绑定到一个需要的概念上时,一个新的模型将本质概念区分开来,从而另一个概念可以被抛弃。
- 知识处理(Knowledge crunching)。这种语言结合了草图和头脑风暴的态度,把我们的讨论变成了模型的实验室,在那里可以练习、尝试和判断数百种实验变化。
正式这最后一点,知识处理,使我们有可能找到一个知识丰富的模型和方法来提炼它。它需要头脑风暴和大量实验的创造力。
金融分析师分析数字。他们筛选大量的详细数据,将它们组合再组合,寻找潜在的含义,寻找一种能揭示真正重要的东西的简单表述——一种可以成为财务决策基础的理解。
有效的领域建模人员是知识处理者。他们获取大量的信息,并寻找相关的细流。他们尝试一个又一个有组织的想法,寻找对大众有意义的简单观点。许多模型都经过了尝试、拒绝或改造。成功来自于一系列新兴的抽象概念,这些概念能够理解所有的细节。这种蒸馏(distillation)是对已发现的最相关的特定知识的严格表达。
知识处理不是一项单独的活动。由开发人员和领域专家组成的团队协作,通常由开发人员领导。他们一起吸收信息并将其转化为有用的形式。这些原始资料来自领域专家的头脑,来自现有系统的用户,来自技术团队使用相关遗留系统或同一领域的另一个项目的先前经验。它根据为项目编写的文档或在业务中使用的文档形式出现,以及大量的讨论。早期的版本或原型将经验反馈给团队,并改变早期的解释。
在旧的瀑布方法(waterfall method)中,业务专家与分析人员交谈,分析人员对结果进行消化和抽象,并将结果传递给编写软件的程序员。这种方法之所以失败是因为它完全缺乏反馈。分析师完全有责任仅基于业务专家的输入来创建模型。他们没有机会从程序员那里学习或获得早期版本的经验。知识只向一个方向流淌,而不会累积。
其他项目有迭代,但没有积累知识,因为它们没有抽象。他们让专家来描述他们想要的功能,然后他们去构建它。他们将结果展示给专家,并询问他们下一步要做什么。如果程序员实践了重构,他们可以保持软件足够干净,以便继续扩展它,但如果程序员对领域不感兴趣,他们只了解应用程序应该做什么,而不是它背后的原则。有用的软件可以通过这种方式构建,但项目永远不会获得那种强大的新特性作为旧特性的必然结果展现出来的那种影响力。
优秀的程序员自然会开始抽象和开发可以做更多工作的模型。但是,如果这种情况只发生在技术环境中,而不与领域专家合作,概念都是天真的(naïve)。这种浅薄的知识使得软件只能完成基本的工作,但却与领域专家的思维方式缺乏深层次的联系。
团队成员之间的交互随着所有成员一起处理模型而改变。领域模型的不断细化破势开发人员学习他们所协助的业务的重要原则,而不是机械地生成功能。领域专家经常通过被迫将他们知道的提炼为要点来精炼他们自己的理解,并且他们开始了解概念严格的软件项目所需的要求。
所有这些都使团队成为更有能力的知识处理者(knowledge crunchers)。他们剔除无关的东西。他们将模型重新塑造成一种更加有用的形式。因为分析人员和程序员对它进行了输入,所以它被清晰地组织和抽象,并且可以为实现提供杠杆作用。因为领域专家正在向它提供信息,所以它反应了对业务的深刻认识,而那些抽象是真正的业务原则。
随着模型的改进,它将成为组织信息的工具,这些信息将继续在项目中流动。它侧重于需求分析。他与编程和设计密切相关。并且,有一个良性循环中,它加深了团队成员对领域的洞察,让他们更清楚地看到,并导致模型的进一步细化。这些模型从来都不是完美的。他们在不断发展。它们在理解领域时必须是实用的且有用的。它们必须足够严格,以使应用程序易于实现和理解。
Continuous Learning
*当我们开始编写软件时,我们知道的永远不够。*关于项目的知识是支离破碎的,分散在许多人和文档中,并与其他信息混合在一起,所以我们甚至不知道哪些知识是我们真正需要的。看起来技术上不那么令人生畏的领域可能是具有欺骗性的——我们没有意识到有多少我们不知道。这导致我们做出错误的假设。
与此同时,所有的项目都会泄露知识。学会了一些东西的人会继续前进。重组又使团队分散,知识分散。关键的子系统是以这样一种方式外包的:交付的是代码而不是知识。在典型的设计方法中,代码和文档不能表达辛辛苦苦获得的知识,因此当口头传统因任何原因被打断时,这些知识就会丢失。
高效的团队有意识地增长他们的知识,实践“持续学习(continuous learning)”[Kerievsky 2001]。对于开发人员来说,这意味着提高技术知识,以及通用的领域建模技能(如本书中的技能)。但这样包括认真学习他们所从事的特定领域。这些自学成才的团队成员组成了一个稳定的核心团队,专注于涉及最关键领域的开发任务(参见第 15 章,“Distillation”)。
知识在核心团队的头脑中积累。
此时,停下来问自己一个问题。你学过 PCB 设计流程吗?尽管这只是对该主题的一个肤浅处理,但是在讨论领域模型时应该有一些学习。我学到了很多。我们学习如何成为一名 PCB 工程师。这不是我们的目标。我学会了与 PCB 专家交谈,理解与应用程序相关的主要该你那,并检查我们正在构建的内容。
事实上,我们发现探测模拟在开发中处于低优先级,最终被放弃了。与此同时,该模型的部分内容也随之小时,这些内容包括通过组件传递信号和计算跳数(counting hops)。应用程序的核心在别处,而模型的改变将这些方面置于中心位置。领域专家了解了更多信息,并明确了应用程序的目标。(第 15 章,“Distillation” 将深入讨论这些问题。)
尽管如此,早起的工作还是必不可少的。重要的模型元素被保留,但更重要的是,它启动了使所有后续工作有效的过程:由团队成员、开发成员和领域专家获得的知识,共享语言的开始,以及通过实现结束反馈循环。探索之旅总要从某个地方开始。
Knowledge Rich Design
在这样的模型中所不找到的知识超越了“找到名词(find the nouns)”。业务活动和规则与涉及的实体一样,都是领域的中心,任何领域都有各种类别的概念。知识处理产生了反应这种洞察力的模型。在模型更改的同时,开发人员重构实现以表达模型,并向应用程序提供该知识的使用。
随着这种超越实体和价值的移动,知识处理会变得更加激烈,因为业务规则之间可能存在实际的不一致。领域专家通常没有意识到他们的心理过程有多复杂,因为在他们工作的过程中,他们浏览所有这些规则,调和矛盾,用常识填补空白。软件做不到这一点。正式通过与软件专家密切合作的知识计算,规则才得以澄清、充实、协调或置于范围之外。
Example : Extracting a Hidden Concept
让我们从一个非常简单的领域模型开始,该模型将用作预定货物到船舶航行的应用程序的基础。
我们可以指出,预定应用程序的职责是将每一个 Cargo 与一个 Voyage 关联起来,并记录和跟踪这种关系。到目前为止一切都好。在应用程序代码的某个地方可能有这样一个方法:
public int makeBooking(Cargo cargo, Voyage voyage) { int confirmation = orderConfirmationSequence.next(); voyage.addCargo(cargo, confirmation);
return confirmation;
}
航运业的标准做饭是接受比一艘船在一次航程中所能装载的更多的货物。这就是所谓的“超额预定(over-booking)”。有时使用一个简单的容量百分比,例如预定容量的 110%。在其他情况下,则适用复杂的规则,有利于主要客户或某些种类的货物。
这是航运领域的一个基本规则,航运业的任何业务人员都知道它,但可能不是软件团队中的所有技术人员都能理解它。
需求文档包含这一行: Allow 10% overbooking.
类图和代码现在看起来像这样:
public int makeBooking(Cargo cargo, Voyage voyage) {
double maxBooking = voyage.capacity() * 1.1;
if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking) return –1;
int confirmation = orderConfirmationSequence.next(); voyage.addCargo(cargo, confirmation);
return confirmation;
}
现在,一个重要的业务规则作为一个保护子句隐藏在应用程序的方法中。稍后我们将看分层架构原则(LAYERED ARCHITECTURE)(第四章),它将指导我们将朝顶规则重构为领域对象,但现在让我们集中于如何使这些知识更明确,并且对项目中的每个人都更容易访问。