20 设计架构

第20章 设计架构

与 Humberto Cervantes 合作

设计师知道,不是当没有什么可以添加的时候,而是当没有什么可以删减的时候,他才达到了完美。

——安托万·德·圣埃克苏佩里(Antoine de Saint-Exupéry)

设计 —— 包括架构设计 —— 是一项复杂的活动。它涉及做出无数决策,这些决策要考虑到系统的许多方面。在过去,这项任务只委托给拥有数十年来之不易经验的高级软件工程师 —— 大师。一种系统的方法为执行这项复杂活动提供指导,以便普通人也能学习并胜任这项工作。

在本章中,我们详细讨论一种方法 —— 属性驱动设计(ADD),它允许以系统、可重复且具有成本效益的方式设计架构。可重复性和可传授性是一门工程学科的标志。为了使一种方法具有可重复性和可传授性,我们需要一套任何经过适当培训的工程师都能遵循的步骤。

我们首先提供 ADD 及其步骤的概述。在此概述之后,对一些关键步骤进行更详细的讨论。

20.1 属性驱动设计

软件系统的架构设计与一般设计并无不同:它涉及做出决策,并利用可用的材料和技能来满足需求和约束。在架构设计中,我们将关于架构驱动因素的决策转化为结构,如图 20.1 所示。架构驱动因素包括具有架构重要性的需求(ASRs—— 第 19 章 的主题),但也包括功能、约束、架构关注点和设计目的。由此产生的结构随后以我们在 第 2 章 中阐述的多种方式指导项目:它们指导分析和构建。它们作为教育新的项目成员的基础。它们指导成本和进度估算、团队组建、风险分析和缓解,当然还有实现。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 20.1 架构设计活动概览

在开始架构设计之前,确定系统的范围很重要——即正在创建的系统中哪些部分在内部,哪些部分在外部,以及系统将与哪些外部实体进行交互。这个上下文可以使用系统上下文图来表示,就像 图 20.2 中所示的那样。上下文图在 第 22 章 中有更详细的讨论。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 20.2 系统上下文图示例

在属性驱动设计(ADD)中,架构设计以轮次进行,每一轮可能由一系列设计迭代组成。一轮包括在一个开发周期内执行的架构设计活动。通过一次或多次迭代,你会生成一个适合本轮既定设计目的的架构。

在每次迭代中,会执行一系列设计步骤。ADD 对每次迭代中需要执行的步骤提供了详细的指导。图 20.3 展示了与 ADD 相关的步骤和工件。在图中,步骤 1 至 7 构成一轮。在一轮中,步骤 2 至 7 构成一轮中的一次或多次迭代。在以下小节中,我们将对这些步骤中的每一个进行概述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 20.3 ADD 的步骤和工件

20.2 ADD的步骤

接下来的部分描述了属性驱动设计(ADD)的步骤。

步骤 1:评审输入

在开始一轮设计之前,你需要确保架构驱动因素(设计过程的输入)是可用且正确的。这些包括:

  • 本轮设计的目的。
  • 主要功能需求。
  • 主要质量属性(QA)场景。
  • 任何约束条件。
  • 任何关注点。

为什么我们要明确捕获设计目的呢?你需要确保自己清楚本轮的目标。在包含多轮的增量设计环境中,一轮设计的目的可能是,例如,为早期估算生成设计、细化现有设计以构建系统的新增量,或者设计并生成原型以减轻某些技术风险。此外,如果这不是全新开发,你需要了解现有架构的设计。

在这一点上,主要功能 —— 通常以一组用例或用户故事的形式捕获 —— 和 QA 场景应该已经被确定了优先级,理想情况下是由你最重要的项目利益相关者确定。(你可以使用几种不同的技术来引出并确定它们的优先级,如在 第 19 章 中所讨论的)。作为架构师,你现在必须 “拥有” 这些。例如,你需要检查在原始需求引出过程中是否有任何重要的利益相关者被忽略,以及自确定优先级以来是否有任何业务条件发生了变化。这些输入确实 “驱动” 着设计,所以正确地获得它们以及正确地确定它们的优先级是至关重要的。我们再怎么强调这一点也不为过。软件架构设计,就像软件工程中的大多数活动一样,是一个 “输入垃圾,清出垃圾” 的过程。如果输入形式不佳,ADD 的结果就不会好。

这些驱动因素成为架构设计待办事项的一部分,你应该使用它来执行不同的设计迭代。当你做出的设计决策考虑了待办事项中的所有项目时,你就完成了这一轮。(我们将在 第 20.8 节 中更深入地讨论待办事项的概念。)

步骤 2 至 7 构成了在本轮设计中执行的每次设计迭代的活动。

步骤 2:通过选择驱动因素建立迭代目标

每次设计迭代都专注于实现一个特定目标。这样的目标通常涉及进行设计以满足一部分驱动因素。例如,一个迭代目标可以是从元素中创建结构,以实现特定的性能场景或一个用例。因此,在进行设计活动时,你需要在开始特定的设计迭代之前确定一个目标。

步骤 3:选择要优化的一个或多个系统元素

满足驱动因素需要你做出架构设计决策,这些决策随后会在一个或多个架构结构中体现出来。这些结构由相互关联的元素组成 —— 模块和 / 或组件,如 第 1 章 所定义 —— 并且这些元素通常是通过细化你在先前迭代中确定的其他元素而获得的。细化可以意味着分解为更细粒度的元素(自顶向下方法)、将元素组合为更粗粒度的元素(自底向上方法)或者改进先前确定的元素。对于全新开发,你可以从建立系统上下文开始,然后选择唯一可用的元素 —— 即系统本身 —— 通过分解进行细化。对于现有系统或全新系统中的后续设计迭代,你通常会选择细化在先前迭代中确定的元素。

你将选择的元素是那些与满足特定驱动因素相关的元素。因此,当设计针对现有系统时,你需要很好地理解作为系统已构建架构一部分的那些元素。获取此信息可能涉及一些 “侦查工作”、逆向工程或与开发人员进行讨论。

在某些情况下,你可能需要颠倒步骤 2 和步骤 3 的顺序。例如,当设计一个全新系统或充实某些类型的参考架构时,至少在设计的早期阶段,你将专注于系统的元素,并通过选择特定元素开始迭代,然后考虑你想要解决的驱动因素。

步骤 4:选择一个或多个满足所选驱动因素的设计概念

选择设计概念可能是你在设计过程中面临的最困难的决定,因为这需要你确定各种可能合理地用于实现迭代目标的设计概念,然后从这些备选方案中进行选择。有许多不同类型的设计概念可供选择——例如,策略、模式、参考架构和外部开发的组件——并且对于每种类型,可能存在许多选项。这可能会导致在做出最终选择之前需要分析相当多的备选方案。在 第 20.3 节 中,我们将更详细地讨论设计概念的识别和选择。

步骤5:实例化架构元素、分配职责和定义接口

一旦你选择了一个或多个设计概念,你必须做出另一种类型的设计决策:如何从你刚刚选择的设计概念中 “实例化” 元素。例如,如果你选择分层模式作为设计概念,你必须决定将使用多少层以及它们之间允许的关系,因为模式本身并没有规定这些。

在实例化元素之后,你需要为每个元素分配职责。例如,在一个应用程序中,通常至少存在三层:表示层、业务层和数据层。这些层的职责不同:表示层的职责包括管理所有的用户交互,业务层管理应用程序逻辑并执行业务规则,数据层管理数据的持久性和一致性。

实例化元素只是创建满足驱动因素或关注点的结构的一部分。已实例化的元素也需要连接起来,从而使它们能够相互协作。这需要在元素之间存在 “关系”,并通过某种接口进行信息交换。接口是一种合同规范,指示信息应如何在元素之间流动。在 第 20.4 节 中,我们将更详细地介绍不同类型的设计概念是如何实例化的、结构是如何创建的以及接口是如何定义的。

步骤 6:绘制视图草图并记录设计决策

此时,你已经完成了本次迭代的设计活动。然而,你可能还没有采取任何行动来确保视图(你所创建的结构的表示)得以保留。例如,如果你在会议室中执行了步骤 5,那么你很可能在白板上得到了一系列图表。这些信息对后续流程至关重要,你必须记录下来,以便稍后分析并传达给其他利益相关者。记录视图的方法可以简单到给白板拍照。

你所创建的视图几乎肯定是不完整的;因此,这些图表可能需要在后续迭代中进行重新审视和细化。这通常是为了容纳因你为支持其他驱动因素而做出的其他设计决策所产生的元素。这就是为什么我们在属性驱动设计(ADD)中说 “绘制草图” 形式的视图,这里的 “草图” 指的是一种初步类型的文档。这些视图更正式、更完整的文档(如果你选择生成的话,见 第 22 章)仅在设计迭代完成后才会产生(作为架构文档活动的一部分)。

除了记录视图的草图之外,你还应该记录在设计迭代中做出的重要决策以及促使这些决策的原因(即基本原理),以便于以后对这些决策进行分析和理解。例如,关于重要权衡的决策应该在此时记录下来。在设计迭代过程中,决策主要在步骤 4 和步骤 5 中做出。在 第 20.5 节 中,我们解释了如何在设计过程中创建初步文档,包括记录设计决策及其基本原理。

步骤 7:对当前设计进行分析,并审查迭代目标以及设计目的的达成情况

到步骤 7 时,你应该已经创建了一个部分设计,该设计解决了为迭代确定的目标。确保实际情况确实如此是个好主意,这样可以避免利益相关者不满意以及后续返工。你可以通过审查你记录的视图草图和设计决策来自己进行分析,但让别人帮助你审查这个设计是个更好的主意。我们这样做的原因与组织经常设立单独的测试 / 质量保证小组的原因相同:另一个人不会与你有相同的假设,并且会有不同的经验基础和不同的视角。这种多样性有助于在代码和架构中发现 “漏洞”。我们在 第 21 章 中更深入地讨论架构分析。

一旦对迭代中执行的设计进行了分析,你应该根据既定的设计目的审查架构的状态。这意味着考虑此时你是否已经进行了足够的设计迭代以满足与本轮设计相关的驱动因素。这也意味着考虑设计目的是否已经实现,或者在未来的项目增量中是否需要额外的设计轮次。在第 20.6 节中,我们讨论了一些简单的技术,可让你跟踪设计进度。

必要时迭代

应该针对每个被考虑的驱动因素进行额外的迭代,并重复步骤 2 至 7。然而,由于时间或资源的限制,这种重复往往是不可能的,这些限制会迫使你停止设计活动并转向实现。

评估是否需要更多设计迭代的标准是什么?让 “风险” 作为你的指南。你至少应该已经解决了具有最高优先级的驱动因素。理想情况下,你应该确定关键驱动因素已得到满足,或者至少确定设计 ““足够好” 以满足它们。

20.3 关于 ADD 步骤 4 的更多内容:选择一个或多个设计概念

在大多数时候,作为架构师,你不需要也不应该重新发明轮子。相反,你的主要设计活动是识别和选择设计概念,以应对最重要的挑战并在设计迭代中解决关键驱动因素。设计仍然是一项具有原创性和创造性的努力,但创造性在于恰当地识别这些现有的解决方案,然后将它们组合并调整以适应手头的问题。即使有现有的解决方案库可供选择——而且我们并不总是有幸拥有丰富的解决方案库——这仍然是设计中最困难的部分。

设计概念的识别

设计概念的识别可能看起来令人生畏,因为有大量的选项可供选择。可能有几十种设计模式和外部开发的组件可用于解决任何特定问题。更糟糕的是,这些设计概念分散在许多不同的来源中:在从业者的博客和网站、研究文献以及书籍中。此外,在许多情况下,一个概念没有标准的定义。例如,不同的网站会以不同的、很大程度上是非正式的方式定义代理模式。最后,一旦你确定了可能有助于实现迭代设计目标的替代方案,你需要为你的目的选择最佳的一个或多个方案。

为了解决特定的设计问题,你可以并且经常会使用和组合不同类型的设计概念。例如,为了解决安全驱动因素,你可能会采用安全模式、安全策略、安全框架或这些的某种组合。

一旦你对希望使用的设计概念类型有了更清晰的认识,你仍然需要确定替代方案 —— 即设计候选方案。你可以通过几种方式实现这一点,尽管你可能会结合使用这些技术而不是单一的方法:

  • 利用现有最佳实践。你可以通过利用现有目录来确定替代方案。一些设计概念,如模式,有大量的文档记录;其他的,如外部开发的组件,则记录得不太全面。这种方法的好处是你可以确定许多替代方案,并利用他人的大量知识和经验。缺点是搜索和研究这些信息可能需要相当多的时间,记录知识的质量通常是未知的,作者的假设和偏见也是未知的。
  • 利用你自己的知识和经验。如果你正在设计的系统与你过去设计的其他系统相似,你可能会想从你以前使用过的一些设计概念开始。这种方法的好处是可以快速而自信地确定替代方案。缺点是你可能最终会反复使用相同的想法,即使它们不是最适合你面临的所有设计问题的,或者如果它们已经被更新、更好的方法所取代。正如俗话所说:如果你只有一把锤子,那么整个世界看起来都像钉子。
  • 利用他人的知识和经验。作为一名架构师,你拥有多年来积累的背景和知识。这种背景和知识因人而异,特别是如果他们过去处理的设计问题类型不同。你可以通过与一些同行进行头脑风暴来进行设计概念的识别和选择,从而利用这些信息。

设计理念的选择

一旦你确定了一系列替代设计概念,你就需要选择其中哪个替代方案最适合解决手头的设计问题。你可以通过一种相对简单的方式来实现这一点,即创建一个表格,列出与每个替代方案相关的优缺点,并根据这些标准和你的驱动因素选择其中一个替代方案。该表格还可以包含其他标准,例如与使用该替代方案相关的成本。像 SWOT(优势、劣势、机会、威胁)分析这样的方法可以帮助你做出这个决定。

在识别和选择设计概念时,请记住作为架构驱动因素一部分的约束条件,因为某些约束会限制你选择特定的替代方案。例如,一个约束可能是所有的库和框架必须采用经批准的许可证。在这种情况下,即使你找到了一个可能满足你的需求的框架,但如果它没有经批准的许可证,你可能需要放弃它。

你还需要记住,你在先前迭代中做出的关于选择设计概念的决策可能会由于不兼容性而限制你现在可以选择的设计概念。例如,在初始迭代中选择了一个 Web 架构,然后在后续迭代中为本地应用程序选择一个用户界面框架。

原型的创建

如果前面提到的分析技术不能指导你做出恰当的设计概念选择,你可能需要创建原型并从它们那里收集测量数据。创建早期的 “一次性” 原型是一种有助于选择外部开发组件的有用技术。这种类型的原型通常在创建时不考虑可维护性、可重用性或实现其他重要目标的可能性。这样的原型不应该被用作进一步开发的基础。

虽然创建原型可能成本高昂,但某些情况强烈促使人们这样做。在考虑是否应该创建原型时,问问这些问题:

  • 项目是否包含新兴技术?
  • 该技术在公司中是新的吗?
  • 是否有某些驱动因素,特别是质量属性,使用所选技术满足这些因素存在风险(即,不清楚它们是否能够被满足)?
  • 是否缺乏可信赖的内部或外部信息,这些信息能在一定程度上确定所选技术将对满足项目驱动因素有用?
  • 是否有与该技术相关的配置选项需要进行测试或理解?
  • 不清楚所选技术是否能容易地与项目中使用的其他技术集成吗?

如果对这些问题的大多数回答是 “是”,那么你应该强烈考虑创建一个一次性原型。

创建原型还是不创建原型?

架构决策通常必须在信息不完整的情况下做出。为了决定走哪条路,一个团队可以进行一系列实验(例如构建原型),以试图减少他们对应该遵循哪条路径的不确定性。问题是,这样的实验可能会带来巨大的成本,而且从实验中得出的结论可能不是确定的。

例如,假设一个团队需要决定他们正在设计的系统应该基于传统的三层架构还是应该由微服务组成。由于这是该团队第一个使用微服务的项目,他们对这种方法没有信心。他们对这两种选择进行成本估算,预计开发三层架构的成本为 50 万美元,开发微服务的成本为 65 万美元。如果在开发了三层架构之后,团队后来得出结论认为选择了错误的架构,估计的重构成本将为 30 万美元。如果首先开发微服务架构,并且后来需要进行重构,估计的额外成本将为 10 万美元。

这个团队应该怎么做呢?

为了决定进行实验是否值得,或者相对于要获得的信心和犯错的成本,我们应该在实验上花费多少,团队可以使用一种称为信息价值(VoI)的技术来解决这些问题。信息价值技术用于通过某种形式的数据收集活动(在这种情况下是构建原型)来计算由于围绕决策的不确定性降低而带来的预期收益。要使用信息价值,团队需要评估以下参数:做出错误设计选择的成本、进行实验的成本、团队对每个设计选择的信心水平以及他们对实验结果的信心水平。使用这些估计值,信息价值然后应用贝叶斯定理来计算两个量:完全信息的预期价值(EVPI)和样本或不完全信息的预期价值(EVSI)。EVPI 表示如果实验能够提供确定的结果(例如,没有假阳性或假阴性),人们应该愿意为实验支付的最大金额。EVSI 表示在知道实验结果可能无法 100% 确定地识别正确解决方案的情况下,人们应该愿意花费多少。

由于这些结果代表预期值,因此应在团队的风险偏好的背景下进行评估。

—Eduardo Miranda

20.4 关于属性驱动设计(ADD)步骤 5 的更多内容:生成结构

设计概念本身并不能帮助你满足驱动因素,除非你生成 “结构”;也就是说,你需要识别并连接从所选设计概念中派生出来的元素。这是属性驱动设计(ADD)中架构元素的 “实例化” 阶段:创建元素以及它们之间的关系,并将职责与这些元素相关联。回想一下,软件系统的架构由一组结构组成。正如我们在 第 1 章 中看到的,这些结构可以分为三大类:

  • 模块结构,由开发时存在的元素组成,如文件、模块和类。
  • 组件和连接器(C&C)结构,由运行时存在的元素组成,如进程和线程。
  • 分配结构,由软件元素(来自模块或 C&C 结构)和在开发时和运行时都可能存在的非软件元素组成,如文件系统、硬件和开发团队。

当你实例化一个设计概念时,实际上你可能会影响不止一种结构。例如,在特定的迭代中,你可能会实例化 第 4 章 中介绍的被动冗余(温备用)模式。这将产生一个 C&C 结构和一个分配结构。作为应用此模式的一部分,你需要选择备用数量、备用状态与活动节点状态保持一致的程度、管理和转移状态的机制以及检测节点故障的机制。这些决策是职责,必须存在于模块结构的元素中的某个地方。

实例化元素

以下是每个设计概念类别可能的实例化方式:

  • 参考架构:对于参考架构,实例化通常意味着进行某种定制。这将需要你添加或删除参考架构所定义的结构中的元素。例如,如果你正在设计一个需要与外部应用程序通信以处理支付的 Web 应用程序,你可能需要在传统的表示层、业务层和数据层旁边添加一个集成组件。
  • 模式:模式提供了一个由元素及其关系和职责组成的通用结构。由于这个结构是通用的,你需要将其调整以适应你的特定问题。实例化通常涉及将模式定义的通用结构转换为适应你正在解决的问题需求的特定结构。例如,考虑客户端 - 服务器架构模式。它确定了计算的基本元素(即客户端和服务器)及其关系(即连接和通信),但没有指定对于你的问题应该使用多少个客户端或服务器,每个的功能应该是什么,哪些客户端应该与哪些服务器通信,或者它们应该使用哪种通信协议。实例化填补了这些空白。
  • 策略:这种设计概念没有规定特定的结构。因此,要实例化一个策略,你可以调整另一种类型的设计概念(你已经在使用的)以实现该策略。或者,你可以利用一个无需任何调整就已经实现该策略的设计概念。例如,你可以(1)选择 “对参与者进行身份验证” 的安全策略,并通过你编织到预先存在的登录过程中的自定义编码解决方案来实例化它;或者(2)采用包含参与者身份验证的安全模式;或者(3)集成一个外部开发的组件,如对参与者进行身份验证的安全框架。
  • 外部开发的组件:这些组件的实例化可能意味着创建新元素,也可能不意味着创建新元素。例如,对于面向对象的框架,实例化可能要求你创建从框架中定义的基类继承的新类。这将产生新元素。一个不涉及创建新元素的例子是为选定的技术指定配置选项,例如线程池中线程的数量。

分配职责并确定属性

当你通过实例化设计概念来创建元素时,你需要考虑分配给这些元素的职责。例如,如果你实例化微服务架构模式(第 5 章),你需要决定微服务将做什么、每种微服务你将部署多少个以及这些微服务的属性是什么。在实例化元素并分配职责时,你应该牢记设计原则,即元素应具有高内聚性(内部)、由一小组职责定义,并表现出低耦合性(外部)。

在实例化设计概念时,你需要考虑的一个重要方面是元素的属性。这可能涉及诸如配置选项、有状态性、资源管理、优先级,甚至是你所选择技术的硬件特性(如果创建的元素是物理节点)等方面。确定这些属性有助于分析和记录你的设计原理。

建立元素之间的关系

结构的创建还需要针对元素之间存在的关系及其属性做出决策。再次考虑客户端 - 服务器模式。在实例化此模式时,你需要决定哪些客户端将通过哪些端口和协议与哪些服务器通信。你还需要决定通信是同步的还是异步的。谁发起交互?传输多少信息以及以何种速率传输?

这些设计决策对于实现诸如性能等质量属性可能会产生重大影响。

定义接口

接口建立了一个契约规范,允许元素进行协作和交换信息。它们可以是外部的也可以是内部的。

外部接口是你的系统必须与之交互的其他系统的接口。这些可能会对你的系统形成约束,因为你通常无法影响它们的规范。正如我们之前提到的,在设计过程开始时建立系统上下文对于识别外部接口很有用。由于外部实体和正在开发的系统通过接口进行交互,所以每个外部系统至少应该有一个外部接口(如图 20.2 所示)。

内部接口是设计概念实例化所产生的元素之间的接口。为了确定关系和接口细节,你需要了解元素如何相互作用以支持用例或质量属性场景。正如我们在 第 15 章 讨论软件接口时所说,“相互作用” 意味着一个元素所做的任何可能影响另一个元素处理的事情。一种特别常见的交互类型是运行时的信息交换。

诸如 UML 序列图、状态图和活动图等行为表示(见 第 22 章)允许你对执行期间元素之间交换的信息进行建模。这种类型的分析对于识别元素之间的关系也很有用:如果两个元素需要直接交换信息或以其他方式相互依赖,那么这些元素之间就存在关系。任何交换的信息都成为接口规范的一部分。

接口的识别通常不会在所有设计迭代中以相同的方式进行。例如,当你开始设计一个全新的系统时,你的第一次迭代只会产生诸如层这样的抽象元素;这些元素将在后续迭代中进行细化。像层这样的抽象元素的接口通常是未充分指定的。例如,在早期迭代中,你可能只是简单地指定用户界面层向业务逻辑层发送 “命令”,而业务逻辑层返回 “结果”。随着设计过程的进行,特别是当你创建结构以解决特定用例和质量属性场景时,你将需要细化参与这些交互的元素的接口。

在一些特殊情况下,识别适当的接口可能会大大简化。例如,如果你选择一个完整的技术栈或一组被设计为可互操作的组件,那么接口将已经由这些技术定义。在这种情况下,接口的规范是一个相对简单的任务,因为所选择的技术已经 “内置” 了许多接口假设和决策。

最后,请注意,在任何给定的属性驱动设计(ADD)迭代中,并非所有内部接口都需要被识别。有些可以委托给后续的设计活动。

20.5 关于属性驱动设计(ADD)步骤 6 的更多内容:在设计过程中创建初步文档

正如我们将在 第 22 章 中看到的,软件架构被记录为一组“视图”,这些视图代表了组成架构的不同结构。这些视图的正式文档记录不是属性驱动设计(ADD)的一部分。然而,结构是作为设计的一部分被生成的。即使它们只是以非正式的方式(如草图)表示,也应该记录这些结构从而能够让你创建这些结构的设计决策,这是作为正常 ADD 活动的一部分应该执行的任务。

记录视图草图

当你通过实例化你为解决特定设计问题而选择的设计概念来生成结构时,你通常不仅会在脑海中生成这些结构,还会为它们创建一些 “草图”。在最简单的情况下,你可以在白板、活动挂图、绘图工具上,甚至只是在一张纸上绘制这些草图。此外,你可以使用建模工具以更严谨的方式绘制这些结构。你所绘制的草图是你架构的初始文档,你应该记录下来,并且如果有必要,可以在以后进一步完善。当你创建草图时,你不一定需要使用像 UML 这样更正式的语言 —— 尽管如果你对这个过程很熟练且感到舒适,那么请使用它。如果你使用一些非正式的符号,你应该注意在符号的使用上保持一致性。最终,你需要为你的图表添加一个图例以提供清晰性并避免歧义。

在创建结构时,你应该养成写下分配给元素的职责的习惯。原因很简单:当你确定一个元素时,你正在脑海中确定该元素的一些职责。在那个时刻把它们写下来可以确保你以后不必记住预期的职责。此外,逐渐写下与你的元素相关的职责比在以后一起记录所有职责要容易得多。

在设计架构时创建这个初步文档需要一些规则。不过,这样做的好处是值得付出努力的,因为你以后能够相对容易且快速地生成更详细的架构文档。如果你正在使用白板或活动挂图,一个简单的记录职责的方法是拍摄你所绘制的草图的照片,并将其粘贴在一个文档中,同时附上一个表格,总结图表中描绘的每个元素的职责(参见 图 20.4 中的一个示例)。如果你使用设计工具,你可以选择一个元素进行创建,并使用通常出现在元素属性表中的文本区域来记录其职责,然后自动生成文档。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 20.4 初步文档示例

该图表由一个描述元素职责的表格作为补充。表 20.1图 20.4 中确定的一些元素起到了这个作用。

表20.1 元素和职责

元素职责
数据流此元素实时从所有数据源收集数据,并将其分发到批处理组件和快速处理组件进行处理。
批处理这负责存储原始数据并预先计算要存储在服务组件中的批处理视图。
. . .. . .

当然,在这个阶段没有必要记录所有的内容。文档的三个目的是分析、构建和教育。在你进行设计的时候,你应该根据你的风险缓解关注点选择一个文档目的,然后为了实现这个目的进行记录。例如,如果你有一个关键的质量属性场景需要你的架构设计来满足,并且如果你需要在分析中证明所提出的设计满足这个标准,那么你必须注意记录与分析相关的信息,以便分析令人满意。同样地,如果你预计需要培训新团队成员,那么你应该绘制系统的组件和连接器视图,展示它是如何运行的以及元素在运行时是如何交互的,也许还可以绘制系统的模块视图,至少展示主要的层次或子系统。

最后,在你进行记录时请记住,你的设计最终可能会被分析。因此,你需要考虑应该记录哪些信息来支持这种分析。

记录设计决策

在每个设计迭代中,你都会做出重要的设计决策以实现迭代目标。当你研究一个代表架构的图表时,你可能会看到一个思考过程的最终产物,但并不总是能轻易理解为实现这个结果所做出的决策。记录超出所选元素、关系和属性表示之外的设计决策,对于帮助阐明你是如何得出结果(即设计原理)至关重要。我们将在 第 22 章 中详细探讨这个主题。

20.6 关于属性驱动设计(ADD)步骤 7 的更多内容:对当前设计进行分析,并审查迭代目标以及设计目的的达成情况

在一次迭代结束时,进行一些分析以反思你刚刚做出的设计决策是明智的。我们在 第 21 章 中描述了几种进行此类分析的技术。在这一点上你需要进行的一种分析是评估你是否已经做了足够的设计工作。具体而言:

  • 你需要做多少设计?
  • 到目前为止你已经做了多少设计?
  • 你完成了吗?

使用待办事项列表和看板等实践可以帮助你跟踪设计进度并回答这些问题。

使用架构待办事项列表

架构待办事项列表是一个待办事项清单,其中包含作为架构设计过程的一部分仍需执行的未完成行动。最初,你应该用你的驱动因素填充设计待办事项列表,但也可以包括支持架构设计的其他活动,例如:

  • 创建原型以测试特定技术或解决特定的质量属性风险。
  • 探索和理解现有资产(可能需要进行逆向工程)。
  • 在对目前为止的设计决策进行审查时发现的问题。

此外,随着决策的做出,你可以向待办事项列表中添加更多项目。例如,如果选择了一个参考架构,你可能需要向架构设计待办事项列表中添加特定的关注点或从中派生的质量属性场景。例如,如果我们选择了一个 Web 应用程序参考架构,并且发现它没有提供会话管理,那么这就成为一个需要添加到待办事项列表中的关注点。

使用设计看板

另一个可用于跟踪设计进度的工具是看板,如图 20.5 所示。这个看板建立了三类待办事项:“未处理”、“部分处理” 和 “完全处理”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 20.5 用于跟踪设计进度的看板

在一次迭代开始时,设计过程的输入成为待办事项列表中的条目。最初(在步骤 1),本次设计回合的待办事项列表中的条目应该位于看板的 “未处理” 列中。当你开始一次设计迭代时,在步骤 2,与你在设计迭代目标中处理的驱动因素相对应的待办事项条目应该移动到 “部分处理” 列中。最后,一旦你完成一次迭代,并且对你的设计决策的分析表明某个特定的驱动因素已经得到处理(步骤 7),该条目应该移动到看板的 “完全处理” 列中。

建立明确的标准以允许将驱动因素移动到 “部分处理” 或 “完全处理” 列是很重要的。例如,“完全处理” 的一个标准可以是该驱动因素已经过分析,或者它已经在原型中实现,并且你确定该驱动因素的要求已经得到满足。在特定迭代中选择的驱动因素可能在该迭代中没有完全得到处理。在这种情况下,它们应该留在 “部分处理” 列中。

选择一种技术来根据优先级区分看板中的条目可能会很有用。例如,你可以根据优先级为条目使用不同的颜色。

看板可以让你很容易地直观跟踪设计的进展,因为你可以快速看到在迭代中有多少(最重要的)驱动因素正在被处理或已经被处理。这种技术也有助于你决定是否需要进行额外的迭代。理想情况下,当大多数你的驱动因素(或者至少是具有最高优先级的那些)位于 “完全处理” 列下时,设计回合就可以结束了。

20.7 小结

设计是困难的。需要一些方法来使其更易于处理(且可重复)。在本章中,我们详细讨论了属性驱动设计(ADD)方法;它允许以一种系统且具有成本效益的方式来设计架构。

我们还讨论了在设计过程的各个步骤中需要考虑的几个重要方面。这些方面包括包括设计概念的识别和选择、它们在生成结构中的用途、接口的定义、初步文档的制作以及跟踪设计进度的方法。

20.8 扩展阅读

ADD 的第一个版本最初被称为 “基于架构的设计”,记录在 [Bachmann 00b] 中。

随后在 2006 年发布了 ADD 2.0 的描述。它是第一个专门关注质量属性及其通过选择不同类型的结构并通过视图表示来实现的方法。ADD 2.0 版本首先记录在一份 SEI 技术报告 [Wojcik 06] 中。

本章中描述的 ADD 版本是 ADD 3.0。与原始版本相比,一些重要的改进包括更多地考虑将实现技术的选择作为主要设计概念,考虑诸如设计目的和架构关注点等其他驱动因素,使初始文档编制和分析成为设计过程的明确步骤,并提供关于如何开始设计过程以及如何在敏捷环境中使用它的的指导。有一整本书 [Cervantes 16] 专门介绍使用 ADD 3.0 进行架构设计。ADD 3.0 的一些概念首先在一篇《IEEE Software》文章 [Cervantes 13] 中被引入。

George Fairbanks 写了一本引人入胜的书,描述了一种风险驱动的架构设计过程,书名为《恰到好处的软件架构:一种风险驱动的方法》[Fairbanks 10]。

信息价值技术可追溯到 20 世纪 60 年代 [Raiffa 00]。在 [Hubbard 14] 中可以找到一种更现代的处理方法。

对于系统设计的一般方法,你可以阅读 Butler Lampson 的经典著作 [Lampson 11]。

看板利用精益生产的概念,是一种用于安排系统生产的方法,如 Corey Ladas 所描述的 [Ladas 09]。

20.9 问题讨论

1. 遵循既定的设计方法有哪些优点?又有哪些缺点?

2. 进行架构设计与敏捷开发方法是否兼容?选择一种敏捷方法并在该背景下讨论属性驱动设计(ADD)。

3. 设计与分析之间有什么关系?是否存在一些知识是其中一个需要而另一个不需要的?

4. 如果你必须在设计过程中向你的经理论证创建和维护架构文档的价值,你会提出哪些论据?

5. 如果你进行全新开发(greenfield development)与二次开发(brownfield development),你对 ADD 步骤的实现会有哪些不同?


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值