Please enable JavaScript.
Coggle requires JavaScript to display documents.
领域驱动设计 笔记 - Coggle Diagram
领域驱动设计 笔记
有效建模的要素
-
-
-
提炼模型。在模型日趋完整的过程中,重要的概念不断被添加到模型中,但同样重要的 是,不再使用的或不重要的概念则从模型中被移除。当一个不需要的概念与一个需要的概念有关 联时,则把重要的概念提取到一个新模型中,其他那些不要的概念就可以丢弃了。
-
持续学习
当开始编写软件时,其实我们所知甚少。项目知识零散地分散在很多人和文档中,其中夹杂 着其他一些无关信息,因此我们甚至不知道哪些知识是真正需要的知识。看起来没什么技术难度 的领域很可能是一种错觉——我们并没意识到不知道的东西究竟有多少。这种无知往往会导致我 们做出错误的假设。
高效率的团队需要有意识地积累知识,并持续学习[Kerievsky 2003]。对于开发人员来说,这 意味着既要完善技术知识,也要培养一般的领域建模技巧(如本书中所讲的那些技巧)。但这也 包括认真学习他们正在从事的特定领域的知识。
-
地图就是模型,而模型被用来描绘人们所关注的现实或想法的某个方面。 模型是一种简化。它是对现实的解释——把与解决问题密切相关的方面抽象出来,而忽略无 关的细节。
模型正是解决此类 信息超载问题的工具。模型这种知识形式对知识进行了选择性的简化和有意的结构化。适当的模 型可以使人理解信息的意义,并专注于问题。
领域建模并不是要尽可能建立一个符合“现实”的模型。即使是对具体、真实世界中的事 物进行建模,所得到的模型也不过是对事物的一种模拟。它也不单单是为了实现某种目的而构 造出来的软件机制。建模更像是制作电影——出于某种目的而概括地反映现实。
高效的领域建模人员是知识的消化者。他们在大量信息中探寻有用的部分。他们不断尝试各 种信息组织方式,努力寻找对大量信息有意义的简单视图。很多模型在尝试后被放弃或改造。只 有找到一组适用于所有细节的抽象概念后,工作才算成功。
有些项目使用了迭代过程,但由于没有对知识进行抽象而无法建立起知识体系。开发人员听 专家们描述某项所需的特性,然后开始构建它。他们将结果展示给专家,并询问接下来做什么。 如果程序员愿意进行重构,则能够保持软件足够整洁,以便继续扩展它;但如果程序员对领域不 感兴趣,则他们只会了解程序应该执行的功能,而不去了解它背后的原理。虽然这样也能开发出 可用的软件,但项目永远也不会从原有特性中自然地扩展出强大的新特性。
领域模型的不 断精化迫使开发人员学习重要的业务原理,而不是机械地进行功能开发。领域专家被迫提炼自己 已知道的重要知识的过程往往也是完善其自身理解的过程,而且他们会渐渐理解软件项目所必需 的概念严谨性。 所有这些因素都促使团队成员成为更合格的知识消化者。他们对知识去粗取精。他们将模 型重塑为更有用的形式。由于分析员和程序员将自己的知识输入到了模型中,因此模型的组织 更严密,抽象也更为整洁,从而为实现提供了更大支持。同时,由于领域专家也将他们的知识 输入到了模型中,因此模型反映了业务的深层次知识,而且真正的业务原则得以抽象。 模型在不断改进的同时,也成为组织项目信息流的工具。模型聚焦于需求分析。它与编程和 设计紧密交互。它通过良性循环加深团队成员对领域的理解,使他们更透彻地理解模型,并对其 进一步精化。模型永远都不会是完美的,因为它是一个不断演化完善的过程。模型对理解领域必 须是切实可用的。它们必须非常精确,以便使应用程序易于实现和理解。
-
有用的模型很少停留在表面。随着对领域和应用程序需求的理解逐步加深,我们往往会丢弃 那些最初看起来很重要的表面元素,或者切换它们的角度。这时,一些开始时不可能发现的巧妙 抽象就会渐渐浮出水面,而它们恰恰切中问题的要害。
知识消化是一种探索,它永无止境
项目需要一种公共语言,这种语言 要比所有语言的最小公分母健壮得多。通过团队的一致努力,领域模型可以成为这种公共语言的 核心,同时将团队沟通与软件实现紧密联系到一起。该语言将存在于团队工作中的方方面面。
模型之间的关系成为所有语言都具有的组合规则。词和短语的意义反映了模型的语义。 开发人员应该使用基于模型的语言来描述系统中的工件、任务和功能。这个模型应该为开发 人员和领域专家提供一种用于相互交流的语言,而且领域专家还应该使用这种语言来讨论需求、 开发计划和特性。语言使用得越普遍,理解进行得就越顺畅。
通过大量使用基于模型的语言,并且不达流畅绝不罢休,我们可 以逐步得到一个完整的、易于理解的模型,它由简单元素组成,并通过组合这些简单元素表达 复杂的概念。 因此: 将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画 图、写东西,特别是讲话时也要使用这种语言。 通过尝试不同的表示方法(它们反映了备选模型)来消除难点。然后重构代码,重新命名类、 方法和模块,以便与新模型保持一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一 致的理解一样。 要认识到,UBIQUITOUS LANGUAGE的更改就是对模型的更改。 领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那 些将会妨碍设计的有歧义和不一致的地方。
使用单词和短语是极为重要的——其将我们的语言能力用于建模工作,这就如同素描对于表 现视觉和空间推理十分重要一样。我们即要利用系统性分析和设计方面的分析能力,也要利用对 代码的神秘“感觉”。这些思考方式互为补充,要充分利用它们来找到有用的模型和设计。在所 有这些方式中,语言上的试验常常是最容易被忽视的
如果连经验丰富的领域专家都不能理解模型,那么模型一定出了什么问题。
最初,当用户讨论系统尚未建模的未来功能时,他们没有模型可供使用。但当他们开始与开 发人员一起仔细讨论这些新想法时,探索共享模型的过程就开始了。最初的模型可能很笨拙且不 完整,但会逐渐精化。随着新语言的演进,领域专家必须付出更多努力来适应它,并更新那些仍 然很重要的旧文档。
当领域专家使用这种语言互相讨论,或者与开发人员进行讨论时,很快就会发现模型中哪些
地方不符合他们的需要,甚至是错误的。另一方面,模型语言的精确性也会促使领域专家发现他们想法中的矛盾和含糊之处。
-
-
如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的, 软件的正确性也值得怀疑。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在 实际项目中,当设计改变时也无法维护这种关系。若分析与和设计之间产生严重分歧,那么在分 析和设计活动中所获得的知识就无法彼此共享。 分析工作一定要抓住领域内的基础概念,并且用易于理解和易于表达的方式描述出来。设计 工作则需要指定一套可以由项目中使用的编程工具创建的组件,使项目可以在目标部署环境中高 效运行,并且能够正确解决应用程序所遇到的问题。
软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这二者之间的明确对应关 系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更 深层次的领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮 的UBIQUITOUS LANGUAGE(通用语言)。
如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用程序服务, 那么这个模型就和程序没有任何关联。如果开发人员没有意识到改变代码就意味着改变模型,那 么他们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。同样,如果建模人员不 参与到程序实现的过程中,那么对程序实现的约束就没有切身的感受,即使有,也会很快忘记。 MODEL-DRIVEN DESIGN的两个基本要素(即模型要支持有效的实现并抽象出关键的领域知识)已 经失去了一个,最终模型将变得不再实用。最后一点,如果分工阻断了设计人员与开发人员之间 的协作,使他们无法转达实现MODEL-DRIVEN DESIGN的种种细节,那么经验丰富的设计人员则不 能将自己的知识和技术传递给开发人员。
-
MODEL-DRIVEN DESIGN利用模型来为应用程序解决问题。项目组通过知识消化将大量杂乱无 章的信息提炼成实用的模型。而MODEL-DRIVEN DESIGN将模型和程序实现过程紧密结合。 UBIQUITOUS LANGUAGE则成为开发人员、领域专家和软件产品之间传递信息的渠道。
开发一个好的领域模型是一门艺术。而模型中各个元素的实际设计和实现则相对系统化。将 领域设计与软件系统中的其他关注点分离会使设计与模型之间的关系非常清晰。根据不同的特征 来定义模型元素则会使元素的意义更加鲜明。对每个元素使用已验证的模式有助于创建出更易于 实现的模型。
-
-
给复杂的应用程序划分层次。在每一层内分别进行设计,使其具有内聚性并且只依赖于 它的下层。采用标准的架构模式,只与上层进行松散的耦合。将所有与领域模型相关的代码 放在一个层中,并把它与用户界面层、应用层以及基础设施层的代码分开。领域对象应该将 重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务 等内容。这使得模型的含义足够丰富,结构足够清晰,可以捕捉到基本的业务知识,并有效 地使用这些知识。
建立一种可以表达领域模型的实现并且用它来解决重 要问题。不妄求万全之策,只要有选择性地运用框架来解决 难点问题,就可以避开框架的很多不足之处。明智而审慎地选择框架中最具价值的功能能够减少 程序实现和框架之间的耦合,使随后的设计决策更加灵活。更重要的是,现在许多框架的用法都 极其复杂,这种简化方式有助于保持业务对象的可读性,使其更富有表达力。我们必须要保持克制,不要总是想着要寻找框架,因为精细的框架也可能会束缚住程序开发人员。
-
当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象 的定义。使类定义变得简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方 式,这种方式应该与其形式和历史无关。要格外注意那些需要通过属性来匹配对象的需求。在定 义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的 符号来实现。这种定义标识的方法可能来自外部,也可能是由系统创建的任意标识符,但它在模 型中必须是唯一的标识。模型必须定义出“符合什么条件才算是相同的事物”。
-
-
-
REPOSITORY将某种类型的所有对象表示为一个概念集合(通常是模拟的)。它的行为类似于 集合(collection),只是具有更复杂的查询功能。在添加或删除相应类型的对象时,REPOSITORY 的后台机制负责将对象添加到数据库中,或从数据库中删除对象。这个定义将一组紧密相关的职 责集中在一起,这些职责提供了对AGGREGATE根的整个生命周期的全程访问。
-
-
开发出实用的模型:(1) 复杂巧妙的领域模型是可以实现的,也是值得我们去花费力气实现的。 (2) 这样的模型离开不断的重构是很难开发出来的,重构需要领域专家和热爱学习领域知识 的开发人员密切参与进来。 (3) 要实现并有效地运用模型,需要精通设计技巧。
重构就是在不改变软件功能的前提下重新设计它。开发人员无需在着手开发之前做出详细的 设计决策,只需要在开发过程中不断小幅调整设计即可,这不但能够保证软件原有的功能不变, 还可使整个设计更加灵活易懂。自动化的单元测试套件能够保证对代码进行相对安全的试验。
-
如果每次对模型和代码所进行的修改都能反映出对领域的新理解,那么通过不断 的重构就能给系统最需要修改的地方增添灵活性,并找到简单快捷的方式来实现普通的功能。用这种方式来进行建模和设计时,虽然需要反复尝试、不断改正错误,但是对模型和设计的修改 却因此而更容易实现,同时反复的修改也能让我们越来越接近柔性设计。
-
重构的投入与回报并非呈线性关系。通常,小的调整会带来小的回报,小的改进也会积 少成多。小改进可防止系统退化,成为避免模型变得陈腐的第一道防线。但是,有些 最重要的理解也会突然出现,给整个项目带来巨大的冲击。 可以确定的是,项目团队会积累、消化知识,并将其转化成模型。微小的重构可能每次只涉 及一个对象,在这里加上一个关联,在那里转移一项职责。然而,一系列微小的重构会逐渐汇聚 成深层模型。 一般来说,持续重构让事物逐步变得有序。代码和模型的每一次精化都让开发人员有了更加 清晰的认识。这使得理解上的突破成为可能。之后,一系列快速的改变得到了更符合用户需要并 更加切合实际的模型。其功能性及说明性急速增强,而复杂性却随之消失。 这种突破不是某种技巧,而是一个事件。它的困难之处在于你需要判断发生了什么,然后再 决定如何处理。
当突破带来更深层的模型时,通常会令人感到不安。与大部分重构相比,这种变化的回报更 多,风险也更高。而且突破出现的时机可能很不合时宜。 尽管我们希望进展顺利,但往往事与愿违。过渡到真正的深层模型需要从根本上调整思路, 并且对设计做大幅修改。在很多项目中,建模和设计工作最重要的进展都来自于突破。
不要试图去制造突破,那只会使项目陷入困境。通常,只有在实现了许多适度的重构后才有 可能出现突破。在大部分时间里,我们都在进行微小的改进,而在这种连续的改进中模型深层含 义也会逐渐显现。 要为突破做好准备,应专注于知识消化过程,同时也要逐渐建立健壮的UBIQUITOUS LANGUAGE。寻找那些重要的领域概念,并在模型中清晰地表达出来(参见第9章)。精化模型, 使其更具柔性(参见第10章)。提炼模型(参见第15章)。利用这些更容易掌握的手段使模型变得 更清晰,这通常会带来突破。 不要犹豫着不去做小的改进,这些改进即使脱离不开常规的概念框架,也可以逐渐加深我们 对模型理解。不要因为好高骛远而使项目陷入困境。只要随时注意可能出现的机会就够了。
-
深层建模听起来很不错,但是我们要如何实现它呢?深层模型之所以强大是因为它包含 了领域的核心概念和抽象,能够以简单灵活的方式表达出基本的用户活动、问题以及 解决方案。深层建模的第一步就是要设法在模型中表达出领域的基本概念。随后,在不断消化知 识和重构的过程中,实现模型的精化。但是实际上这个过程是从我们识别出某个重要概念并且在 模型和设计中把它显式地表达出来的那个时刻开始的。 若开发人员识别出设计中隐含的某个概念或是在讨论中受到启发而发现一个概念时,就会对 领域模型和相应的代码进行许多转换,在模型中加入一个或多个对象或关系,从而将此概念显式 地表达出来。 有时,这种从隐式概念到显式概念的转换可能是一次突破,使我们得到一个深层模型。但更 多的时候,突破不会马上到来,而需要我们在模型中显式表达出许多重要概念,并通过一系列重 构不断地调整对象职责、改变它们与其他对象的关系、甚至多次修改对象名称,在这之后,突破 才会姗姗而来。最后,所有事情都变得清晰了。但是要实现上述过程,必须首先识别出以某种形 式存在的隐含概念,无论这些概念有多么原始。
开发人员必须能够敏锐地捕捉到隐含概念的蛛丝马迹,但有时他们必须主动寻找线索。要挖 掘出大部分的隐含概念,需要开发人员去倾听团队语言、仔细检查设计中的不足之处以及与专家 观点相矛盾的地方、研究领域相关文献并且进行大量的实验。
倾听领域专家使用的语言。有没有一些术语能够简洁地表达出复杂的概念?他们有没有纠正 过你的用词(也许是很委婉的提醒)?当你使用某个特定词语时,他们脸上是否已经不再流露出 迷惑的表情?这些都暗示了某个概念也许可以改进模型。
你所需要的概念并不总是浮在表面上,也绝不仅仅是通过对话和文档就能让它显现出来。有 些概念可能需要你自己去挖掘和创造。要挖掘的地方就是设计中最不足的地方,也就是操作复杂 且难于解释的地方。每当有新的需求时,似乎都会让这个地方变得更加复杂。 有时,你很难意识到模型中丢失了什么概念。也许你的对象能够实现所有的功能,但是有些 职责的实现却很笨拙。而有时,你虽然能够意识到模型中丢失了某些东西,但是却无法找到解决 方案。 这个时候,你必须积极地让领域专家参与到讨论中来。如果你足够幸运,这些专家可能会愿 意一起思考各种想法,并通过模型来进行验证。如果你没那么幸运,你和你的同事就不得不自己 思索出不同的想法,让领域专家对这些想法进行判断,并注意观察专家的表情是认同还是反对。
由于经验和需求的不同,不同的领域专家对同样的事情会有不同的看法。即使是同一个人 提供的信息,仔细分析后也会发现逻辑上不一致的地方。在挖掘程序需求的时候,我们会不断 遇到这种令人烦恼的矛盾,但它们也为深层模型的实现提供了重要线索。有些矛盾只是术语说 法上的不一致,有些则是由于误解而产生的。但还有一种情况是专家们会给出相互矛盾的两种 说法。
采用同样的思 考模式通常可以帮助我们透过问题领域的表面获得更深层的理解。要解决所有矛盾是不太现实的,甚至是不需要的。然而,即使不去解决矛盾,我们也应该仔细思考对立的两种看法是如何同时应用于 同一个外部现实的,这会给我们带来启示。
-
然后找到一个看起来足够清晰且实用的概念,并在模型中尝试它。后面,随着经验的积累 和知识的消化,我们会有更好的想法,最终,这个概念至少会被替换一次。因此,建模人员/设 计人员绝对不能固执己见。 并不是所有这些方向性的改变都毫无用处。每次改变都会把开发人员更深刻的理解添加到模 型中。每次重构都使设计变得更灵活并且为那些可能需要修改的地方做好准备。
对象是用来封装过程的,这样我 们只需考虑对象的业务目的或意图就可以了。过程是应该被显式表达出来,还是应该被隐藏起来呢?区分的方法很简单:它是经常被领域 专家提起呢,还是仅仅被当作计算机程序机制的一部分?
-
为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必 须要让人们乐于使用,而且易于做出修改。这就是柔性设计(supple design)。通过迭代循环,我们可以把这些原料打造成有用的形式:建立的模型能够简单而清 晰地捕获主要关注点;其设计可以让客户开发人员真正使用这个模型。在设计和代码的开发过程 中,我们将获得新的理解,并通过这些理解改善模型概念。我们一次又一次回到迭代循环中,通 过重构得到更深刻的理解。但我们究竟要获得什么样的设计呢?在这个过程中应该进行哪些实 验?早期的设计版本通常达不到柔性设计的要求。由于项目的时间期限和预算的缘故,很多设计 一直就是僵化的。我也从未见过有哪个大型程序自始至终都是柔性的。但是,当复杂性阻碍了项 目的前进时,就需要仔细修改最关键、最复杂的地方,使之变成一个柔性设计,这样才能突破复 杂性带给我们的限制,而不会陷入遗留代码维护的麻烦中。
如果开发人员为了使用一个组件而必须要去研究它的实现,那么就失去了封装的价值。当某 个人开发的对象或操作被别人使用时,如果使用这个组件的新的开发者不得不根据其实现来推测 其用途,那么他推测出来的可能并不是那个操作或类的主要用途。如果这不是那个组件的用途, 虽然代码暂时可以工作,但设计的概念基础已经被误用了,两位开发人员的意图也是背道而驰。
在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。 这样可以使客户开发人员不必去理解内部细节。这些名称应该与UBIQUITOUS LANGUAGE保持一 致,以便团队成员可以迅速推断出它们的意义。在创建一个行为之前先为它编写一个测试,这样 可以促使你站在客户开发人员的角度上来思考它。
尽可能把程序的逻辑放到函数中,因为函数是只返回结果而不产生明显副作用的操作。严格 地把命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。当发现 了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUE OBJECT中,这 样可以进一步控制副作用。
使用这些ENTITY的人必须了解使用这些命令的后果。在这种情况下,使用 ASSERTION(断言)可以把副作用明确地表示出来,使它们更易于处理。如果操作的副作用仅仅是由它们的实现隐式定义的,那么在一个具有大量相互调用关系的系 统中,起因和结果会变得一团糟。理解程序的唯一方式就是沿着分支路径来跟踪程序的执行。封 装完全失去了价值。跟踪具体的执行也使抽象失去了意义。所有这些断言都描述了状态,而不是过程,因此它们更易于分析。
把操作的后置条件和类及AGGREGATE的固定规则表述清楚。如果在你的编程语言中不能直 接编写ASSERTION,那么就把它们编写成自动的单元测试。还可以把它们写到文档或图中(如果 符合项目开发风格的话)。 寻找在概念上内聚的模型,以便使开发人员更容易推断出预期的ASSERTION,从而加快学习 过程并避免代码矛盾。
从单个方法的设计,到类和MODULE的设计,再到大型结构的设计(参见第16章),高内聚 低耦合这一对基本原则都起着重要的作用。这两条原则既适用于代码,也适用于概念。为了避免 机械化地遵循它,我们必须经常根据我们对领域的直观认识来调整技术思路。在做每个决定时, 都要问自己:“这是根据当前模型和代码中的特定关系做出的权宜之计呢,还是反映了底层领域 的某种轮廓?” 寻找在概念上有意义的功能单元,这样可以使得设计既灵活又易懂。
把设计元素(操作、接口、类和AGGREGATE)分解为内聚的单元,在这个过程中,你对领 域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规 律性,并寻找能够解释这些变化模式的底层CONCEPTUAL CONTOUR。使模型与领域中那些一致的 方面(正是这些方面使得领域成为一个有用的知识体系)相匹配。
把设计元素(操作、接口、类和AGGREGATE)分解为内聚的单元,在这个过程中,你对领 域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规 律性,并寻找能够解释这些变化模式的底层CONCEPTUAL CONTOUR。使模型与领域中那些一致的 方面(正是这些方面使得领域成为一个有用的知识体系)相匹配。
当然,每个关联都是一种依赖,要想理解一个类,必须理解它与哪些对象有联系。与这个类 有联系的其他对象还会与更多的对象发生联系,而这些联系也是必须要弄清楚的。每个方法的每 个参数的类型也是一个依赖,每个返回值也都是一个依赖。
-
低耦合是对象设计的一个基本要素。尽一切可能保持低耦合。把其他所有无关概念提取到对 象之外。这样类就变得完全独立了,这就使得我们可以单独地研究和理解它。每个这样的独立类 都极大地减轻了因理解MODULE而带来的负担。 当一个类与它所在的模块中的其他类存在依赖关系时,比它与模块外部的类有依赖关系要好 得多。同样,当两个对象具有自然的紧密耦合关系时,这两个对象共同涉及的多个操作实际上能 够把它们的关系本质明确地表示出来。我们的目标不是消除所有依赖,而是消除所有不重要的依 赖。当无法消除所有的依赖关系时,每清除一个依赖对开发人员而言都是一种解脱,使他们能够集中精力处理剩下的概念依赖关系。
-
它们用于澄清代码意图,使得使用代码的影响变得显而易见,并且
解除模型元素的耦合。重点突击某个部分,使设计的一个部分真正变得灵活起来,这比分散精力泛泛地处理整个系
统要有用得多。
但更常见的情况是,可以对你的领域或其他领域中那些
建立已久的概念系统加以修改和利用,其中有些系统已经被精化和提炼达几个世纪之久。
一位经验丰富的开发人员在研究领域问题时,如果发现了他所熟悉的某种职责或某个关系 网,他会想起以前这个问题是如何解决的。以前尝试过哪些模型?哪些是有效的?在实现中有哪 些难题?它们是如何解决的?先前经历过的尝试和失败会突然间与新的情况联系起来。
分析模式的最大作用是借鉴其他项目的经验,把那些项目中有关设计方向 和实现结果的广泛讨论与当前模型的理解结合起来。脱离具体的上下文来讨论模型思想不但难 以落地,而且还会造成分析与设计严重脱节的风险,而这一点正是MODEL-DRIVEN DESIGN坚决 反对的。
当在领域中应用任何一种设计模式时,首先关注的问题应该是模式的意图是否确实适合领域 概念。以递归的方式遍历一些相互关联对象确实比较方便,但它们是否真的存在整体—部分层次结构?你是否发现可以通过某种抽象方式把所有部分都归到同一概念类型中?如果你确实发现 了这种抽象方式,那么使用COMPOSITE可以令模型的这些部分变得更清晰,同时使你能够借助设 计模式所提供的那些经过深思熟虑的设计及实现的考量。
通过重构得到更深层的理解是一个涉及很多方面的过程。我们有必要暂停一下,把一些 要点归纳到一起。有三件事情是必须要关注的: (1) 以领域为本; (2) 用一种不同的方式来看待事物; (3) 始终坚持与领域专家对话。
与传统重构观点不同的是,即使在代码看上去很整洁的时候也可能需要重构,原因是模型的 语言没有与领域专家保持一致,或者新需求不能被自然地添加到模型中。重构的原因也可能来自 学习:当开发人员通过学习获得了更深刻的理解,从而发现了一个得到更清晰或更有用的模型的 机会。
不管问题的根源是什么,下一步都是要找到一种能够使模型表达变得更清楚和更自然的改进方案。这可能只需要做一些简单、明显的修改,只需几小时即可完成。在这种情况下,所做的修 改类似于传统重构。但寻找新模型可能需要更多时间,而且需要更多人参与。
-
当发生 以下情况时,就应该进行重构了: 设计没有表达出团队对领域的最新理解; 重要的概念被隐藏在设计中了(而且你已经发现了把它们呈现出来的方法);发现了一个能令某个重要的设计部分变得更灵活的机会。
传统意义上的重构听起来是一个非常稳定的过程。但通过重构得到更深层理解往往不是这样 的。在对模型进行一段时间稳定的改进后,你可能突然有所顿悟,而这会改变模型中的一切。这 些突破不会每天都发生,然而很大一部分深层模型和柔性设计都来自这些突破。 这样的情况往往看起来不像是机遇,而更像危机。例如,你突然发现模型中有一些明显的缺 陷,在表达方面显示出一个很大的漏洞,或存在一些没有表达清楚的关键区域。或者有些描述是 完全错误的。 这些都表明团队对模型的理解已经达到了一个新的水平。他们现在站在更高的层次上发现了 原有模型的弱点。他们可以从这种角度构思一个更好的模型。 通过重构得到更深层理解是一个持续不断的过程。人们发现一些隐含的概念,并把它们明确 地表示出来。有些设计部分变得更具有柔性,或许还采用了声明式的风格。开发工作一下子到了 突破的边缘,然后开发人员跨越这条界线,得到了一个更深层的模型,接下来又重新开始了稳步 的改进过程。
-
他们的意图是至少共享其所做的 一部分工作,但却没有界限告诉他们共享了什么、没有共享什么。而且他们也没有一个过程来维 持共享模型,或快速检测模型是否有分歧。他们只是在系统行为突然变得不可预测时才意识到他 们之间产生了分歧。
任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现 bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应 该在哪个上下文中使用。 模型混乱的问题最终会在代码不能正常运行时暴露出来,但问题的根源却在于团队的组织方 式和成员的交流方法。模型混乱的问题最终会在代码不能正常运行时暴露出来,但问题的根源却在于团队的组织方 式和成员的交流方法。为了解决多个模型的问题,我们需要明确地定义模型的范围——模型的范围是软件系统中一 个有界的部分,这部分只应用一个模型,并尽可能使其保持统一。团队组织中必须一致遵守这个 定义。明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表 现(代码和数据库模式等)来设臵模型的边界。在这些边界中严格保持模型的一致性,而不要受 到边界之外问题的干扰和混淆。
因此,通过定义这个BOUNDED CONTEXT,最终得到了什么?对CONTEXT内的团队而言:清晰!。 这两支团队知道他们必须与这个模型保持一致。他们根据这一点制定设计决策,并注意防范出现 不一致的情况。而CONTEXT之外的团队获得了:自由。他们不必行走在灰色地带,不必使用同一 个模型,虽然他们还是总觉得应该使用同一个模型。
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指 两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生变化时,都 必须更新两个地方。每次由于新知识导致一个对象被修改时,必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一概念的两个版本,它们遵守不同的规则,甚至有 不同的数据。更严重的是,团队成员必须学习做同一件事情的两种方法,以及保持这两种方法同 步的各种方式。 假同源可能稍微少见一点,但它潜在的危害更大。它是指使用相同术语(或已实现的对象) 的两个人认为他们是在谈论同一件事情,但实际上并不是这样。
ADAPTER是一个包装器,它允许客户使用另外一种协议,这种协议可以是行为实现者不理 解的协议。当客户向适配器发送一条消息时,ADAPTER把消息转换为一条在语义上等同的消息, 并将其发送给“被适配者”(adaptee)。之后ADAPTER对响应消息进行转换,并将其发回。
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容, 而这种形式将使它更有价值,也更有用。模型就是知识的精炼。通过每次重构所得到的更深层的 理解,我们得以把关键的领域知识和优先级提取出来。精炼的主要动机是把最有价值的那部分提取出来。
领域模型的战略精炼包括以下部分: (1) 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作; (2) 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通; (3) 指导重构; (4) 专注于模型中最有价值的那部分; (5) 指导外包、现成组件的使用以及任务委派。
一个严峻的现实是我们不可能对所有设计部分进行同等的精化,而是必须分出优先级。为了 使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建 应用程序的功能。但本来就稀缺的高水平开发人员往往会把工作重点放在技术基础设施上,或者 只是去解决那些不需要专门领域知识就能理解的领域问题(这些问题都已经有了很好的定义)。
-
-
控制这些风险的最好方法是保持绝对的精简。剔除那些不重要的细节,只关注核心抽象以及 它们的交互,这样文档的老化速度就会减慢,因为这个层次的模型通常更稳定。 精炼文档应该能够被团队中的非技术人员理解。把它当作一个共享的视图,描述每个人都应 该知道的东西,而且可以把它作为团队所有成员研究模型和代码的一个起点。
这样就把“做什么”和“如何做”分开了。计算有时会非常复杂,使设计开始变得膨胀。机械性的“如何做”大量增加,把概念性的“做 什么”完全掩盖了。为解决问题提供算法的大量方法掩盖了那些用于表达问题的方法。把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注 意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功 能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么)了,而把解决方案的复杂 细节(如何做)转移给了框架。
-
对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来, 并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取到其 他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开。
把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型, 使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中, 而专用的、详细的实现类则留在由子领域定义的MODULE中。对ABSTRACT CORE 进行建模需要深入理解关键概念以及它们在系统的主要交互中扮演的角色。换言之,它是通过重 构得到更深层理解的。而且它通常需要大量的重新设计。
通过持续重构得到更深层的理解,从而向深层模型和柔性设计推 进。精炼的目标是把模型设计得更明显,使我们可以用模型简单地把领域表示出来。深层模型把 领域中最本质的方面精炼成一些简单的元素,使我们可以把这些元素组合起来解决应用程序中的 重要问题。 尽管任何带来深层模型的突破都有价值,但只有CORE DOMAIN中的突破才能改变整个项目的 轨道。
控制成本的一个关键是保持一种简单、轻量级的结构。不要试图使结构面面俱到。只需解决 最主要的问题即可,其他问题可以留到后面一个一个地解决。一种最小化的松散结构可以起到轻量级的指导作用,它有助于避免混乱。
-
-
-
-