架构的结构和视角
回顾在上一小节中有介绍,软件系统其实就是一系列结构的集合,但是,尤其是现代的系统通常都是由许多结构组成的,从一开始就想掌握一个系统的全部细节是很复杂的一件事情,这时候就需要从不同的视角(view)来剖析,达到简化问题的目的。
结构和视图
- 视图(view)是对结构(structure)中具有代表意义的部分元素和关系进行抽取,也可以说,视图是对结构的精简。
- 通常,架构师设计结构,但是真正记录在文档里的是这个结构的精简版——视图。
三种结构
上一节大概说过,组成架构的结构类型大概有三种:模块结构,组件-连接件结构,分配结构。
-
模块结构
- 模块结构更多的是对系统静态的划分,每一个模块都有自己特定的功能,一般不会去考虑在执行过程中那些交互式响应。
- 模块视图回答了以下问题
- 给每一个模块分配的最主要的功能是什么?
- 一个模块可以使用哪些该模块之外的元素?
- 实际会使用或者依赖哪些其它的软件?
- 模块之间的关系,泛化?继承?
- 当分配给模块的功能发生改变时会对整个系统产生哪些影响?-作为扩展
-
组件-连接件结构
- 与模块结构相反,组件-连接件结构更多的是对系统运行过程中的划分,组件(component)可能是服务器,客户端等,连接件(connector)作为组件间的连接机制可能是回调函数,异步处理等等。
- 组件-连接件视图可以帮助我们回答以下几个问题
- 在运行过程中的主要的组件是什么,以及它们之间是怎样交互的?
- 共享数据的存储问题
- 系统中的哪些部分是冗余的?
- 在系统中数据是怎样处理的?
- 系统中的哪些部分是可以并行执行的?
- 系统的架构能否在执行过程中进行改变,如果可以,具体怎么实现?
- 系统运行中的属性,比如性能,安全,可执行性等等-作为扩展
-
分配结构
- 就像前面说的,软件系统其实是有结构的,但是对于底层的硬件,比如CPU,文件系统,网络等等这些是没有结构的,分配结构主要就是把有结构的软件元素映射到外部环境中。
- 分配视图帮助我们回答以下问题:
- 软件元素主要运行在什么处理器上?
- 元素在运行构建和测试过程中存储在哪个目录的哪个文件里?
- 软件元素和开发团队之间的关系
结构作为切入点
每一个结构都可以推出一些相关的质量属性,比如:
- 模块的使用结构,反映了模块之间的相互依赖调用,该结构对系统的可扩展性有很大的影响。
- 并发结构,反映了系统内部的可并发性,该结构对系统是否会因为资源的占有进入死锁状态或者出现性能上的瓶颈有很大影响。
- 部署结构,对最终的系统的性能,可用性和安全性有很大影响。
每一个结构都给架构提供了一个不同的设计切入点,所以合理的设计结构可以让系统拥有相应的好的属性。
一些有用的模块结构
- 分解结构(Decomposition structure)
- 模块之间是' is-a-submodule-of '的关系,也就是模块一直被分解直到最容易被理解为止,这些结构中的模块在开始设计的时候是相同的,随着架构师列举出软件应该实现的功能,就会把这些功能分配给不同的模块来满足后序的设计和最终的实现。模块会有相应的产出,如结构,代码,测试计划等等。结构模型通过把可能出现的改变局部话来保证了系统的可修改性,变更只会在一小部分的模型中出现。这个结构通常被用于开发系统的基础部分,包括文档的架构,项目的集成和测试方案。分解结构中继续细化的单元被称为"片段"或者"子系统"。
- 使用结构(use structure)
- 在这个重要但是经常被忽视的结构中,单元同样表示模块或者是类,单元之间用通过使用关系连接,使用关系是对依赖关系的一种特化。一个软件单元需要调用另一个软件单元的某一个函数,来达到自己的功能目标。使用结构由于可以使系统被扩展和添加功能或者提取系统有用的核心功能,通常被用于那些工程系统。
- 层结构(layer structure)
- 在这个结构中,模块被称为层,一个层可以被视为一个通过一个接口提供的一系列紧密结合的服务的虚拟机。层与层之间的调用是有严格的规定的,一个层只允许使用和它邻接且在它下面的那个层。层结构可以增加系统的可移植性和跨平台的能力。
- 类/泛化结构(Class/generalization structure)
- 在这个结构中的模块单元被称为是类,模块之间的关系可以是继承或者实例化。这个视图产生一系列具有相近行为或者能力和参数的不同的单元,类结构允许一个单元去重用另一单元或者在原来单元的基础上增加功能,如果存在关于这个项目的某个文档是按照面向对象分析和设计的,那么大多数都是类结构。
- 数据结构
- 数据模型用术语数据实体和它们之间的关系描述了静态的信息结构,比如,有些关系可能会强制规定一个客户必须拥有一个以上的账户等等。
一些有用的C&C结构
组件-连接件结构反映了系统的实时视图,在这些结构中,上述的模块都被编译为可执行的形式,所有的组件-连接件结构和基于模块的结构是正交关系并用于处理正在运行系统的动态层面。组件-连接件间的关系显示了组件和连接件是如何切合在一起的,这个关系被称为附件(attachment),连接件可以只是最普通的结构比如'调用',有用的C&C结构包括以下:
- 服务结构(Service structure)
- 结构中的单元被称为是服务,这些服务通过服务协的机制比如SOAP进行交互操作。服务结构是一种很重要的结构,它使得我们可以异步或者独立的对组件进行开发。
- 并发结构(Concurrency structure)
- 对于这个组件-连接件结构可以使架构师确定并发的几率并且明确可能发生资源冲突的地方。该结构中的单元称为组件,连接件是组件间的沟通机制,组件按照一定的规则被整理在不同的逻辑线程(logical thread)中,一个逻辑线程是一系列的计算过程并且在后续的设计过程中该逻辑线程可以被分配到单独一个物理线程中,并发结构在设计过程的初期被使用,用于发现和识别并发执行中的需求。
一些有用的分配结构
分配结构定义了那些属于C&C结构和模块结构的元素怎样映射到非软件的事物上,比如硬件,团队,文件系统等,有用的分配结构包括:
- 部署结构(Deployment structure)
- 部署结构展示了软件是如何被分配到硬件进程和通信部件上的,该结构中的元素包括软件元素(通常是组件-连接件中的一个进程),硬件实体,和通信连接,元素间的关系是 allocated-to,表明了软件元素处于哪一个物理单元之上并且如果分配是动态的还包括 migrates-to 的关系,这个结构可以被用于解释性能,数据集成,安全和可用性,在分布式和并行式系统中非常有用。
- 实现结构(Implementation structure)
- 实现结构展示了软件元素(通常是模块)在系统开发,集成或者配置控制环境中是如何映射到文件结构中的,这对开发和构建过程的管理来说非常重要。
- 工作分配结构(Work assignment structure)
- 工作分配结构把实现和集成模块的任务以团队为单位进行分配,有了结构分配图使得谁做什么工作变的清晰,同时架构师知道每一个团队的专长,在大型的多参与者的开发项目中,工作分配结构通常是将功能单元只分配给一个团队实现,其它需要开发相同功能的团队进行功能的调用即可,而不是让每一个需要该功能的团队把它都开发一遍,这个结构同样规定可团队之间的沟通方式,比如,邮件...
将结构互相之间进行联系
每一个结构都提供了一种不同的设计和处理系统的观点,并且从每一个结构自身出发它们都是有用且有效的,尽管每一个结构都分别对应了系统的不同方面,但是这些结构本身并不是孤立的。某一个结构中的元素可能会和其它结构中的元素相联系,所以我们需要对这些联系作相应的解释,比如,在分解结构中的一个模块可能会被解释为一个组件-连接件,一个组件-连接件中的一部分,或者是一个组件-连接件中的一些组件,这些反应了系统在运行过程中的自我改变的能力,总的来说,结构与结构之间的映射是多对多的。
如图1.2所示是一个有关两个结构之间是怎样进行联系的简单的例子,图的左边显示了一个小型CS系统的模块分解图(module decomposition view),在这个系统中,有两个模块必须被实现,分别是客户端软件和服务器端软件,图的右边显示了这个CS架构系统的组件-连接件的视图,从图中也可以看出在运行过程中有十个客户端和服务器相连接,所以这个小的CS架构的系统有两个模块和十一个组件(十个连接件)。
虽然在图中明显的可以看出在分解结构和CS结构中的元素是非常相似的,但是这两个视图的用途却不尽相同,比如,右边的视图可以被用于性能的分析,瓶颈的预测和对网络流量的管理,如果使用左边的视图来做以上事情是十分困难的。
在个人的项目中有时侯会决定一个主要的结构,并在有可能的情况下,用该主要结构来描述其它结构,通常将主要结构规定为模块的分解结构,这样做是因为模块的分解结构跟有利于衍生出整个项目的结构,因为分解结构反映了开发团队的结构,在有些其他项目中,主要的结构也可能是C&C结构,用来显示系统的功能或者实现的主要质量属性。
越少越好
不是所有的系统都需要考虑许多的架构结构,系统越庞大,结构间显著的差异就比较多,但对于小系统来说,结构的数量是相对比较少的,通常只是处理一个组件-连接件结构。如果在小系统中只有一个进程,那么进程结构就可以相应的退化为一个节点并且不需要明确的表示在设计中。如果小系统只是基于一个处理器实现,那么对应的部署结构也不需要被考虑。总的来说,当且仅当一个结构会给开发过程带来好处,比如简化开发或者维持开销时,该结构才会被设计并记录。
选择哪一个结构
我们已经对许多有用的结构架构进行了简单明确的描述,但是在实际开发过程中,应该选择哪一个架构,哪一个架构应该被记录,当然不是所有的,对于现在来说,只是根据架构的可用性和对系统重要质量属性的影响进行选择,再挑选出一个表现最好的架构进行使用。