《领域驱动设计-软件核心复杂性应对之道》阅读笔记(四)

第四部分 战略设计

第14章 保持模型的完整性

在这里插入图片描述

14.1 模式:BOUNDED CONTEXT

任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现bug、变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。
模型混乱的问题最终会在代码不能正常运行时暴露出来,但问题的根源却在于团队的组织方式和成员的交流方法。因此,为了澄清模型的上下文,我们既要注意项目,也要注意它的最终产品(代码、数据库模式等)。
一个模型只在一个上下文中使用。这个上下文可以是代码的一个特定部分,也可以是某个特定团队的工作。如果模型是在一次头脑风暴会议中得到的,那么这个模型的上下文可能仅限于那次讨论。
因此:
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
BOUNDED CONTEXT明确地限定了模型的应用范围。
在CONTEXT中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。在其他CONTEXT中,会使用其他模型,这些模型具有不同的术语、概念、规则和UBIQUITOUS LANGUAGE的技术行话。通过划定明确的边界,可以使模型保持纯粹,因而在它所适用的CONTEXT中更有效。
因此,通过定义这个BOUNDED CONTEXT,最终得到了什么?对CONTEXT内的团队而言:清晰!。 而CONTEXT之外的团队获得了:自由。他们不必行走在灰色地带,不必使用同一个模型,虽然他们还是总觉得应该使用同一个模型。

识别BOUNDED CONTEXT中的不一致
很多征兆都可能表明模型中出现了差异。最明显的是已编码的接口不匹配。对于更微妙的情况,一些意外行为也可能是一种信号。采用了自动测试的CONTINUOUS INTEGRATION可以帮助捕捉到这类问题。但语言上的混乱往往是一种早期的警告信号。
将不同模型的元素组合到一起可能会引发两类问题:重复的概念和假同源。重复的概念是指两个模型元素(以及伴随的实现)实际上表示同一个概念。每当这个概念的信息发生变化时,都必须更新两个地方。每次由于新知识导致一个对象被修改时,必须重新分析和修改另一个对象。如果不进行实际的重新分析,结果就会出现同一概念的两个版本,它们遵守不同的规则,甚至有不同的数据。更严重的是,团队成员必须学习做同一件事情的两种方法,以及保持这两种方法同步的各种方式。
假同源可能稍微少见一点,但它潜在的危害更大。它是指使用相同术语(或已实现的对象)的两个人认为他们是在谈论同一件事情,但实际上并不是这样。假同源会导致开发团队互相干扰对方的代码,也可能导致数据库中含有奇怪的矛盾,还会引起团队沟通的混淆。

14.2 模式:CONTINUOUS INTEGRATION

CONTINUOUS INTEGRATION是指把一个上下文中的所有工作足够频繁地合并到一起,并使它们保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题。像领域驱动设计中的其他方法一样,CONTINUOUS INTEGRATION也有两个级别的操作:(1) 模型概念的集成;(2) 实现的集成。
团队成员之间通过经常沟通来保证概念的集成。团队必须对不断变化的模型形成一个共同的理解。有很多方法可以帮助做到这一点,但最基本的方法是对UBIQUITOUS LANGUAGE多加锤炼。 同时,实际工件通过系统性的合并/构建/测试过程来集成,这样能够尽早暴露出模型的分裂问题。
建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分裂问题。严格坚持使用UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同的概念时,使所有人对模型都能达成一个共识。

14.3 模式:CONTEXT MAP

BOUNDED CONTEXT之间的代码重用是很危险的,应该避免。功能和数据的集成必须要通过转换去实现。通过定义不同上下文之间的关系,并在项目中创建一个所有模型上下文的全局视图, 可以减少混乱。
对于软件模型与设计的持续概念细分,项目经理和团队成员需要一个清晰的视图。
因此:
识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。
描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。
先将当前的情况描绘出来。以后再做改变。
CONTEXT MAP无需拘泥于任何特定的文档格式。我发现类似本章的简图在可视化和沟通上下文图方面很有帮助。有些人可能喜欢使用较多的文本描述或别的图形表示。在某些情况下,团队成员之间的讨论就足够了。需求不同,细节层次也不同。不管CONTEXT MAP采用什么形式,它必须在所有项目人员之间共享,并被他们理解。它必须为每个BOUNDED CONTEXT提供一个明确的名称,而且必须阐明联系点和它们的本质。

14.3.1 测试CONTEXT的边界

对各个BOUNDED CONTEXT的联系点的测试特别重要。这些测试有助于解决转换时所存在的一些细微问题以及弥补边界沟通上存在的不足。测试充当了有用的早期报警系统,特别是在我们必须信赖那些模型细节却又无法控制它们时,它能让我们感到放心。

14.3.2 CONTEXT MAP的组织和文档化

这里只有以下两个重点。
(1) BOUNDED CONTEXT应该有名称,以便可以讨论它们。这些名称应该被添加到团队的UBIQUITOUS LANGUAGE中。
(2) 每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的CONTEXT,或任何情况的CONTEXT。

14.4 BOUNDED CONTEXT之间的关系

略~

14.5 模式:SHARED KERNEL

当不同团队开发一些紧密相关的应用程序时,如果团队之间不进行协调,即使短时间内能够取得快速进展,但他们开发出的产品可能无法结合到一起。最后可能不得不耗费大量精力在转换层上,并且频繁地进行改动,不如一开始就使用CONTINUOUS INTEGRATION那么省心省力,同时这也造成重复工作,并且无法实现公共的UBIQUITOUS LANGUAGE所带来的好处。
因此:
从领域模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位,一个团队在没与另一个团队商量的情况下不应擅自更改它。
功能系统要经常进行集成,但集成的频率应该比团队中CONTINUOUS INTEGRATION的频率低一些。在进行这些集成的时候,两个团队都要运行测试。
SHARED KERNEL通常是CORE DOMAIN,或是一组GENERIC SUBDOMAIN(通用子领域),也可能二者兼有,它可以是两个团队都需要的任何一部分模型。使用SHARED KERNEL的目的是减少重复(并不是消除重复,因为只有在一个BOUNDED CONTEXT中才能消除重复),并使两个子系统之间的集成变得相对容易一些。

14.6 模式:CUSTOMER/SUPPLIER DEVELOPMENT TEAM

上游和下游子系统很自然地分隔到两个BOUNDED CONTEXT中。如果两个组件需要不同的技能或者不同的工具集来实现时,更需要把它们隔离到不同的上下文中。转换很容易,因为只需要进行单向转换。但两个团队的行政组织关系可能会引起问题。
如果下游团队对变更具有否决权,或请求变更的程序太复杂,那么上游团队的开发自由度就会受到限制。由于担心破坏下游系统,上游团队甚至会受到抑制。同时,由于上游团队掌握优先权,下游团队有时也会无能为力。
因此:
在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道 双方的约定和进度。
两个团队共同开发自动化验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。这些测试使上游团队在做出修改时不必担心对下游团队产生副作用。
这种模式有两个关键要素。
(1) 关系必须是客户与供应商的关系,其中客户的需求是至关重要的。由于下游团队并不是唯一的客户,因此不同客户的要求必须通过协商来平衡,但这些要求都是非常重要的。这种关系与那种经常出现的“穷亲威”关系相反,在后者的关系中,下游团队不得不乞求上游团队满足其 需求。
(2) 必须有自动测试套件,使上游团队在修改代码时不必担心破坏下游团队的工作,并使下游团队能够专注于自己的工作,而不用总是密切关注上游团队的行动。

14.7 模式:CONFORMIST

当两个开发团队具有上/下游关系时,如果上游团队没有动力来满足下游团队的需求,那么下游团队将无能为力。出于利他主义的考虑,上游开发人员可能会做出承诺,但他们可能不会履行承诺。下游团队出于良好的意愿会相信这些承诺,从而根据一些永远不会实现的特性来制定计划。下游项目只能被搁置,直到团队最终学会利用现有条件自力更生为止。下游团队不会得到根据他们的需求而量身定做的接口。
在这种情况下,有3种可能的解决途径。一种是完全放弃对上游的使用。做出这种选择时, 应进行切实地评估,绝不要假定上游会满足下游的需求。有时我们会高估这种依赖性的价值,或是低估它的成本。如果下游团队决定切断这条链,他们将走上SEPARATE WAY(各行其道)的道路。
有时,使用上游软件具有非常大的价值,因此必须保持这种依赖性(或者是行政决策规定团队不能改变这种依赖性)。在这种情况下,还有两种途径可供选择,选择哪一种取决于上游设计的质量和风格。如果上游的设计很难使用(可能是由于缺乏封装、使用了不恰当的抽象或者建模时使用了下游团队无法使用的范式),那么下游团队仍然需要开发自己的模型。他们将担负起开发转换层的全部责任,这个层可能会非常复杂。
另一方面,如果上游设计的质量不是很差,而且风格也能兼容的话,那么最好不要再开发一个独立的模型。这种情况下可以使用CONFORMIST(跟随者)模式。
因此:
通过严格遵从上游团队的模型,可以消除在BOUNDED CONTEXT之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY模式可以极大地简化集成。此外,这样还可以与供应商团队共享UBIQUITOUS LANGUAGE。供应商处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息。
CONFORMIST模式类似于SHARED KERNEL模式。在这两种模式中,都有一个重叠的区域——在这个重叠区域内模型是相同的,此外还有你的模型所扩展的部分,以及另一个模型对你没有 影响的部分。这两种模式之间的区别在于决策制定和开发过程不同。SHARED KERNEL是两个高度协调的团队之间的合作模式,而CONFORMIST模式则是应对与一个对合作不感兴趣的团队进行集成。

14.8 模式:ANTICORRUPTION LAYER

当正在构建的新系统与另一个系统的接口很大时,为了克服连接两个模型而带来的困难,新模型所表达的意图可能会被完全改变,最终导致它被修改得像是另一个系统的模型了(以一种特定的风格)。遗留系统的模型通常很弱。即使对于那些模型开发得很好的例外情况,它们可能也不符合当前项目的需要。然而,集成遗留系统仍然具有很大的价值,而且有时还是绝对必要的。
因此:
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个系统现有接口与其进行对话,而只需对那个系统作出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。
ANTICORRUPTION LAYER并不是向另一个系统发送消息的机制。相反,它是在不同的模型和协议之间转换概念对象和操作的机制。

14.8.1 设计ANTICORRUPTION LAYER的接口

ANTICORRUPTION LAYER的公共接口通常以一组SERVICE的形式出现,但偶尔也会采用ENTITY的形式。构建一个全新的层来负责两个系统之间的语义转换为我们提供了一个机会,它使我们能够重新对另一个系统的行为进行抽象,并按照与我们的模型一致的方式把服务和信息提供给我们的系统。在我们的模型中,把外部系统表示为一个单独的组件可能是没有意义的。最好是使用多个SERVICE(或偶尔使用ENTITY),其中每个SERVICE都使用我们的模型来履行一致的职责。

14.8.2 实现ANTICORRUPTION LAYER

对ANTICORRUPTION LAYER设计进行组织的一种方法是把它实现为FACADE、ADAPTER和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。
FACADE是子系统的一个可供替换的接口,它简化了客户访问,并使子系统更易于使用。由于我们非常清楚要使用另一个系统的哪些功能,因此可以创建FACADE来促进和简化对这些特性的访问,并把其他特性隐藏起来。FACADE并不改变底层系统的模型。它应该严格按照另一个系统的模型来编写。FACADE应该属于另一个系统的BOUNDED CONTEXT,它只是为了满足你的专门需要而呈现出的一个更友好的外观。
ADAPTER是一个包装器,它允许客户使用另外一种协议,这种协议可以是行为实现者不理解的协议。当客户向适配器发送一条消息时,ADAPTER把消息转换为一条在语义上等同的消息,并将其发送给“被适配者”(adaptee)。之后ADAPTER对响应消息进行转换,并将其发回。
在这里插入图片描述

14.9 模式:SEPARATE WAY

集成总是代价高昂,而有时获益却很小。
除了在团队之间进行协调所需的常见开销以外,集成还迫使我们做出一些折中。可以满足某一特定需求的简单专用模型要为能够处理所有情况的更加抽象的模型让路。或许有些完全不同的技术能够轻而易举地提供某些特性,但它却难以集成。或许某个团队很难合作,使得其他团队在尝试与之合作时找不到行之有效的方法。
在很多情况下,集成不会提供明显的收益。如果两个功能部分并不需要互相调用对方的功能, 或者这两个部分所使用的对象并不需要进行交互,或者在它们操作期间不共享数据,那么集成可能就是没有必要的(尽管可以通过一个转换层进行集成)。仅仅因为特性在用例中相关,并不一定意味着它们必须集成到一起。
采用SEPARATE WAY(各行其道)模式需要预先决定一些选项。尽管持续重构最后可以撤销任何决策,但完全隔离开发的模型是很难合并的。如果最终仍然需要集成,那么转换层将是必要的,而且可能很复杂。

14.10 模式:OPEN HOST SERVICE

当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。需要维护的东西会越来越多,而且进行修改的时候担心的事情也会越来越多。
要想设计出一个足够干净的协议,使之能够被多个团队理解和使用,是一件十分困难的事情, 因此只有当子系统的资源可以被描述为一组内聚的SERVICE并且必须进行很多集成的时候,才值得这样做。在这些情况下,它能够把维护模式和持续开发区别开。
因此:
定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。

14.11 模式:PUBLISHED LANGUAGE

与现有领域模型进行直接的转换可能不是一种好的解决方案。这些模型可能过于复杂或设计得较差。它们可能没有被很好地文档化。如果把其中一个模型作为数据交换语言,它实质上就被固定住了,而无法满足新的开发需求。
OPEN HOST SERVICE使用一个标准化的协议来支持多方集成。它使用一个领域模型来在各系统间进行交换,尽管这些系统的内部可能并不使用该模型。这里我们可以更进一步——发布这种语言,或找到一种已经公开发布的语言。我这里所说的发布仅仅是指该语言已经可以供那些对它感兴趣的群体使用,而且已经被充分文档化,兼容一些独立的解释。
把一个良好文档化的、能够表达出所需领域信息的共享语言(如XML、JSON)作为公共的通信媒介,必要时在 其他信息与该语言之间进行转换。

14.12 “大象”的统一

盲人摸象的故事,略~

14.13 选择你的模型上下文策略

在任何时候,绘制出CONTEXT MAP来反映当前状况都是很重要的。但是,一旦绘制好CONTEXT MAP之后,你很可能想要改变现状。现在,你可以开始有意识地选择CONTEXT的边界和关系。以下是一些指导原则。

14.13.1 团队决策或更高层决策

首先,团队必须决定在哪里定义BOUNDED CONTEXT,以及它们之间有什么样的关系。这些决策必须由团队做出,或者至少传达给整个团队,并且被团队里的每个人理解。按照本身价值来说,在决定是否扩展或分割BOUNDED CONTEXT时,应该权衡团队独立工作的价值以及能产生直接且丰富集成的价值,以这两种价值的成本—效益作为决策的依据。在实践中,团队之间的行政关系往往决定了系统的集成方式。由于汇报结构, 有技术优势的统一可能无法实现。管理层所要求的合并可能并不实用。你不会总能得到你想要的 东西,但你至少可以评估出这些决策的代价,并反映给管理层,以便采取相应的措施来减小代价。 从一个现实的CONTEXT MAP开始,并根据实际情况来选择改变。

14.13.2 臵身上下文中

开发软件项目时,我们首先是对自己团队正在开发的那些部分感兴趣(“设计中的系统”), 其次是对那些与我们交互的系统感兴趣。典型情况下,设计中的系统将被划分为一到两个 BOUNDED CONTEXT,开发团队的主力将在这些上下文中工作,或许还会有另外一到两个起支持作用的CONTEXT。除此之外,就是这些CONTEXT与外部系统之间的关系。这是一种简单、典型的情况,能让你对可能会遇到的情形有一些粗略的了解。
实际上,我们正是自己所处理的主要CONTEXT的一部分,这会在我们的CONTEXT MAP中反映出来。只要我们知道自己存在偏好,并且在超出该CONTEXT MAP的应用边界时能够意识到已越界, 那么就不会有什么问题。

14.13.3 转换边界

在画出BOUNDED CONTEXT的边界时,有无数种情况,也有无数种选择。但权衡时所要考虑的通常是下面所列出的某些因素。
首选较大的BOUNDED CONTEXT
 当用一个统一模型来处理更多任务时,用户任务之间的流动更顺畅。
 一个内聚模型比两个不同模型再加它们之间的映射更容易理解。
 两个模型之间的转换可能会很难(有时甚至是不可能的)。
 共享语言可以使团队沟通起来更清楚。
首选较小的BOUNDED CONTEXT
 开发人员之间的沟通开销减少了。
 由于团队和代码规模较小,CONTINUOUS INTEGRATION更容易了。
 较大的上下文要求更加通用的抽象模型,而掌握所需技巧的人员会出现短缺。
不同的模型可以满足一些特殊需求,或者是能够把一些特殊用户群的专门术语和UBIQUITOUS LANGUAGE的专门术语包括进来。

14.13.4 接受那些我们无法更改的事物:描述外部系统

最好从一些最简单的决策开始。一些子系统显然不在开发中的系统的任何BOUNDEDCONTEXT中。一些无法立即淘汰的大型遗留系统和那些提供所需服务的外部系统就是这样的例 子。我们很容易就能识别出这些系统,并把它们与你的设计隔离开。
在做出假设时必须要保持谨慎。我们会很轻易地认为这些系统构成了其自己的BOUNDEDCONTEXT,但大多数外部系统只是勉强满足定义。首先,定义BOUNDED CONTEXT的目的是把模型统一在特定边界之内。你可能负责遗留系统的维护,在这种情况下,可以明确地声明这一目的, 或者也可以很好地协调遗留团队来执行非正式的CONTINUOUS INTEGRATION,但不要认为遗留团队的配合是理所当然的事情。仔细检查,如果开发工作集成得不好,一定要特别小心。在这样的系统中,不同部分之间出现语义矛盾是很平常的事情。

14.13.5 与外部系统的关系

这里可以应用3种模式。首先,可以考虑SEPARATE WAY模式。当然,如果你不需要集成,就不用把它们包括进来。但一定要真正确定不需要集成。
如果集成确实非常重要,可以在两种极端的模式之中进行选择:CONFORMIST模式或ANTICORRUPTION LAYER模式。作为CONFORMIST并不那么有趣,你的创造力和你对新功能的选择都会受到限制。当构建一个大型的新系统时,遵循遗留系统或外部系统的模型可能是不现实的(毕竟,为什么要构建新系统呢?)。但是,当对一个大的系统进行外围扩展时,而且这个系统仍然是主要系统,在这种情况下,继续使用遗留模型可能就很合适。
如果你决定采用CONFORMIST设计,就必须全心全意地去做。你应该约束自己只可以去扩展现有模型,而不能去修改它。
当正在设计的系统功能并不仅仅是扩展现有系统时,而且你与另一个系统的接口很小,或者另一个系统的设计非常糟糕,那么实际上你会希望使用自己的BOUNDED CONTEXT,这意味着需要构建一个转换层,甚至是一个ANTICORRUPTION LAYER。

14.13.6 设计中的系统

你的项目团队正在构建的软件就是设计中的系统。你可以在这个区域内声明BOUNDED CONTEXT,并在每个BOUNDED CONTEXT中应用CONTINUOUS INTEGRATION,以便保持它们的统一。 但应该有几个上下文呢?各个上下文之间又应该是什么关系呢?与外部系统的情况相比,这些问题的答案会变得更加不确定,因为我们拥有更多的主动权。
一般来说,每个BOUNDED CONTEXT对应一个团队。一个团队也可以维护多个BOUNDED CONTEXT,但多个团队在一个上下文中工作却是比较难的(虽然并非不可能)。

14.13.7 用不同模型满足特殊需要

同一业务的不同小组常常有各自的专用术语,而且可能各不相同。这些本地术语可能是非常精确的,并且是根据他们的需要定制的。
当不需要集成或者集成相对有限时,就可以继续使用已经习惯的术语,以免破坏模型。但这也有其自己的代价和风险。如下所示。
 没有共同的语言,交流将会减少。
 集成开销更高。
 随着相同业务活动和实体的不同模型的发展,工作会有一定的重复。
但是,最大的风险或许是,它会成为拒绝改变的理由,或为古怪、狭隘的模型辩护。
有时会出现一个深层次的模型,它把这些不同语言统一起来,并能够满足双方的要求。只有经过大量开发工作和知识消化之后,深层次模型才会在生命周期的后期出现。深层次模型不是计划出来的,我们只能在它出现的时候抓住机遇,修改自己的策略并进行重构。
记住,在需要大量集成的地方,转换成本会大大增加。

14.13.8 部署

在复杂系统中,对打包和部署进行协调是一项繁琐的任务,这类任务总是要比看上去难得多。 BOUNDED CONTEXT策略的选择将影响部署。例如,当CUSTOMER/SUPPLIER TEAM部署新版本时, 他们必须相互协调来发布经过共同测试的版本。在这些版本中,必须要进行代码和数据迁移。在分布式系统中,一种好的做法是把CONTEXT之间的所有转换层放在同一个进程中,这样就不会出现多个版本共存的情况。
当数据迁移可能很花时间或者分布式系统无法同步更新时,即使是单一BOUNDED CONTEXT 中的组件部署也是很困难的,这会导致代码和数据有两个版本共存。
绘制CONTEXT边界时应该反映出部署计划的可行性。当两个CONTEXT通过一个转换层连接时,要想更新其中的一个CONTEXT,新的转换层需要为另一个CONTEXT提供相同的接口。SHARED KERNEL需要进行更多的协调工作,不仅在开发中如此,而且在部署中也同样应该如此。SEPARATE WAY模式可以使工作简单很多。

14.13.9 权衡

通过总结这些指导原则可知有很多统一或集成模型的策略。一般来说,我们需要在无缝功能集成的益处和额外的协调和沟通工作之间做出权衡。还要在更独立的操作与更顺畅的沟通之间做出权衡。更积极的统一需要对有关子系统的设计有更多控制。
在这里插入图片描述

14.14 转换

像建模和设计的其他方面一样,有关BOUNDED CONTEXT的决策并非不可改变的。在很多情况下,我们必须改变最初有关边界以及BOUNDED CONTEXT之间关系的决策,这是不可避免的。一般而言,分割CONTEXT是很容易的,但合并它们或改变它们之间的关系却很难。

14.14.1 合并CONTEXT:SEPARATE WAY →SHARED KERNEL

合并BOUNDED CONTEXT的动机很多:翻译开销过高、重复现象很明显。合并很难,但什么时候做都不晚,只是需要一些耐心。
即使你的最终目标是完全合并成一个采用CONTINUOUS INTEGRATION的CONTEXT,也应该先过渡到SHARED KERNEL。
(1) 评估初始状况。在开始统一两个CONTEXT之前,一定要确信它们确实需要统一。
(2) 建立合并过程。你需要决定代码的共享方式以及模块应该采用哪种命名约定。SHARED KERNEL的代码至少每周要集成一次,而且它必须有一个测试套件。在开发任何共享代码之前, 先把它设置好。(测试套件将是空的,因此很容易通过!)
(3) 选择某个小的子领域作为开始,它应该是两个CONTEXT中重复出现的子领域,但不是CORE DOMAIN的一部分。
(4) 从两个团队中共选出2~4位开发人员组成一个小组,由他们来为子领域开发一个共享的模型。不管模型是如何得出的,它的内容必须详细。这包括一些困难的工作:识别同义词和映射那些尚未被翻译的术语。这个联合团队需要为模型开发一个基本的测试集。
(5) 来自两个团队的开发人员一起负责实现模型(或修改要共享的现有代码)、确定各种细节并使模型开始工作。如果这些开发人员在模型中遇到了问题,就从第(3)步开始重新组织团队,并进行必要的概念修订工作。
(6) 每个团队的开发人员都承担与新的SHARED KERNEL集成的任务。
(7) 清除那些不再需要的翻译。
这时你会得到一个非常小的SHARED KERNEL,并且有一个过程来维护它。在后续的项目迭代中,重复第(3)~(7)步来共享更多内容。随着过程的不断巩固和团队信心的树立,就可以选择更杂的子领域了,同时处理多个子领域,或者处理CORE DOMAIN中的子领域。
如果两个模型中有一个毫无疑问是符合首选条件的,那么就考虑向它过渡,而不用进行集成。不共享公共的子领域,而只是系统性地通过重构应用程序把这些子领域的所有职责从一个BOUNDED CONTEXT转移到另一个BOUNDED CONTEXT,从而使用那个更受青睐的CONTEXT的模型,并对该模型进行需要的增强。在没有集成开销的情况下,消除了冗余。很有可能(但也不是必然的)那个更受青睐的BOUNDED CONTEXT最终会完全取代另一个BOUNDED CONTEXT,这样就实现了与合并完全一样的效果。

14.14.2 合并CONTEXT:SHARED KERNEL→CONTINUOUS INTEGRATION

(1) 确保每个团队都已经建立了CONTINUOUS INTEGRATION所需的所有过程(共享代码所有权、 频繁集成等)。两个团队协商集成步骤,以便所有人都以同一步调工作。
(2) 团队成员在团队之间流动。这样可以形成一大批同时理解两个模型的人员,并且可以把两个团队的人员联系起来。
(3) 澄清每个模型的精髓。
(4) 现在,团队应该有了足够的信心把核心领域合并到SHARED KERNEL中。这可能需要多次迭代,有时需要在新共享的部分与尚未共享的部分之间使用临时的转换层。一旦进入到合并CORE DOMAIN的过程中,最好能快速完成。这是一个开销高且易出错的阶段,因此应该尽可能缩短时间,要优先于新的开发任务。但注意量力而行,不要超过你的处理能力。
(5) 随着SHARED KERNEL的增长,把集成频率提高到每天一次,最后实现CONTINUOUS INTEGRATION。
(6) 当SHARED KERNEL逐渐把先前两个BOUNDED CONTEXT的所有内容都包括进来的时候,你会发现要么形成了一个大的团队,要么形成了两个较小的团队,这两个较小的团队共享一个 CONTINUOUS INTEGRATION的代码库,而且团队成员可以经常在两个团队之间来回流动。

14.14.3 逐步淘汰遗留系统

略~

14.14.4 OPEN HOST SERVICE→PUBLISHED LANGUAGE

我们已经通过一系列特定的协议与其他系统进行了集成,但随着需要访问的系统逐渐增多, 维护负担也不断增加,或者交互变得很难理解。我们需要通过PUBLISHED LANGUAGE来规范系统 之间的关系。
(1) 如果有一种行业标准语言可用,则尽可能评估并使用它。
(2) 如果没有标准语言或预先公开发布的语言,则完善作为HOST的系统的CORE DOMAIN。
(3) 使用CORE DOMAIN作为交换语言的基础,尽可能使用像XML这样的标准交互范式。
(4)(至少)向所有参与协作的各方发布新语言。
(5) 如果涉及新的系统架构,那么也要发布它。
(6) 为每个协作系统构建转换层。
(7) 切换。
记住,PUBLISHED LANGUAGE必须是稳定的,但是当继续进行重构时,仍然需要能够自由地更改HOST的模型。因此,不要把交换语言和HOST的模型等同起来。

第15章 精炼

精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容,而这种形式将使它更有价值,也更有用。模型就是知识的精炼。通过每次重构所得到的更深层的理解,我们得以把关键的领域知识和优先级提取出来。

15.1 模式:CORE DOMAIN

在设计大型系统时,有非常多的组成部分——它们都很复杂而且对开发的成功也至关重要,但这导致真正的业务资产——领域模型最为精华的部分——被掩盖和忽略了。
一个严峻的现实是我们不可能对所有设计部分进行同等的精化,而是必须分出优先级。为了使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建应用程序的功能。但本来就稀缺的高水平开发人员往往会把工作重点放在技术基础设施上,或者只是去解决那些不需要专门领域知识就能理解的领域问题(这些问题都已经有了很好的定义)。
因此:
对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。
让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。仔细判断任何其他部分的投入,看它是否能够支持这个提炼出来的CORE。

15.1.1 选择核心

对CORE DOMAIN的选择取决于看问题的角度。
一个应用程序的CORE DOMAIN在另一个应用程序中可能只是通用的支持组件。尽管如此,仍然可以在一个项目中(而且通常在一个公司中)定义一个一致的CORE。像其他设计部分一样, 人们对CORE DOMAIN的认识也会随着迭代而发展。开始时,一些特定关系可能显得不重要。而最初被认为是核心的对象可能逐渐被证明只是起支持作用。

15.1.2 工作的分配

在项目团队中,技术能力最强的人员往往缺乏丰富的领域知识。这限制了他们的作用,并且更倾向于分派他们来开发一些支持组件,从而形成了一个恶性循环——知识的缺乏使他们远离了那些能够学到领域知识的工作。
打破这种恶性循环是很重要的,方法是建立一支由开发人员和一位或多位领域专家组成的联合团队,其中开发人员必须能力很强、能够长期稳定地工作并且对学习领域知识非常感兴趣,而领域专家则要掌握深厚的业务知识。如果你认真对待领域设计,那么它就是一项有趣且充满技术挑战的工作。你肯定也会找到持这种观点的开发人员。
从外界聘请一些短期的专业人员来设计CORE DOMAIN的关键环节通常是行不通的,因为团队需要积累领域知识,而且短期人员会造成知识流失。相反,充当培训和指导角色的专家可能非常有价值,因为他们帮助团队建立领域设计技巧,并促进团队成员使用尚未掌握的高级设计原则。
出于类似的原因,购买CORE DOMAIN也是行不通的。
除了上述原因之外,还有一个更重要的原因需要引起我们的注意。自主开发的软件的最大价值来自于对CORE DOMAIN的完全控制。一个设计良好的框架可能会提供满足你的专门使用需求的高水平抽象,它可以节省开发那些更通用部分的时间,并使你能够专注于CORE。

15.2 精炼的逐步提升

一份简单的DOMAIN VISION STATEMENT(领域愿景说明)只需很少的投入,它传达了基本概念以及它们的价值。HIGHLIGHTED CORE(突出核心)可以增进沟通,并指导决策制定,这也只需对设计进行很少的改动甚至无需改动。
更积极的精炼方法是通过重构和重新打包显式地分离出GENERIC SUBDOMAIN,然后单独进行处理。在使用COHESIVE MECHANISM的同时,也要保持设计的通用性、易懂性和柔性,这两个方面可以结合起来。只有除去了这些细枝末节,才能把CORE剥离出来。 重新打包出一个SEGREGATED CORE(分离的核心),可以使这个CORE清晰可见(即使在代码中也是如此),并且促进将来在CORE模型上的工作。
最富雄心的精炼是ABSTRACT CORE(抽象内核),它用纯粹的形式表示了最基本的概念和关系(因此,需要对模型进行全面的重新组织和重构)。

15.3 模式:GENERIC SUBDOMAIN

模型中有些部分除了增加复杂性以外并没有捕捉或传递任何专门的知识。任何外来因素都会使CORE DOMAIN愈发的难以分辨和理解。模型中充斥着大量众所周知的一般原则,或者是专门的细节,这些细节并不是我们的主要关注点,而只是起到支持作用。然而,无论它们是多么通用的元素,它们对实现系统功能和充分表达模型都是极为重要的。
因此:
识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独的MODULE中。任何专有的东西都不应放在这些模块中。

15.3.1 通用不等于可重用

现成的解决方案可能适用于某种特殊情况,也可能不适用,但假设你要自己实现代码(内部实现或外包出去),那么不要特别关注代码的可重用性。因为那样做会违反精炼的基本动机——我们应该尽可能把大部分精力投入到CORE DOMAIN工作中,而只在必要的时候才在支持性的GENERIC SUBDOMAIN中投入工作。
重用确实会发生,但不一定总是代码重用。模型重用通常是更高级的重用。

15.4 模式:DOMAIN VISION STATEMENT

写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是“价值主张”。 那些不能将你的领域模型与其他领域模型区分开的方面就不要写了。展示出领域模型是如何实现 和均衡各方利益的。这份描述要尽量精简。尽早把它写出来,随着新的理解随时修改它。
DOMAIN VISION STATEMENT可以用作一个指南,它帮助开发团队在精炼模型和代码的过程中保持统一的方向。团队中的非技术成员、管理层甚至是客户也都可以共享领域愿景说明(当然,包含专有信息的情况除外)。

15.5 模式:HIGHLIGHTED CORE

尽管团队成员可能大体上知道核心领域是由什么构成的,但CORE DOMAIN中到底包含哪些元素,不同的人会有不同的理解,甚至同一个人在不同的时间也会有不同的理解。如果我们总是要不断过滤模型以便识别出关键部分,那么就会分散本应该投入到设计上的精力,而且这还需要广泛的模型知识。

15.5.1 精炼文档

精炼文档并不是完备的设计文档。它只是一个最简单的切入点,描述并解释了核心,并给出了更进一步研究这些核心部分的理由。精炼文档为读者提供了一个总体视图,指出了各个部分是如何组合到一起的,并且指导读者到相应的代码部分寻找更多细节。
因此(作为HIGHLIGHTED CORE的一种形式):
编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE 元素之间的主要交互过程。
独立文档带来的所有常见风险也会在这里出现:
(1) 文档可能得不到维护;
(2) 文档可能没人阅读;
(3) 由于有多个信息来源,文档可能达不到简化复杂性的目的。
控制这些风险的最好方法是保持绝对的精简。剔除那些不重要的细节,只关注核心抽象以及它们的交互,这样文档的老化速度就会减慢,因为这个层次的模型通常更稳定。 精炼文档应该能够被团队中的非技术人员理解。把它当作一个共享的视图,描述每个人都应该知道的东西,而且可以把它作为团队所有成员研究模型和代码的一个起点。

15.5.2 标明CORE

(作为另一种形式的HIGHLIGHTED CORE):
把模型的主要存储库中的CORE DOMAIN标记出来,不用特意去阐明其角色。使开发人员很容易就知道什么在核心内,什么在核心外。

15.5.3 把精炼文档作为过程工具

把精炼文档作为一个指南。如果开发人员发现精炼文档本身需要修改以便与他们的代码或模型修改保持同步,那么这样的修改需要大家一起协商。这种修改要么是从根本上修改CORE DOMAIN元素或关系;要么是修改CORE DOMAIN的边界,把一些元素包含进来,或是把一些元素排除出去。不管使用什么沟通渠道(包括新版本的精炼文档的分发),模型的修改都必须传达到整个团队。
如果精炼文档概括了CORE DOMAIN的核心元素,那么它就可以作为一个指示器——用以指示模型改变的重要程度。当模型或代码的修改影响到精炼文档时,需要与团队其他成员一起协商。当对精炼文档做出修改时,需要立即通知所有团队成员,而且要把新版本的文档分发给他们。CORE外部的修改或精炼文档外部的细节修改则无需协商或通知,可以直接把它们集成到系统中,其他成员在后续工作过程中自然会看到这些修改。这样开发人员就拥有了XP所建议的完全的自治性。

15.6 模式:COHESIVE MECHANISM

封装机制是面向对象设计的一个基本原则。把复杂算法隐藏到方法中,再为方法起一个一看就知道其用途的名字,这样就把“做什么”和“如何做”分开了。这种技术使设计更易于理解和 使用。然而它也有一些先天的局限性。
计算有时会非常复杂,使设计开始变得膨胀。机械性的“如何做”大量增加,把概念性的“做什么”完全掩盖了。为解决问题提供算法的大量方法掩盖了那些用于表达问题的方法。
把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么)了,而把解决方案的复杂 细节(如何做)转移给了框架。
CORE DOMAIN或GENERIC SUBDOMAIN的模型描述的是事实、规则或问题。而COHESIVE MECHANISM则用来满足规则或者用来完成模型指定的计算。

15.6.1 GENERIC SUBDOMAIN与COHESIVE MECHANISM的比较

GENERIC SUBDOMAIN与COHESIVE MECHANISM的动机是相同的——都是为CORE DOMAIN减负。 区别在于二者所承担的职责的性质不同。GENERIC SUBDOMAIN是以描述性的模型作为基础的,它用这个模型表示出团队会如何看待领域的某个方面。在这一点上它与CORE DOMAIN没什么区别, 只是重要性和专门程度较低而已。COHESIVE MECHANISM并不表示领域,它的目的是解决描述性模型所提出来的一些复杂的计算问题。
模型提出问题,COHESIVE MECHANISM解决问题。

15.6.2 MECHANISM是CORE DOMAIN一部分

我们几乎总是想要把MECHANISM从CORE DOMAIN中分离出去。例外的情况是MECHANISM本身就是专有的并且是软件的一项核心价值。有时,非常专用的算法就是这种情况。例如,如果一个非常高效的算法(用于计算日程安排)是运输物流应用程序中的标志性特性之一,那么该机制就可以被认为是概念核心的一部分。

15.7 通过精炼得到声明式风格

精炼的价值在于使你能够看到自己正在做什么,不让无关细节分散你的注意力,并通过不断削减得到核心。如果领域中那些起到支持作用的部分提供了一种简练的 语言,可用于表示CORE的概念和规则,同时又能够把计算或实施这些概念和规则的方式封装起 来,那么CORE DOMAIN的重要部分就可以采用声明式设计。

15.8 模式:SEGREGATED CORE

模型中的元素可能有一部分属于CORE DOMAIN,而另一部分起支持作用。核心元素可能与一般元素紧密耦合在一起。CORE的概念内聚性可能不是很强,看上去也不明显。这种混乱性和耦合关系抑制了CORE。设计人员如果无法清晰地看到最重要的关系,就会开发出脆弱的设计。
因此:
对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来, 并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取到其他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开。
这里基本上采用了与GENERIC SUBDOMAIN一样的原则,只是从另一个方向来考虑而已。那些在应用程序中非常关键的内聚子领域可以被识别出来,并分离到它们自己的内聚包中。如何处理剩下那些未加区分的元素虽然也很重要,但其重要性略低。这些元素或多或少地可以保留在原先的位置,也可以放到包含了重要类的包中。最后,越来越多的剩余元素可以被提取到GENERIC SUBDOMAIN中。但就目前来看,使用哪种简单解决方案都可以,只需把注意力集中在SEGREGATED CORE(分离的核心)上即可。
通过重构得到SEGREGATED CORE的一般步骤如下所示。
(1) 识别出一个CORE子领域(可能是从精炼文档中得到的)。
(2) 把相关的类移到新的MODULE中,并根据与这些类有关的概念为模块命名。
(3) 对代码进行重构,把那些不直接表示概念的数据和功能分离出来。把分离出来的元素放到其他包的类(可以是新的类)中。尽量把它们与概念上相关的任务放在一起,但不要为了追求完美而浪费太长时间。把注意力放在提炼CORE子领域上,并且使CORE子领域对其他包的引用变 得更明显且易于理解。
(4) 对新的SEGREGATED CORE MODULE进行重构,使其中的关系和交互变得更简单、表达得更 清楚,并且最大限度地减少并澄清它与其他MODULE的关系(这将是一个持续进行的重构目标)。
(5) 对另一个CORE子领域重复这个过程,直到完成SEGREGATED CORE的工作。

15.8.1 创建SEGREGATED CORE的代价

有时候,把CORE分离出来会使得它与那些紧密耦合的非CORE类的关系变得更晦涩,甚至更复杂,但CORE DOMAIN更清晰了,而且更易于处理,因此获得的好处还是足以抵偿这种代价。
SEGREGATED CORE使我们能够提高CORE DOMAIN的内聚性。我们可以使用很多有意义的方式来分解模型,有时在创建SEGREGATED CORE时,可以把一个内聚性很好的MODULE拆分开,通过牺牲这种内聚性来换取CORE DOMAIN的内聚性。这样做是值得的,因为企业软件的最大价值来自于模型中企业的那些特有方面。
当然,另一个代价是分离CORE需要付出很大的工作量。我们必须认识到,在做出SEGREGATED CORE的决定时,有可能需要开发人员对整个系统做出修改。

15.8.2 不断发展演变的团队决策

就像很多战略设计决策所要求的一样,创建SEGREGATED CORE需要整个团队一致行动。 这一行动需要团队的一致决策,而且团队必须足够自律和协调才能执行这样的决策。困难之处在于既要约束每个人使其都使用相同的CORE定义,又不能一成不变地去执行这个决策。由于CORE DOMAIN也是不断演变的(像任何其他设计方面一样),在处理SEGREGATED CORE的过程中我们会不断积累经验,这将使我们对什么是核心什么是支持元素这些问题产生新的理解。 我们应该把这些理解反馈到设计中,从而得到更完善的CORE DOMAIN和SEGREGATED CORE MODULE的定义。
这意味着新的理解必须持续不断地在整个团队中共享,但个人(或编程对)不能单方面根据这些理解擅自采取行动。

15.9 模式:ABSTRACT CORE

当不同MODULE的子领域之间有大量交互时,要么需要在MODULE之间创建很多引用,这在很大程度上抵消了划分模块的价值;要么就必须间接地实现这些交互,而后者会使模型变得晦涩难懂。
我们不妨考虑采用横向切割而不是纵向切割的方式。多态性(polymorphism)允许我们忽略抽象类型实例的很多细节变化。如果MODULE之间的大部分交互都可以在多态接口这个层次上表达出来,那么就可以把这些类型重构到一个特定的CORE MODULE中。
这里并不是寻找技术上的技巧。只有当领域中的基本概念能够用多态接口来表达时,这才是一种有价值的技术。在这种情况下,把这些分散注意力的细节分离出来可以使MODULE解耦,同时可以精炼出一个更小、更内聚的CORE DOMAIN。
因此:
把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型, 使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中, 而专用的、详细的实现类则留在由子领域定义的MODULE中。

15.10 深层模型精炼

精炼并不仅限于从整体上把领域中的一些部分从CORE中分离出来。它也意味着对子领域(特别是CORE DOMAIN)进行精炼,通过持续重构得到更深层的理解,从而向深层模型和柔性设计推进。精炼的目标是把模型设计得更明显,使我们可以用模型简单地把领域表示出来。深层模型把领域中最本质的方面精炼成一些简单的元素,使我们可以把这些元素组合起来解决应用程序中的 重要问题。
尽管任何带来深层模型的突破都有价值,但只有CORE DOMAIN中的突破才能改变整个项目的轨道。

第16章 大型结构

在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林” 的境地。
我们需要理解各个部分在整体中的角色,而不必去深究细节。 “大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统。它用一组高级概念或规则(或两者兼有)来为整个系统的设计建立一种模式。这种组织原则既能指导设计,又能帮助 理解设计。另外,它还能够协调不同人员的工作,因为它提供了共享的整体视图,让人们知道各个部分在整体中的角色。
设计一种应用于整个系统的规则(或角色和关系)模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的位臵(即使是在不知道各个部分的详细职责的情况下)。

16.1 模式:EVOLVING ORDER

一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但架构中早期的设计假设又会使项目变得束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员/设计人员的能力。很快,开发人员就会为适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上来。
问题并不在于指导规则本身应不应该存在,而在于这些规则的严格性和来源。如果这些用于控制设计的规则确实符合开发环境,那么它们不但不会阻碍开发,而且还会推动开发在健康的方向上前进,并且保持开发的一致性。
当发现一种大型结构可以明显使系统变得更清晰, 而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如不使用它,因此最好不要为了追求设计的完整性而勉强去使用一种结构,而应该找到尽可能精简 的方式解决所出现问题。要记住宁缺勿滥的原则。

16.2 模式:SYSTEM METAPHOR

当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。围绕这个隐喻来组织设计,并把它吸收到UBIQUITOUS LANGUAGE中。SYSTEM METAPHOR应该既能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的BOUNDED CONTEXT。但所有隐喻都不是完全精确的,因此应不断检查隐喻是否过度或不恰当,当发现它起到妨碍作用时,要随时准备放弃它。

16.3 模式:RESPONSIBILITY LAYER

所谓的层,就是对系统进行划分,每个层的元素都知道或能够使用在它“下面”的那些层的服务,但却不知道它“上面”的层,而且与它上面的层保持独立。
注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象、AGGREGATE和MODULE的职责都清晰地位于一个职责层当中。

16.4 模式:KNOWLEDGE LEVEL

如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的 需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。
因此:
创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
像所有有用的思想一样,REFLECTION和KNOWLEDGE LEVEL可能令人们感到振奋,但不应滥用这种模式。它确实能够使对象不必为了满足各种不同情形下的需求而变得过于复杂,但它所引入的间接性也会使系统变得更模糊。如果KNOWLEDGE LEVEL太复杂,开发人员和用户就很难理解系统的行为。负责配置它的用户(或超级用户)最终将需要具备程序员的技能,甚至需要掌握处理元数据的技能。如果他们出现了错误,应用程序也将会产生错误行为。
而且,数据迁移的基本问题并没有完全得到解决。当KNOWLEDGE LEVEL中的某个结构发生变化时,必须对现有的操作级别中的对象进行相应的处理。新旧对象确实可以共存,但无论如何都需要进行仔细的分析。

16.5 模式:PLUGGABLE COMPONENT FRAMEWORK

当很多应用程序需要进行互操作时,如果所有应用程序都基于相同的一些抽象,但它们是独立设计的,那么在多个BOUNDED CONTEXT之间的转换会限制它们的集成。各个团队之间如果不能紧密地协作,就无法形成一个SHARED KERNEL。重复和分裂将会增加开发和安装的成本,而且互操作会变得很难实现。
一些成功的项目将它们的设计分解为组件,每个组件负责提供某些类别的功能。通常所有组件都插入到一个中央hub上,这个hub支持组件所需的所有协议,并且知道如何与它们所提供的接口进行对话。
因此:
从接口和交互中提炼出一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接口进行操作,那么就可以允许它使用这些组件。
PLUGGABLE COMPONENT FRAMEWORK也有几个缺点。一个缺点是它是一种非常难以使用的模式。它需要高精度的接口设计和一个非常深入的模型,以便把一些必要的行为捕获到 ABSTRACT CORE中。另一个很大的缺点是它只为应用程序提供了有限的选择。如果一个应用程序需要对CORE DOMAIN使用一种非常不同的方法,那么可插入式组件框架将起到妨碍作用。开发人员可以对模型进行特殊修改,但如果不更改所有不同组件的协议,就无法修改ABSTRACT CORE。这样一来,CORE的持续精化过程(也是通过重构得到更深层理解的过程)在某种程度上会陷入僵局。

第17章 领域驱动设计的综合运用

略~

术语归纳

AGGREGATE(聚合)——聚合就是一组相关对象的集合,我们把聚合作为数据修改的单元。 外部对象只能引用聚合中的一个成员,我们把它称为根。在聚合的边界之内应用一组一致的规则。
分析模式(analysis pattern)——分析模式是用来表示业务建模中的常见构造的概念集合。 它可能只与一个领域有关,也可能跨多个领域。
ASSERTION(断言)——断言是对程序在某个时刻的正确状态的声明,它与如何达到这个状态无关。通常,断言指定了一个操作的结果或者一个设计元素的固定规则。
BOUNDED CONTEXT(限界上下文)——特定模型的限界应用。限界上下文使团队所有成员能够明确地知道什么必须保持一致,什么必须独立开发。
客户(client)——一个程序元素,它调用正在设计的元素,使用其功能。
内聚(cohesion)——逻辑上的协定和依赖。
命令,也称为修改器命令(command/modifier)——使系统发生改变的操作(例如,设置变量)。它是一种有意产生副作用的操作。
CONCEPTUAL CONTOUR(概念轮廓)——领域本身的基本一致性,如果它能够在模型中反映出来的话,则有助于使设计更自然地适应变化。
上下文(context)——一个单词或句子出现的环境,它决定了其含义。参见 BOUNDED CONTEXT。
CONTEXT MAP(上下文图)——项目所涉及的限界上下文以及它们与模型之间的关系的一种表示。
CORE DOMAIN(核心领域)——模型的独特部分,是用户的核心目标,它使得应用程序与众不同并且有价值。
声明式设计(declarative design)——一种编程形式,由精确的属性描述对软件进行实际的控制。它是一种可执行的规格。
深层模型(deep model)——领域专家们最关心的问题以及与这些问题最相关的知识的清晰表示。深层模型不停留在领域的表层和粗浅的理解上。
设计模式(design pattern)——设计模式是对一些互相交互的对象和类的描述,我们通过定制这些对象和类来解决特定上下文中的一般设计问题。
精炼(distillation)——精炼是把一堆混杂在一起的组件分开的过程,从中提取出最重要的内容,使得它更有价值,也更有用。在软件设计中,精炼就是对模型中的关键方面进行抽象,或者是对大系统进行划分,从而把核心领域提取出来。
领域(domain)——知识、影响或活动的范围。
领域专家(domain expert)——软件项目的成员之一,精通的是软件的应用领域而不是软件开发。并非软件的任何使用者都是领域专家,领域专家需要具备深厚的专业知识。
领域层(domain layer)——在分层架构中负责领域逻辑的那部分设计和实现。领域层是在软件中用来表示领域模型的地方。
ENTITY(实体)——一种对象,它不是由属性来定义的,而是通过一连串的连续事件和标识定义的。
FACTORY(工厂)——一种封装机制,把复杂的创建逻辑封装起来,并为客户抽象出所创建的对象的类型。
函数(function)——一种只计算和返回结果而没有副作用的操作。
不可变的(immutable)——在创建后永远不发生状态改变的一种特性。
隐式概念(implicit concept)——一种为了理解模型和设计的意义而必不可少的概念,但它从未被提及。
INTENTION-REVEALING INTERFACE(释意接口)——类、方法和其他元素的名称既表达了初始开发人员创建它们的目的,也反映出了它们将会为客户开发人员带来的价值。
固定规则(invariant)——一种为某些设计元素做出的断言,除了一些特殊的临时情况(例如,方法执行的中间,或者尚未提交的数据库事务的中间)以外,它必须一直保持为真。
迭代(iteration)——程序反复进行小幅改进的过程。也表示这个过程中的一个步骤。
大型结构(large-scale structure)——一组高层的概念和/或规则,它为整个系统建立了一种设计模式。它使人们能够从大的角度来讨论和理解系统。
LAYERED ARCHITECTURE(分层架构)——一种用于分离软件系统关注点的技术,它把领域层与其他层分开。
生命周期(life cycle)——一个对象从创建到删除中间所经历的一个状态序列,通常具有一些约束,以确保从一种状态变为另一种状态时的完整性。它可能包括ENTITY在不同的系统和BOUNDED CONTEXT之间的迁移。
模型(model)——一个抽象的系统,描述了领域的所选方面,可用于解决与该领域有关的问题。
MODEL-DRIVEN DESIGN(模型驱动的设计)——软件元素的某个子集严格对应于模型的元素。 也代表一种合作开发模型和实现以便互相保持一致的过程。
建模范式(modeling paradigm)——一种从领域中提取概念的特殊方式,与工具结合起来使用,为这些概念创建软件类比。 (例如,面向对象编程和逻辑编程。)
REPOSITORY(存储库)——一种把存储、检索和搜索行为封装起来的机制,它类似于一个对象集合。
职责(responsibility)——执行任务或掌握信息的责任。
SERVICE(服务)——一种作为接口提供的操作,它在模型中是独立的,没有封装的状态。
副作用(side effect)——由一个操作产生的任何可观测到的状态改变,不管这个操作是有意的还是无意的(即使是一个有意的更新操作)。
STANDALONE CLASS(孤立的类)——无需引用任何其他对象(系统的基本类型和基础库除外)就能够理解和测试的类。
无状态(stateless)——设计元素的一种属性,客户在使用任何无状态的操作时,都不需要关心它的历史。无状态的元素可以使用甚至修改全局信息(即它可以产生副作用),但它不保存影响其行为的私有状态。
战略设计(strategic design)——一种针对系统整体的建模和设计决策。这样的决策影响整个项目,而且必须由团队来制定。
柔性设计(supple design)——柔性设计使客户开发人员能够掌握并运用深层模型所蕴含的潜力来开发出清晰、灵活且健壮的实现,并得到预期结果。同样重要的是,利用这个深层模型, 开发人员可以轻松地实现并调整设计,从而很容易地把他们的新知识加入到设计中。
UBIQUITOUS LANGUAGE(通用语言)——围绕领域模型建立的一种语言,团队所有成员都使用这种语言把团队的所有活动与软件联系起来。
统一(unification)——模型的内部一致性,使得每个术语都没有歧义且没有规则冲突。
VALUE OBJECT(值对象)——一种描述了某种特征或属性但没有概念标识的对象。
WHOLE VALUE(完整值)——对单一、完整的概念进行建模的对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
软件设计师教程笔记整理——XMind文件是一种方便而有效的方法,用于帮助软件设计师整理和管理他们的学习笔记。XMind是一个强大的思维导图工具,可以辅助软件设计师将复杂的概念和信息整理成可视化的思维导图。以下是一些关于如何使用XMind整理软件设计师教程笔记的方法。 首先,在创建XMind文件之前,确定整理笔记的主题和目标。软件设计师教程可能涉及到多个主题,如需求分析、系统设计、编码技术等。根据教程的内容,创建适当的主题和子主题,以帮助分类和组织信息。 其次,使用XMind的多样化功能来补充思维导图。通过添加文本、图片、链接和附件等元素,增强思维导图的可读性和信息密度。例如,可以在节点中添加关键概念的定义、示意图、参考链接或相关文档,以便将来复习、研究或进一步学习。 第三,合理使用颜色和样式来区分不同类型的信息。XMind提供了丰富的颜色和样式选项,可以用来标记重要的内容、不同类型的问题或者需要进一步研究的部分。使用颜色和样式可以帮助软件设计师更快地浏览和理解思维导图。 最后,定期更新和完善XMind文件。软件设计师教程是一个不断更新和发展的领域,因此笔记的更新也是非常重要的。软件设计师应该根据自己的学习进度和新增知识,及时更新XMind文件中的内容,以保持其有效性和准确性。 总的来说,软件设计师教程笔记整理-XMind文件是一个有用的工具,可以帮助软件设计师更好地组织和管理他们的学习笔记。通过合理使用XMind的功能和特点,软件设计师可以轻松地将教程中的关键信息整理成可视化的思维导图,并随时更新和完善。这将有助于软件设计师提高学习效率和工作效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值