文章目录
- 第22章 记录架构
- 22.1 架构文档的用途和受众
- 22.2 符号表示
- 22.3 视图
- 模块视图
- 组件和连接器视图
- C&C 视图的符号表示
- 分配视图
- 质量视图
- 22.4 组合视图
- 22.5 记录行为
- 22.6 视图之外
- 22.7 记录基本原理
- 22.8 架构的利益相关者
- 22.9 实际考虑
- 建模工具
- 在线文档、超文本和维基
- 遵循发布策略
- 记录动态变化的架构
- 可追溯性
- 22.10 小结
- 22.11 扩展阅读
- 22.12 问题讨论
第22章 记录架构
文档是你写给未来自己的一封情书。
—Damian Conway
创建一个架构是不够的。必须以一种能让其利益相关者正确使用它来完成工作的方式进行传达。如果你不辞辛苦地创建了一个强大的架构,一个你期望经得起时间考验的架构,那么你就 必须 不辞辛苦地对其进行足够详细、毫无歧义的描述,并进行组织,以便其他人能够快速找到和更新所需的信息。
文档代表了架构师发言。它在当下为架构师发言,此时架构师应当在做其他事情,而非回答关于架构的成百上千个问题。它也在未来为架构师发言,那时架构师已经忘记了架构所包含的细节,或者当那个人已经离开了项目,而其他人现在成为了架构师。
最优秀的架构师会制作出良好的文档,不是因为这是“要求”,而是因为他们明白这对于当前的事情至关重要——以可预测的方式生产出高质量的产品,并且尽量减少返工。他们将直接的利益相关者视为在这项工作中关系最密切的人:开发人员、部署人员、测试人员、分析人员。
但是架构师也认为文档对他们自身有价值。文档充当着在重大设计决策得到确认时容纳其结果的容器。一个经过深思熟虑的文档方案可以使设计过程更加顺利和系统。无论是在为期六个月的设计阶段还是为期六天的敏捷冲刺中,在架构设计进行的过程中,文档都有助于架构师对架构设计进行推理和交流。
请注意,“文档”并不一定意味着生成一个实体的、印刷的、像书一样的制品。诸如维基这样的在线文档,以能够引发讨论、利益相关者反馈和搜索的方式托管,是架构文档的理想论坛。另外,不要认为文档是与设计不同且在设计之后的一个步骤。你用来向他人解释架构的语言可以在你进行设计工作时为你所用。理想情况下,设计和文档是同一项工作。
22.1 架构文档的用途和受众
架构文档必须服务于多种目的。它应该具有足够的透明度和易访问性,以便新员工能够快速理解。它应该足够具体,以作为构建或取证的蓝图。它应该包含足够的信息,以作为分析的基础。
架构文档可以被视为既具有规定性又具有描述性。对于某些受众来说,它规定了什么“应该”是正确的,对尚未做出的决策施加限制。对于其他受众来说,它描述了什么“是”正确的,叙述关于系统设计已经做出的决策。
许多不同类型的人会对架构文档感兴趣。他们希望并期待这份文档能帮助他们完成各自的工作。理解架构文档的用途至关重要,因为这些用途决定了要记录的重要信息。
从根本上说,架构文档有四个用途。
-
架构文档作为一种教育手段。教育用途包括向人们介绍系统。这些人可能是团队的新成员、外部分析师,甚至是新的架构师。在很多情况下,这个 “新” 人是客户,你首次向他们展示你的解决方案 —— 你希望这个展示能带来资金或批准继续进行。
-
架构文档作为利益相关者之间沟通的主要工具。它作为沟通工具的具体用途取决于哪些利益相关者在进行沟通。
也许架构文档最热心的消费者之一正是项目未来的架构师。这可能是同一个人(如本章开头的引文中所述),也可能是接替者,但无论哪种情况,未来的架构师肯定在文档中有巨大的利益。新架构师有兴趣了解他们的前辈是如何解决系统中的难题的,以及为什么做出特定的决策。即使未来的架构师是同一个人,他或她也会将文档用作思想的宝库、设计决策的仓库,这些决策数量众多且错综复杂,仅凭记忆永远无法重现。
我们在 第 22.8 节 中列举了架构及其文档的利益相关者。
-
架构文档作为系统分析和构建的基础。架构告诉实现者要实现哪些模块以及这些模块如何连接在一起。这些依赖关系决定了模块开发团队必须与之沟通的其他团队。
对于那些对设计满足系统质量目标的能力感兴趣的人来说,架构文档是评估的素材。它必须包含评估各种属性所需的信息,如安全性、性能、可用性、可维护性和可修改性。
-
架构文档在发生事件时作为取证的基础。当事件发生时,有人负责追查事件的直接原因和根本原因。事件发生前的控制流信息将提供 “实际执行” 的架构。例如,接口规范数据库将为控制流提供上下文,组件描述将指出在事件跟踪中每个组件应该发生的情况。
为了使文档随着时间的推移继续提供价值,它需要保持更新。
22.2 符号表示
用于记录视图的符号在形式化程度上有很大差异。大致来说,有三种主要的符号类别:
- 非正式符号:视图可以使用通用的绘图和编辑工具以及为手头系统选择的视觉惯例来描绘(通常是图形化的)。你可能见过的大多数方框和线条图都属于这一类 —— 想想 PowerPoint 或类似的东西,或者在白板上手绘的草图。描述的语义用自然语言来表征,不能进行形式化分析。
- 半正式符号:视图可以用一种标准化的符号来表达,这种符号规定了图形元素和构造规则,但没有对这些元素的意义提供完整的语义处理。可以进行基本分析以确定一个描述是否满足语法属性。UML 及其系统工程辅助工具 SysML 在这个意义上是半正式符号。大多数广泛使用的商业建模工具都采用这一类符号。
- 正式符号:视图可以用一种具有精确(通常基于数学)语义的符号来描述。可以对语法和语义进行形式化分析。有多种用于软件架构的正式符号。通常被称为架构描述语言(ADL),它们通常为架构表示提供图形词汇和底层语义。在某些情况下,这些符号专门用于特定的架构视图。在其他情况下,它们允许有许多视图,甚至提供正式定义新视图的能力。ADL 的有用性在于它们能够通过相关工具支持自动化 —— 自动化以对架构进行有用的分析,或协助代码生成。在实践中,正式符号的使用很少见。
通常,更正式的符号需要更多的时间和精力来创建和理解,但以减少的歧义性和更多的分析机会来回报这种努力。相反,更非正式的符号更容易创建,但提供的保证较少。
无论形式化程度如何,始终记住不同的符号在表达不同种类的信息时更好(或更差)。除了形式化之外,没有 UML 类图会帮助你推理可调度性,序列图也不会告诉你很多关于系统按时交付的可能性。在选择符号和表示语言时,你应该牢记你需要捕捉和推理的重要问题。
22.3 视图
也许与软件架构文档相关的最重要概念是 “视图”。软件架构是一个复杂的实体,不能以简单的一维方式进行描述。视图是一组系统元素及其之间关系的表示 —— 不是所有的系统元素,而是特定类型的那些元素。例如,系统的分层视图将显示 “层” 类型的元素;也就是说,它将展示系统分解为层以及这些层之间的关系。然而,纯粹的分层视图不会显示系统的服务、客户端和服务器、数据模型或任何其他类型的元素。
因此,视图让我们将软件架构这个多维实体划分为若干个(我们希望是)有趣且易于管理的系统表示。“视图” 的概念引出了架构文档的一个基本原则:
记录一个架构就是记录相关视图,然后添加适用于多个视图的文档。
哪些是相关视图呢?这完全取决于你的目标。如我们之前所见,架构文档可以有很多用途:作为实现者的任务说明、分析的基础、自动代码生成的规范、系统理解和逆向工程的起点,或者项目评估和规划的蓝图。
不同的视图也在不同程度上揭示不同的质量属性。反过来,你和系统开发中的其他利益相关者最关心的质量属性将影响你选择记录哪些视图。例如,一个 “模块视图” 将让你推理系统的可维护性,一个 “部署视图” 将让你推理系统的性能和可靠性等等。
因为不同的视图支持不同的目标和用途,我们不提倡使用任何特定的视图或视图集合。你应该记录的视图取决于你期望对文档的使用。不同的视图将突出不同的系统元素和关系。要表示多少不同的视图是一个成本 / 效益决策的结果。每个视图都有成本和收益,你应该确保创建和维护特定视图的预期收益超过其成本。
视图的选择是由记录设计中的特定模式的需求驱动的。有些模式由模块组成,有些由组件和连接器组成,还有一些有部署方面的考虑。模块视图、组件和连接器(C&C)视图以及分配视图分别是表示这些考虑的适当机制。这些视图类别当然对应于 第 1 章 中描述的三种架构结构类别。(回想一下 第 1 章,结构是元素、关系和属性的集合,而视图是一个或多个架构结构的表示。)
在本节中,我们探讨这三类基于结构的视图,然后介绍一个新的类别:质量视图。
模块视图
模块是一个提供一组一致职责的实现单元。模块可以采用类、类的集合、层、方面或实现单元的任何分解形式。模块视图的示例有分解视图、使用视图和层视图。每个模块视图都有一组分配给它的属性。这些属性表达了与每个模块以及模块之间的关系相关的重要信息,还有对模块的约束。示例属性包括职责、可见性信息(其他哪些模块可以使用它)以及修订历史。模块之间的关系包括: “是…… 的一部分”、“依赖于” 和 “是一种”。
系统软件分解为可管理单元的方式仍然是系统结构的重要形式之一。至少,它决定了系统的源代码如何分解为单元,每个单元可以对其他单元提供的服务做出何种假设,以及这些单元如何聚合成更大的集合。它还包括影响多个单元以及受多个单元影响的共享数据结构。模块结构通常决定了对系统某一部分的更改可能如何影响其他部分,从而决定了系统支持可修改性、可移植性和可重用性的能力。
如果没有至少一个模块视图,任何软件架构的文档都不太可能是完整的。表 22.1 总结了模块视图的特征。
表 22.1 模块视图总结
元素 | 模块是软件的实现单元,提供一组一致的职责。 |
---|---|
关系 |
|
约束 | 不同的模块视图可能会施加拓扑约束,例如对模块之间可见性的限制。 |
用法 |
|
模块的有助于指导实现或作为分析输入的属性,应作为模块视图支持文档的一部分进行记录。属性列表可能会有所不同,但可能包括以下内容:
- 名称:模块的名称当然是引用它的主要方式。模块的名称通常暗示其在系统中的角色。此外,模块的名称可能反映其在分解层次结构中的位置;例如,名称 A.B.C 指的是模块 C,它是模块 B 的子模块,而模块 B 本身又是 A 的子模块。
- 职责:模块的职责属性是一种确定其在整个系统中的角色的方式,并在名称之外为其建立一个标识。虽然模块的名称可能暗示其角色,但职责说明能更确定地确立该角色。职责应描述得足够详细,以便让读者清楚每个模块的作用。如果有项目需求规格说明,模块的职责通常通过追溯到该规格说明来捕获。
- 实现信息:模块是实现的单元。因此,从管理其开发和构建包含它们的系统的角度记录与其实现相关的信息是很有用的。这可能包括:
- 映射到源代码单元:这确定了构成模块实现的文件。例如,如果一个名为
Account
的模块用 Java 实现,可能有几个文件构成其实现:IAccount.java
(一个接口)、AccountImpl.java
(账户功能的实现),甚至可能还有一个单元测试AccountTest.java
。 - 测试信息:模块的测试计划、测试用例、测试框架和测试数据很重要,需要记录下来。此信息可能只是指向这些工件位置的指引。
- 管理信息:经理可能需要有关模块的预计进度和预算的信息。此信息可能只是指向这些工件位置的指针。
- 实现约束:在许多情况下,架构师会对模块有一个实现策略,或者可能知道实现必须遵循的约束。
- 修订历史:了解模块的历史,包括其作者和特定的变更,在进行维护活动时可能会对你有帮助。
- 映射到源代码单元:这确定了构成模块实现的文件。例如,如果一个名为
模块视图可用于向不熟悉系统的人解释系统的功能。模块分解的不同粒度级别提供了系统职责的自上而下的呈现,因此可以指导学习过程。对于已经有实现的系统,如果模块视图保持更新,它们会很有帮助,因为它们向团队中的新开发人员解释了代码库的结构。
相反,很难使用模块视图来推断运行时行为,因为这些视图只是软件功能的静态划分。因此,模块视图通常不用于性能、可靠性和许多其他运行时质量的分析。对于这些目的,我们依赖于组件和连接器视图以及分配视图。
组件和连接器视图
组件和连接器(C&C)视图展示具有某些运行时存在的元素,例如进程、服务、对象、客户端、服务器和数据存储。这些元素被称为 “组件”。此外,C&C 视图将交互路径(如通信链路和协议、信息流以及对共享存储的访问)作为元素包含在内。在 C&C 视图中,这样的交互被表示为 “连接器”。C&C 视图的示例包括客户端 - 服务器、微服务和通信进程。
C&C 视图中的一个组件可能代表一个复杂的子系统,其本身可以被描述为一个 C&C 子架构。一个组件的子架构可能采用与该组件出现的模式不同的模式。
简单的连接器示例包括服务调用、异步消息队列、支持发布 - 订阅交互的事件多播以及表示异步、保持顺序的数据流的管道。连接器通常代表更复杂的交互形式,例如数据库服务器和客户端之间的面向事务的通信通道,或者调解服务用户和提供者集合之间交互的企业服务总线。
连接器不一定是二元的;也就是说,它们不一定恰好有两个与之交互的组件。例如,一个发布 - 订阅连接器可能有任意数量的发布者和订阅者。即使连接器最终是使用二元连接器(如过程调用)实现的,在 C&C 视图中采用 n 元连接器表示也可能很有用。连接器体现了一种交互协议。当两个或更多组件交互时,它们必须遵守关于交互顺序、控制位置以及错误情况和超时处理的约定。交互协议应被记录下来。
C&C 视图中的主要关系是连接。“连接” 表示哪些连接器连接到哪些组件,从而将系统定义为组件和连接器的图。兼容性通常根据信息类型和协议来定义。例如,如果 Web 服务器期望通过 HTTPS 进行加密通信,那么客户端必须执行加密。
C&C 视图的一个元素(组件或连接器)将有与其相关联的各种属性。具体来说,每个元素都应该有一个名称和类型,其附加属性取决于组件或连接器的类型。作为架构师,你应该为支持特定 C&C 视图的预期分析的属性定义值。以下是一些典型属性及其用途的示例:
- 可靠性:给定组件或连接器的故障可能性是多少?此属性可用于帮助确定整个系统的可用性。
- 性能:在何种负载下组件将提供什么样的响应时间?对于给定的连接器,可以预期什么样的带宽、延迟或抖动?此属性可与其他属性一起用于确定系统范围的属性,如响应时间、吞吐量和缓冲需求。
- 资源需求:组件或连接器的处理和存储需求是什么?如果相关,它消耗多少能源?此属性可用于确定提议的硬件配置是否足够。
- 功能:一个元素执行什么功能?此属性可用于推理系统执行的端到端计算。
- 安全性:组件或连接器是否实施或提供安全功能,如加密、审计跟踪或身份验证?此属性可用于确定潜在的系统安全漏洞。
- 并发性:此组件是否作为单独的进程或线程执行?此属性有助于分析或模拟并发组件的性能,并识别可能的死锁和瓶颈。
- 运行时可扩展性:消息结构是否支持不断发展的数据交换?连接器是否可以适应处理那些新的消息类型?
C&C 视图通常用于向开发人员和其他利益相关者展示系统的工作方式:可以 “动画化” 或跟踪 C&C 视图,展示端到端的活动线程。C&C 视图也用于推理运行时系统质量属性,如性能和可用性。特别是,一个有良好文档记录的视图允许架构师在给定各个元素及其交互的属性的估计或测量值的情况下预测整体系统属性,如延迟或可靠性。
表 22.2 总结了 C&C 视图的特征。
表22.2C&C视图总结
元素 |
|
---|---|
关系 |
|
约束 | 组件只能连接到连接器,而连接器只能连接到组件。
|
用法 | 展示系统如何工作。
|
C&C 视图的符号表示
一如既往,方框和线条图可用于表示组件和连接器(C&C)视图。虽然非正式符号在它们能够传达的语义方面受到限制,但遵循一些简单的准则可以为描述增添严谨性和深度。主要准则很简单:为每种组件类型和每种连接器类型分配一个单独的符号,并在一个图例部分列出每种类型。
UML 组件与 C&C 组件在语义上非常匹配,因为它们允许直观地记录重要信息,如接口、属性和行为描述。UML 组件还区分组件类型和组件实例,这在定义特定于视图的组件类型时很有用。
分配视图
分配视图描述了软件单元到软件被开发或执行的环境元素的映射。这种视图中的环境各不相同;它可能是硬件、软件执行的操作环境、支持开发或部署的文件系统,或者开发组织。
表 22.3 总结了分配视图的特征。这些视图由软件元素和环境元素组成。环境元素的示例有处理器、磁盘阵列、文件或文件夹,或者一组开发人员。软件元素来自模块视图或组件和连接器(C&C)视图。
表 22.3 分配视图总结
元素 | 软件元素和环境元素。 软件元素具有对环境的 “要求” 属性。 环境元素具有向软件 “提供” 的属性。 |
---|---|
关系 | “被分配到”:一个软件元素被映射(分配到)一个环境元素。 |
约束 | 因视图而异。 |
用法 | 用于推理性能、可用性、安全性和可靠性。用于推理分布式开发以及向团队分配工作。用于推理对软件版本的并发访问。用于推理系统安装的形式和机制。 |
分配视图中的关系是 “被分配到”。我们通常在从软件元素到环境元素的映射方面谈论分配视图,尽管反向映射也可能是相关且潜在有趣的。单个软件元素可以被分配到多个环境元素,并且多个软件元素可以被分配到单个环境元素。如果这些分配在系统执行期间随时间变化,那么就说该架构在该分配方面是动态的。例如,进程可能从一个处理器或虚拟机迁移到另一个。
在分配视图中,软件元素和环境元素具有属性。分配视图的一个目标是将软件元素所需的属性与环境元素提供的属性进行比较,以确定分配是否会成功。例如,为了确保其 “所需” 的响应时间,一个组件必须在(被分配到)一个提供足够快处理能力的处理器上执行。再举一个例子,一个计算平台可能不允许一个任务使用超过 10 千字节的虚拟内存;可以使用所讨论的软件元素的执行模型来确定所需的虚拟内存使用情况。类似地,如果您将一个模块从一个团队迁移到另一个团队,您可能希望确保新团队具有处理该模块的适当技能和背景知识。
分配视图可以描绘静态或动态视图。静态视图展示了环境中资源的固定分配。动态视图显示了资源分配发生变化的条件和触发因素。例如,一些系统在负载增加时提供并利用新资源。一个例子是负载均衡系统,其中在另一台机器上创建新的进程或线程。在这种视图中,需要记录分配视图发生变化的条件、运行时软件的分配以及动态分配机制。
回想一下 第 1 章,分配结构之一是工作分配结构,它将模块分配给团队进行开发。该分配也可以根据 “负载” 进行更改 —— 在这种情况下,是已经在工作的开发团队的负载。
质量视图
模块视图、组件和连接器视图以及分配视图都是结构视图:它们主要展示架构师在架构中设计的结构,以满足功能和质量属性要求。
这些视图是指导和约束下游开发人员的绝佳选择,下游开发人员的主要工作是实现这些结构。然而,在某些质量属性(或者就此而言,任何利益相关者关注的问题)特别重要且普遍存在的系统中,结构视图可能不是呈现满足这些需求的架构解决方案的最佳方式。原因是解决方案可能分布在多个结构中,而这些结构组合起来很麻烦(例如,因为每个结构中显示的元素类型不同)。
另一种视图,我们称之为 “质量视图”,可以针对特定的利益相关者或解决特定的关注点进行定制。质量视图是通过提取结构视图的相关部分并将它们组合在一起形成的。这里有五个例子:
- “安全视图” 可以展示为提供安全性而采取的所有架构措施。它将描绘具有某种安全角色或责任的组件、这些组件如何通信、安全信息的任何数据存储库以及具有安全利益的存储库。该视图的属性将包括系统环境中的其他安全措施(例如,物理安全)。安全视图还将展示安全协议的操作以及人类与安全元素交互的位置和方式。最后,它将捕获系统如何响应特定的威胁和漏洞。
- “通信视图” 对于全球分散且异构的系统可能特别有帮助。此视图将展示所有组件到组件的通道、各种网络通道、服务质量参数值以及并发区域。这样的视图可用于分析某些类型的性能和可靠性,例如死锁或竞争条件检测。此外,它可以展示(例如)网络带宽如何动态分配。
- “异常” 或 “错误处理视图” 可以帮助阐明并引起对错误报告和解决机制的关注。这样的视图将展示组件如何检测、报告和解决故障或错误。它将帮助架构师确定错误的来源,并为每个错误指定适当的纠正措施。最后,它将在这些情况下促进根本原因分析。
- “可靠性视图” 将对诸如复制和切换等可靠性机制进行建模。它还将描绘时间问题和事务完整性。
- “性能视图” 将包括架构中对推断系统性能有用的那些方面。这样的视图可能会显示网络流量模型、操作的最大延迟等等。
这些和其他质量视图反映了 ISO/IEC/IEEE 标准 42010:2011 的文档编制理念,该标准规定创建由架构利益相关者的关注点驱动的视图。
22.4 组合视图
将架构记录为一组独立视图的基本原理为记录任务带来了分而治之的优势。当然,如果这些视图完全不同且彼此之间没有关联,那么就没有人能够理解整个系统。然而,因为架构中的所有结构都是同一架构的一部分并且存在是为了实现一个共同的目的,所以它们中的许多都彼此有很强的关联。管理架构结构如何关联是架构师工作的重要部分,无论这些结构是否有任何文档存在。
有时,展示两个视图之间强关联的最方便方法是将它们合并为一个 “组合视图”。组合视图包含来自两个或更多其他视图的元素和关系。只要你不尝试在其中加载过多的映射,这样的视图就会非常有用。
合并视图的最简单方法是创建一个 “叠加层”,它结合了原本会出现在两个单独视图中的信息。如果两个视图之间的关系紧密 —— 也就是说,如果一个视图中的元素与另一个视图中的元素之间有很强的关联,那么这种方法效果很好。在叠加层中,元素和关系保留其在组成视图中定义的类型。
以下视图组合通常很自然地出现:
- “不同的组件和连接器(C&C)视图相互组合”。因为所有 C&C 视图都显示了各种类型的组件和连接器之间的运行时关系,所以它们往往结合得很好。不同的(单独的)C&C 视图倾向于显示系统的不同部分,或者倾向于显示其他视图中组件的分解细化。结果通常是一组可以很容易组合的视图。
- “部署视图与任何显示进程的 C&C 视图组合”。进程是部署到处理器、虚拟机或容器上的组件。因此,这些视图中的元素之间有很强的关联。
- “分解视图与任何工作分配、实现、使用或分层视图组合”。分解后的模块构成了工作、开发和使用的单位。此外,这些模块填充了各个层
图 22.1 显示了一个组合视图的示例,它是客户端 - 服务器、多层和部署视图的叠加。
图 22.1 组合视图
22.5 记录行为
记录架构需要行为文档,通过描述架构元素如何相互交互来补充结构视图。对诸如系统发生死锁的可能性、系统在期望时间内完成任务的能力或最大内存消耗等特性进行推理,要求架构描述提供关于单个元素的特性及其资源消耗的信息,以及它们之间的交互模式 —— 即它们彼此之间的行为方式。在本节中,我们将为您提供关于为获得这些好处需要记录哪些类型内容的指导。
有两种用于记录行为的符号表示:面向跟踪的和综合的。
“跟踪” 是活动或交互的序列,描述系统在特定状态下对特定触发事件的响应。跟踪描述了系统结构元素之间的一系列活动或交互。虽然可以想象描述所有可能的跟踪以生成相当于全面行为模型的结果,但面向跟踪的文档并不会真的试图这样做。在这里,我们描述了四种用于记录跟踪的符号表示:用例图、序列图、通信图和活动图。虽然还有其他符号表示可用(如消息序列图、时序图和业务流程执行语言),但我们选择了这四种作为面向跟踪符号表示的代表性示例。
-
“用例图” 描述了参与者如何使用系统来实现他们的目标;它们经常被用于捕获系统的功能需求。UML 为用例图提供了图形符号,但没有指定用例的文本应如何编写。UML 用例图是提供参与者和系统行为概述的好方法。其描述是文本形式的,应包括以下项目:用例名称和简要描述、启动用例的参与者(主要参与者)、参与用例的其他参与者(次要参与者)、事件流、替代流和不成功的情况。
-
UML “序列图” 显示了从结构文档中提取的元素实例之间的交互序列。在设计系统时,它对于确定需要定义接口的位置很有用。序列图仅显示参与正在记录的场景的实例。它有两个维度:垂直方向表示时间,水平方向表示各种实例。交互按时间顺序从上到下排列。图 22.2 是一个序列图的简单示例,说明了基本的 UML 符号表示。序列图没有明确显示并发性。如果这是你的目标,请使用活动图代替。
图 22.2 UML 序列图的简单示例
如图 图 22.2 所示,对象(即元素实例)有一条生命线,在时间轴上绘制为垂直虚线。序列通常由最左边的参与者启动。实例通过发送消息进行交互,消息显示为水平箭头。消息可以是通过网络发送的消息、函数调用或通过队列发送的事件。消息通常映射到接收者实例接口中的资源(操作)。实线上的填充箭头表示同步消息,而开放箭头表示异步消息。虚线箭头是返回消息。生命线上的执行出现条表示实例正在处理或被阻塞等待返回。
-
UML “通信图” 显示了交互元素的图,并为每个交互标注一个表示其顺序的数字。与序列图类似,通信图中显示的实例是伴随的结构文档中描述的元素。通信图在验证架构是否能够满足功能需求的任务中很有用。当理解并发动作很重要时,如进行性能分析时,这种图就没有用了。
-
UML “活动图” 类似于流程图。它们将业务流程显示为一系列步骤(称为动作),并包括表示条件分支和并发性的符号,以及显示发送和接收事件。动作之间的箭头表示控制流。可选地,活动图可以指示执行动作的架构元素或参与者。值得注意的是,活动图可以表示并发性。分叉节点(描绘为与流箭头垂直的粗条)将流分成两个或更多个并发的动作流。这些并发流可以稍后通过连接节点(也描绘为垂直条)同步为单个流。连接节点在继续之前等待所有输入流完成。
与序列图和通信图不同,活动图不显示在特定对象上执行的实际操作。因此,这些图对于广泛描述特定工作流中的步骤很有用。条件分支(用菱形符号表示)允许单个图表示多个跟踪,尽管活动图通常不会尝试显示所有可能的跟踪或系统(或其一部分)的完整行为。图 22.3 显示了一个活动图。
图 22.3 活动图
与跟踪符号表示相反,“综合的” 符号表示显示结构元素的完整行为。有了这种类型的文档,就可以推断从初始状态到最终状态的所有可能路径。状态机是许多全面符号表示使用的一种形式体系。这种形式体系表示架构元素的行为,因为每个状态都是可能导致该状态的所有历史的抽象。状态机语言允许您用对交互的约束以及对内部和环境刺激的定时反应来补充系统元素的结构描述。
UML 状态机图允许您在给定特定输入的情况下跟踪系统的行为。这样的图使用方框表示状态,使用箭头表示状态之间的转换。因此,它对架构的元素进行建模,并有助于说明它们的运行时交互。图 22.4 是一个状态机图的示例,显示了汽车音响系统的状态。
图 22.4 汽车音响系统的 UML 状态机图
状态机图中的每个转换都用引起转换的事件进行标注。例如,在 图 22.4 中,转换对应于驾驶员可以按下的按钮或影响巡航控制系统的驾驶动作。可选地,转换可以指定一个保护条件,它括在括号中。当与转换对应的事件发生时,评估保护条件,并且只有在此时保护条件为真时转换才被启用。转换也可以有结果,称为动作或效果,用斜线表示。当存在动作时,它表示当转换发生时,斜线后面的行为将被执行。状态也可以指定进入和退出动作。
22.6 视图之外
除了视图和行为之外,关于架构的综合信息将包括以下内容:
-
视图之间的映射。由于架构的所有视图都描述同一个系统,所以任何两个视图有很多共同之处是合理的。组合视图(如 第 22.4 节 所述)会产生一组视图。阐明这些视图之间的关联可以帮助读者深入了解架构作为一个统一的概念整体是如何工作的。
架构中跨视图的元素之间的关联通常是多对多的。例如,每个模块可能映射到多个运行时元素,每个运行时元素可能映射到多个模块。
视图到视图的关联可以方便地用表格捕获。要创建这样的表格,以某种方便的查找顺序列出第一个视图的元素。表格本身应该用注释或介绍来解释它所描绘的关联,即两个视图中的元素之间的对应关系。例如,从组件和连接器视图映射到模块视图的 “由…… 实现”,从模块视图映射到组件和连接器视图的 “实现”,从分解视图映射到分层视图的 “包含在…… 中” 等等。
-
记录模式。如果你在设计中使用模式,如 第 20 章 所建议的,这些模式应该在文档中被识别出来。首先,记录正在使用给定模式这一事实。然后说明为什么选择这种解决方案方法 —— 为什么该模式适合手头的问题。使用模式涉及做出连续的设计决策,最终导致该模式的实例化。这些设计决策可能表现为新实例化的元素以及它们之间的关系,而这些反过来又应该在结构视图中被记录下来。
-
一个或多个上下文图。上下文图展示了系统或系统的一部分如何与其环境相关联。这个图的目的是描绘一个视图的范围。这里的 “上下文” 是指系统(的一部分)与之交互的环境。环境中的实体可以是人类、其他计算机系统或物理对象,如传感器或受控设备。可以为每个视图创建一个上下文图,每个图展示不同类型的元素如何与系统的环境交互。上下文图对于展示系统或子系统如何与其环境交互的初始画面很有用。
-
可变性指南。可变性指南展示了如何运用此视图中所示架构的任何可变点。
-
基本原理。基本原理解释了视图中反映的设计为何会如此。这一部分的目标是解释设计为何具有目前的形式,并提供一个令人信服的论据说明它是合理的。第 22.7 节 更详细地描述了记录基本原理的方法。
-
词汇表和首字母缩略词列表。你的架构可能包含许多专业术语和首字母缩略词。为你的读者解码这些内容将确保所有的利益相关者都在说同一种语言,可以这么说。
-
文档控制信息。列出发布组织、当前版本号、发布日期和状态、变更历史以及向文档提交变更请求的程序。通常,这些信息会在文档的前言部分被捕获。变更控制工具可以提供很多这样的信息。
22.7 记录基本原理
在设计时,你会做出重要的设计决策以实现每次迭代的目标。这些设计决策包括:
- 从几个备选方案中选择一个设计概念;
- 通过实例化所选设计概念来创建结构;
- 建立元素之间的关系并定义接口;
- 分配资源(例如,人员、硬件、计算资源)。
当你研究代表架构的图表时,你看到的是一个思考过程的最终产物,但并不总是能轻易理解为实现这个结果而做出的决策。记录超出所选元素、关系和属性表示的设计决策对于理解你如何得出这个结果至关重要;换句话说,它列出了设计的基本原理。
当你的迭代目标涉及满足一个重要的质量属性场景时,你所做的一些决策将在实现场景响应度量方面发挥重要作用。因此,你应该非常小心地记录这些决策:它们对于促进对你所创建的设计进行分析、促进实现以及在更晚的时候帮助理解架构(例如在维护期间)是必不可少的。鉴于大多数设计决策都是 “足够好” 的,很少是最优的,你还需要证明所做决策的合理性,并记录与你的决策相关的风险,以便可以对其进行审查并可能重新审视。
你可能会觉得记录设计决策是一项繁琐的任务。然而,根据正在开发的系统的关键程度,你可以调整记录的信息量。例如,要记录最少的信息,你可以使用一个简单的表格,如 表 22.4。如果你决定记录比这个最小值更多的信息,以下信息可能会很有用:
- 产生了什么证据来证明决策的合理性?
- 谁做了什么?
- 为什么采取捷径?
- 为什么要做出权衡?
- 你做了哪些假设?
表 22.4 记录设计决策的示例表格
设计决策和位置 | 基本原理和假设(包括被舍弃的备选方案) |
---|---|
在 TimeServerConnector(时间服务器连接器)和 FaultDetectionService(故障检测服务)中引入“并发”(策略)。 | 应该引入并发机制,以便能够同时接收和处理多个事件(陷阱)。 |
在通信层中通过引入消息队列来使用“消息传递”模式。 | 虽然使用消息队列会带来性能损失,但选择消息队列是因为某些实现具有高性能,而且这将有助于支持质量属性场景 QA-3。 |
. . . | . . . |
就像我们建议你在确定元素时记录职责一样,你也应该在做出设计决策时记录下来。如果你等到以后再做,你就不会记得你为什么要做这些事情。
22.8 架构的利益相关者
在 第 2 章 中,我们说过架构的关键目的之一是实现利益相关者之间的沟通。在本章中,我们提到架构文档是为架构利益相关者服务而产生的。那么他们是谁呢?
利益相关者的集合会因组织和项目而异。本节中的利益相关者列表仅为示意,并非完整无缺。作为架构师,你的主要职责之一是确定项目的真正利益相关者。同样,我们在此为每个利益相关者列出的文档需求是典型的,但并非确定不变的。你需要将以下讨论作为起点,并根据项目需求进行调整。
架构的主要利益相关者包括:
-
项目经理:关心进度、资源分配,可能还有出于商业原因发布系统子集的应急计划。为了制定进度计划,项目经理需要了解要实现的模块以及实现的顺序,还有一些关于模块复杂性的信息,例如职责列表,以及它们对其他模块的依赖关系。这些依赖关系可能暗示了实现的特定顺序。项目经理对任何元素的设计细节或确切接口并不感兴趣,除非知道这些任务是否已完成。然而,这个人对系统的总体目标和约束、与其他系统的交互(这可能暗示项目经理必须建立的组织间接口)以及必须获取的硬件环境感兴趣。项目经理可能会创建或协助创建工作分配视图,在这种情况下,他或她将需要一个分解视图来完成。因此,项目经理可能对以下视图感兴趣:
- 模块视图:分解视图、使用视图和 / 或分层视图。
- 分配视图:部署和工作分配。
- 其他:显示交互系统以及系统概述和目的的顶级上下文图。
-
开发团队成员:架构为他们提供了行动指南,对他们的工作方式进行了约束。有时开发人员会被分配负责一个他们没有实现的元素,例如一个现成的商业产品或一个遗留元素。仍然需要有人对该元素负责,以确保它按预期执行并在必要时进行定制。这个人会想知道以下信息:
-
系统背后的总体思路。虽然该信息属于需求领域而非架构领域,但一个顶级上下文图或系统概述可以在很大程度上提供必要的信息。
-
开发人员被分配实现哪些元素,即功能应在哪里实现。
-
分配元素的详细信息,包括其必须操作的数据模型。
-
分配部分与之交互的元素以及这些接口是什么。
-
开发人员可以利用的代码资产。
-
必须满足的约束,例如质量属性、遗留系统接口和预算(资源或财务)。
因此,开发人员可能希望看到以下内容:
-
模块视图:分解、使用和 / 或分层视图以及泛化视图。
-
组件和连接器(C&C)视图:各种视图,显示开发人员被分配的组件以及他们与之交互的组件。
-
分配视图:部署、实现和安装视图。
-
其他:系统概述;包含开发人员被分配的模块的上下文图;开发人员的元素的接口文档以及他们与之交互的元素的接口文档;实现所需可变性的可变性指南;以及基本原理和约束。
-
-
测试人员和集成人员:对于他们来说,架构指定了必须组合在一起的各个部分的正确黑盒行为。黑盒测试人员需要访问元素的接口文档。集成人员和系统测试人员需要查看接口集合、行为规范和使用视图,以便他们可以处理增量子集。因此,测试人员和集成人员可能希望看到以下视图:
- 模块视图:分解视图、使用视图和数据模型。
- C&C 视图:所有视图。
- 分配视图:部署视图;安装视图;以及实现视图,以找出构建模块的资产在哪里。
- 其他:显示要测试或集成的模块的上下文图;模块的接口文档和行为规范以及他们与之交互的元素的接口文档。
测试人员和集成人员值得特别关注,因为一个项目在测试上花费大约一半的总工作量并不罕见。确保一个顺利、自动化且无错误的测试过程将对项目的总体成本产生重大积极影响。
-
必须与该系统互操作的其他系统的设计人员也是利益相关者。对于这些人来说,架构定义了提供和要求的操作集以及它们的操作协议。这些利益相关者可能希望看到以下工件:
- 与他们的系统将交互的那些元素的接口文档,如在模块视图和 / 或 C&C 视图中找到的。
- 与他们的系统将交互的系统的数据模型。
- 来自各种视图的显示交互的顶级上下文图。
-
维护人员将架构作为维护活动的起点,揭示潜在变更将影响的区域。维护人员希望看到与开发人员相同的信息,因为两者都必须在相同的约束下进行更改。但是维护人员还希望看到一个分解视图,以便他们能够确定需要进行更改的位置,也许还需要一个使用视图来帮助他们构建影响分析,以充分了解变更的影响。此外,他们希望看到设计基本原理,这将使他们能够从架构师的原始思考中受益,并通过识别已经被舍弃的设计备选方案来节省时间。因此,维护人员可能希望看到与系统开发人员相同的视图。
-
最终用户不需要看到架构,毕竟架构在很大程度上对他们是不可见的。然而,通过检查架构,他们通常可以对系统、系统的功能以及如何有效地使用它获得有用的见解。如果最终用户或他们的代表审查你的架构,你可能能够发现否则直到部署才会被注意到的设计差异。为了达到这个目的,最终用户可能对以下视图感兴趣:
- C&C 视图:强调控制流和数据转换的视图,以查看输入如何转换为输出;处理感兴趣属性(如性能或可靠性)的分析结果。
- 分配视图:部署视图,以了解功能如何分配到用户与之交互的平台上。
- 其他:上下文图。
-
分析师对设计是否满足系统的质量目标感兴趣。架构为架构评估方法提供素材,并且必须提供评估质量属性所需的信息。例如,架构包括驱动诸如速率单调实时可调度性分析、可靠性框图、仿真和仿真生成器、定理证明器和模型检查器等分析工具的模型。这些工具需要有关资源消耗、调度策略、依赖关系、组件故障率等信息。由于分析可以涵盖几乎任何主题领域,分析师可能需要访问架构文档任何部分中记录的信息。
-
基础设施支持人员设置和维护支持系统的开发、集成、暂存和生产环境的基础设施。可变性指南对于帮助设置软件配置管理环境特别有用。基础设施支持人员可能希望看到以下视图:
- 模块视图:分解视图和使用视图。
- C&C 视图:各种视图,以查看将在基础设施上运行的内容。
- 分配视图:部署视图和安装视图,以查看软件(包括基础设施)将在哪里运行;实现视图。
- 其他:可变性指南。
-
将来的架构师是架构文档最热心的读者,对所有内容都有既得利益。一段时间后,你或者你的继任者(当你得到晋升并被分配到一个更复杂的项目时)将想知道所有关键设计决策以及做出这些决策的原因。将来的架构师对所有内容都感兴趣,但他们将特别渴望获得全面而坦诚的基本原理和设计信息。并且,请记住,那个将来的架构师可能就是你!不要期望记住你现在正在做出的所有这些微小的设计决策。记住,架构文档是你写给未来自己的一封情书。
22.9 实际考虑
到目前为止,本章一直关注架构文档应包含的信息。然而,除了架构文档的内容之外,还有一些涉及文档形式、分发和演变的问题。在本节中,我们将讨论其中的一些问题。
建模工具
有许多商业上可用的建模工具可用于以定义的符号规范架构构造;SysML 是一种广泛使用的选择。许多这些工具提供针对工业环境中实际大规模使用的功能:支持多用户的界面、版本控制、模型的语法和语义一致性检查、支持模型与需求或模型与测试之间的跟踪链接,在某些情况下,还支持自动生成实现模型的可执行源代码。在许多项目中,这些都是必备功能,因此工具的购买价格(在某些情况下并非微不足道)应与项目自行实现这些功能的成本进行评估。
在线文档、超文本和维基
系统的文档可以构建为链接的网页。面向网络的文档通常由一些短页面(创建为适合一个屏幕)组成,具有更深的结构。一个页面通常提供一些概述信息,并具有指向更详细信息的链接。
使用维基等工具,可以创建一个许多利益相关者都可以贡献的 “共享” 文档。托管组织需要决定它希望给予各种利益相关者哪些权限;所使用的工具必须支持所选的权限策略。对于架构文档,我们希望选定的利益相关者对架构进行评论并添加澄清信息,但我们只希望选定的团队人员能够实际更改它。
遵循发布策略
项目的开发计划应指定保持重要文档(包括架构文档)为最新状态的流程。文档工件应像任何其他重要项目工件一样进行版本控制。架构师应计划发布文档版本以支持主要项目里程碑,这通常意味着在里程碑之前足够早地发布,以便开发人员有时间将架构投入使用。例如,可以在每次迭代或冲刺结束时或随着每个增量发布向开发团队提供修订后的文档。
记录动态变化的架构
当你的网络浏览器遇到一种它从未见过的文件类型时,很可能它会上网搜索并下载适当的插件来处理该文件,安装它,并重新配置自身以使用它。无需关闭,更不用说经历编码 - 集成 - 测试的开发周期,浏览器就能够通过添加新组件来改变自己的架构。
利用动态服务发现和绑定的面向服务的系统也具有这些特性。更具挑战性的高度动态、自组织和具有反射性(意味着自我意识)的系统已经存在。在这些情况下,在任何静态架构文档中都无法确定相互交互的组件的身份,更不用说它们的交互了。
从文档角度来看同样具有挑战性的另一种架构动态性存在于以极快速度重建和重新部署的系统中。一些开发机构,例如负责商业网站的那些,每天多次构建他们的系统并使其 “上线”。
无论是在运行时发生变化还是由于高频发布和部署周期而发生变化,所有动态架构在文档方面都有一个共同点:它们的变化速度比文档周期快得多。在任何一种情况下,在新的架构文档生成、审查和发布之前,没有人会拖延事情。
即便如此,了解这些不断变化的系统的架构与了解遵循更传统生命周期的系统的架构一样重要,甚至可以说更重要。如果你是高度动态环境中的架构师,你可以这样做:
- 记录关于你的系统所有版本的真实情况。你的网络浏览器在需要新插件时不会随便抓取任何软件;插件必须具有特定的属性和特定的接口。并且那个新软件也不是随便插在任何地方,而是插在架构中的预定位置。记录这些不变量。这个过程可能会使你记录的架构更像是对任何符合要求的系统版本都必须遵循的约束或指南的描述。这没关系。
- 记录架构被允许改变的方式。在前面提到的例子中,这通常意味着添加新组件并用新实现替换组件。在 第 22.6 节 中讨论的可变性指南中进行此操作。
- 自动生成接口文档。如果你使用明确的接口机制,如协议缓冲区(在 第 15 章 中描述),那么总是有组件接口的最新定义;否则,系统将无法工作。将这些接口定义合并到数据库中,以便有修订历史记录可用,并且可以搜索接口以确定哪些信息在哪些组件中使用。
可追溯性
当然,架构并非孤立存在,而是存在于关于正在开发的系统的信息环境中,其中包括需求、代码、测试、预算和进度等等。每个这些领域的提供者都必须问自己:“我的部分正确吗?我怎么知道?” 这个问题在不同的领域有不同的具体形式;例如,测试人员会问:“我在测试正确的东西吗?” 正如我们在 第 19 章 中看到的,架构是对需求和业务目标的响应,它的 “我的部分正确吗?” 问题的版本是确保这些需求和目标得到满足。可追溯性意味着将特定的设计决策与导致这些决策的特定需求或业务目标相链接,并且这些链接应在文档中捕获。如果在一天结束时,所有架构需求陈述(ASR)都在架构的跟踪链接中得到说明(“覆盖”),那么我们就可以确定架构部分是正确的。跟踪链接可以非正式地表示(例如,一个表格),也可以在项目的工具环境中以技术方式支持。在任何一种情况下,跟踪链接都应该是架构文档的一部分。
22.10 小结
编写架构文档与其他类型的写作很相似。黄金法则是:了解你的读者。你必须理解文档的用途以及文档的受众。架构文档作为各种利益相关者之间沟通的手段:向上到管理层,向下到开发人员,横向到同行。
架构是一个复杂的产物,最好通过聚焦于特定的视角(称为视图)来表达,这些视角取决于要传达的信息。你必须选择要记录的视图,并选择用于记录这些视图的符号。这可能涉及组合有很大重叠的各种视图。你不仅要记录架构的结构,还要记录其行为。
此外,你应该在文档中记录视图之间的关系、你使用的模式、系统的上下文、架构中内置的任何可变性机制以及你的主要设计决策的基本原理。
在创建、维护和分发文档方面还有其他实际考虑因素,例如选择发布策略、选择像维基这样的传播工具以及为动态变化的架构创建文档。
22.11 扩展阅读
《记录软件架构:视图及其他》[Clements 10a] 对本章中描述的架构文档方法进行了全面阐述。它详细介绍了众多不同的视图及其表示法。还描述了如何将文档整理成一个连贯的整体。附录 A 涵盖了使用统一建模语言(UML)来记录架构和架构信息。
ISO/IEC/IEEE 42010:2011(简称 “eye-so-forty-two-oh-ten”)是 ISO(和 IEEE)标准《系统与软件工程:架构描述》。该标准围绕两个关键理念:架构描述的概念框架,以及关于在任何符合 ISO/IEC/IEEE 42010 的架构描述中必须包含哪些信息的说明,使用由利益相关者关注点驱动的多个观点。
AADL(addl.info)是一种架构描述语言,已成为记录架构的 SAE 标准。SAE 是航空航天、汽车和商用车行业工程专业人员的组织。
SysML 是一种通用系统建模语言,旨在支持系统工程应用的广泛分析和设计活动。它的定义使得可以指定足够的细节以支持各种自动化分析和设计工具。SysML 标准由对象管理组织(OMG)维护;这种语言是 OMG 与国际系统工程委员会(INCOSE)合作开发的。SysML 是作为 UML 的一个概要开发的,这意味着它重用了很多 UML,但也提供了满足系统工程师需求所需的扩展。关于 SysML 的大量信息可在网上获取,但 [Clements 10a] 的附录 C 讨论了如何使用 SysML 来记录架构。在本书付印时,SysML 2.0 正在开发中。
在 [Cervantes 16] 中可以找到一个在设计时记录架构决策的扩展示例。
22.12 问题讨论
1. 访问你最喜欢的开源系统的网站并查找其架构文档。有什么内容?缺少什么?这将如何影响你为这个项目贡献代码的能力?
2. 银行对安全性有充分的理由保持谨慎。草拟一份自动取款机(ATM)所需的文档,以便对其安全架构进行推理。
3. 如果你正在设计一个基于微服务的架构,你需要记录哪些元素、关系和属性才能对端到端延迟或吞吐量进行推理?
4. 假设你的公司刚刚收购了另一家公司,你被赋予将你公司的一个系统与另一家公司的类似系统进行合并的任务。你希望看到另一个系统架构的哪些视图?为什么?你会要求两个系统都有相同的视图吗?
5. 你会在什么时候选择使用跟踪表示法来记录行为,什么时候会使用综合表示法?每种方法你能获得什么价值?又需要付出什么努力?
6. 你会将项目预算的多少用于软件架构文档编制?为什么?你如何衡量成本和收益?如果你的项目是一个安全关键系统或高安全性系统,这会如何变化?