第9章 软件架构设计
像学写文章一样,在学会字、词、句之后,就应上升到段落,就应追求文章的“布局谋篇”,这就是架构。通俗地讲,软件架构设计就是软件系统的“布局谋篇”。
人们在软件工程实践中,逐步认识到了软件架构的重要性,从而开辟了一个崭新的研究领域。软件架构的研究内容主要涉及软件架构描述、软件架构设计、软件架构风格、软件架构评价和软件架构的形成方法等。
软件设计人员学习软件架构知识旨在站在较高的层面上整体地解决好软件的设计、复用、质量和维护等方面的实际问题。
9.1 软件架构概述
软件架构是软件抽象发展到一定阶段的产物,从编程的角度,可以清晰地看到软件抽象层次和表达工具的发展历史。
20 世纪 60 年代是子程序的年代:出现了原始的软件架构,即子程序,并以程序间的调用为连接关系。
20 世纪 70 年代是模块化的年代:出现了数据流分析、实体—关系图(E-R 图)、信息隐藏等工具和方法,软件的抽象层次发展到了模块级。
20 世纪 80 年代是面向对象的年代:基于模块化的编程语言进一步发展成面向对象的语言,继承性地增加了一种新元素之间的连接关系。
20 世纪 90 年代是框架的年代:标准的基于对象的架构以框架的形式出现了。如电子数据表、文档、图形图像、音频剪辑等可互换的黑箱对象,可以相互嵌入。
当前(最近 10 年来):中间件和 IT 架构作为标准平台出现,用可购买可复用的元素来构建系统,同时,基于架构的开发方法和理论不断成熟。
9.1.1 软件架构的定义
软件架构仍在不断发展中,还没有形成一个统一的、公认的定义,这里仅举出几个较权威的定义。
定义 1:软件或计算机系统的软件架构是该系统的一个(或多个)结构,而结构由软件元素、元素的外部可见属性及它们之间的关系组成。
定义 2:软件架构为软件系统提供了一个结构、行为和属性的高级抽象,由构成系统的元素的描述、这些元素的相互作用、指导元素集成的模式及这些模式的约束组成。
定义 3:软件架构是指一个系统的基础组织,它具体体现在:系统的构件,构件之间、构件与环境之间的关系,以及指导其设计和演化的原则上。(IEEE1471- 2000)
前两个定义都是按“元素—结构—架构”这一抽象层次来描述的,它们的基本意义相同,其中定义 1 较通俗,因此,本章采用这一定义。该定义中的“软件元素”是指比“构件”
更一般的抽象,元素的“外部可见属性”是指其他元素对该元素所做的假设,如它所提供的服务、性能特征等。
为了更好地理解软件架构的定义,特作如下说明:
- (1)架构是对系统的抽象,它通过描述元素、元素的外部可见属性及元素之间的关系来反映这种抽象。因此,仅与内部具体实现有关的细节是不属于架构的,即定义强调元素的“外部可见”属性。
- (2)架构由多个结构组成,结构是从功能角度来描述元素之间的关系的,具体的结构传达了架构某方面的信息,但是个别结构一般不能代表大型软件架构。
- (3)任何软件都存在架构,但不一定有对该架构的具体表述文档。即架构可以独立于架构的描述而存在。如文档已过时,则该文档不能反映架构。
- (4)元素及其行为的集合构成架构的内容。体现系统由哪些元素组成,这些元素各有哪些功能(外部可见),以及这些元素间如何连接与互动。即在两个方面进行抽象:在静态方面,关注系统的大粒度(宏观)总体结构(如分层);在动态方面,关注系统内关键行为的共同特征。
- (5)架构具有“基础”性:它通常涉及解决各类关键的重复问题的通用方案(复用性),以及系统设计中影响深远(架构敏感)的各项重要决策(一旦贯彻,更改的代价昂贵)。
- (6)架构隐含有“决策”,即架构是由架构设计师根据关键的功能和非功能性需求(质量属性及项目相关的约束)进行设计与决策的结果。不同的架构设计师设计出来的架构是不一样的,为避免架构设计师考虑不周,重大决策应经过评审。特别是架构设计师自身的水平是一种约束,不断学习和积累经验才是摆脱这种约束走向自由王国的必经之路。
在设计软件架构时也必须考虑硬件特性和网络特性,因此,软件架构与系统架构二者间的区别其实不大。但是,在大多情况下,架构设计师在软件方面的选择性较之硬件方面,其自由度大得多。因此,使用“软件架构”这一术语,也表明了一个观点:架构设计师通常将架构的重点放在软件部分。
将软件架构置于商业背景中进行观察,可以发现软件架构对企业非常重要。
- (1)影响架构的因素。软件系统的项目干系人(客户、用户、项目经理、程序员、测试人员、市场人员等)对软件系统有不同的要求开发组织(项目组)有不同的人员知识结构、架构设计师的素质与经验、当前的技术环境等方面都是影响架构的因素。
这些因素通过功能性需求、非功能性需求、约束条件及相互冲突的要求,影响架构设计师的决策,从而影响架构。
- (2)架构对上述诸因素具有反作用,例如,影响开发组织的结构。架构描述了系统的大粒度(宏观)总体结构,因此可以按架构进行分工,将项目组为几个工作组,从而使开发有序;影响开发组织的目标,即成功的架构为开发组织提供了新的商机,这归功于:系统的示范性、架构的可复用性及团队开发经验的提升,同时,成功的系统将影响客户对下一个系统的要求等。这种反馈机制构成了架构的商业周期。
9.1.2 软件架构的重要性
从技术角度看,软件架构的重要性表现为如下几方面。
- (1)项目关系人之间交流的平台。软件系统的项目关系人分别关注系统的不同特性,而这些特性都由架构所决定,因此,架构提供了一个共同语言(公共的参考点),项目关系人以此作为彼此理解、协商、达成共识或相互沟通的基础。架构分析既依赖于又促进了这个层次上的交流。
- (2)早期设计决策。从软件生命周期来看,软件架构是所开发系统的最早设计决策的体现,主要表现为:
架构明确了对系统实现的约束条件:架构是架构设计师对系统实现的各方面进行权衡的结果,是总体设计的体现,因此,在具体实现时必须按架构的设计进行。
架构影响着系统的质量属性:要保证系统的高质量,具有完美的架构是必要的(虽然不充分)。
架构可以用来预测系统的质量,例如,可以根据经验对该架构的质量(如性能)作定性的判断。
架构为维护的决策提供根据。在架构层次上能为日后的更改决策提供推理、判断的依据。一个富有生命力的架构,应该是在最有可能更改的地方有所考虑(架构的柔性),使其在此点最容易进行更改。
架构有助于原型开发。可以按架构构造一个骨架系统(原型),例如,在早期实现一个可执行的特例,确定潜在的性能问题。
借助于架构进行成本与进度的估计。
- (3)在较高层面上实现软件复用。软件架构作为系统的抽象模型,可以在多个系统间传递(复用),特别是比较容易地应用到具有相似质量属性和功能需求的系统中。产品线通常共享一个架构。产品线的架构是开发组织的核心资产之一,利用架构及其范例进行多系统的开发,在开发时间、成本、生产率和产品质量方面具有极大的回报。基于架构的开发强调对各元素的组合或装配。系统开发还可以使用其他组织开发的元素,例如购买商业构件。
- (4)架构对开发的指导与规范意义不容忽略。架构作为系统的总体设计,它指导后续的详细设计和编码。架构使基于模板的开发成为可能,有利于开发的规范化和一致性,减少开发与维护成本。架构可以作为培训的基础,有利于培养开发团队和培训相关人员。
从软件开发过程来看,如果采用传统的软件开发模型(生命周期模型),则软件架构的建立应位于概要设计之前,需求分析之后。
基于架构的软件开发模型则明确地把整个软件过程划分为架构需求、设计、文档化、评审(评估)、实现、演化等 6 个子过程。本章各节将分别对这些子过程进行讨论。
9.1.3 架构的模型
软件架构作为一个有机的整体,可以分解成多个侧面来认识,每个侧面强调它的不同方面的特征,从而使架构设计师能整体地把握它的重点。我们可以将软件架构归纳成 5 种模型:结构模型、框架模型、动态模型、过程模型和功能模型。最常用的是结构模型和动态模型。
- (1)结构模型。这是一个最直观、最普遍的建模方法。这种方法以架构的构件、连接件和其他概念来刻画结构,并力图通过结构来反映系统的重要语义内容,包括系统的配置、约束、隐含的假设条件、风格、性质。研究结构模型的核心是架构描述语言。
- (2)框架模型。框架模型与结构模型类似,但它不太侧重描述结构的细节而更侧重于整体的结构。框架模型主要以一些特殊的问题为目标建立只针对和适应该问题的结构。
- (3)动态模型。动态模型是对结构或框架模型的补充,研究系统“大颗粒”的行为性质。例如,描述系统的重新配置或演化。动态可能指系统总体结构的配置、建立或拆除通信通道或计算的过程。
- (4)过程模型。过程模型研究构造系统的步骤和过程。因而结构是遵循某些过程脚本的结果。
- (5)功能模型。该模型认为架构由一组功能构件按层次组成,且下层向上层提供服务。
它可以看作是一种特殊的框架模型。
这 5 种模型各有所长,也许将 5 种模型有机地统一在一起,形成一个完整的模型来刻画软件架构更合适。即将软件架构视为这些模型的统一体,通过这些模型的表述(文档)来完整反映软件架构。例如,Kruchten 在 1995 年提出了一个“4+1”的视图模型。“4+1” 视图模型从 5 个不同的视角包括逻辑视图、进程视图、物理视图、开发视图和场景视图来描述软件架构。每一个视图只关心系统的一个侧面,5 个视图结合在一起才能反映系统的软件架构的全部内容。“4+1”视图模型如图 9-1 所示。
- (1)逻辑视图:主要支持系统的功能需求,即系统提供给最终用户的服务。在逻辑视图中,系统分解成一系列的功能抽象,这些抽象主要来自问题领域。这种分解不但可以用来进行功能分析,而且可用作标识在整个系统的各个不同部分的通用机制和设计元素。在面向对象技术中,通过抽象、封装和继承,可以用对象模型来代表逻辑视图,用类图来描述逻辑视图。逻辑视图中使用的风格为面向对象的风格,逻辑视图设计中要注意的主要问题是要保持一个单一的、内聚的对象模型贯穿整个系统。
- (2)开发视图:也称为模块视图,主要侧重于软件模块的组织和管理。软件可通过程序库或子系统进行组织,这样,对于一个软件系统,就可以由不同的人进行开发。开发视图要考虑软件内部的需求,如软件开发的容易性、软件的重用和软件的通用性,要充分考虑由于具体开发工具的不同而带来的局限性。开发视图通过系统输入输出关系的模型图和子系统图来描述。可以在确定了软件包含的所有元素之后描述完整的开发角度,也可以在确定每个元素之前,列出开发视图原则。
- (3)进程视图:侧重于系统的运行特性,主要关注一些非功能性的需求,例如系统的性能和可用性。进程视图强调并发性、分布性、系统集成性和容错能力,以及逻辑视图中的主要抽象的进程结构。它也定义逻辑视图中的各个类的操作具体是在哪一个线程中被执行的。
进程视图可以描述成多层抽象,每个级别分别关注不同的方面。
- (4)物理视图:主要考虑如何把软件映射到硬件上,它通常要考虑到解决系统拓扑结构、系统安装、通信等问题。当软件运行于不同的节点上时,各视图中的构件都直接或间接地对应于系统的不同节点上。因此,从软件到节点的映射要有较高的灵活性,当环境改变时,对系统其他视图的影响最小。
- (5)场景:可以看作是那些重要系统活动的抽象,它使四个视图有机地联系起来,从某种意义上说,场景是最重要的需求抽象。在开发架构时,它可以帮助设计者找到架构的构件和它们之间的作用关系。同时,也可以用场景来分析一个特定的视图,或描述不同视图构件间是如何相互作用的。场景可以用文本表示,也可以用图形表示。
希赛教育专家提示:逻辑视图和开发视图描述系统的静态结构,而进程视图和物理视图描述系统的动态结构。对于不同的软件系统来说,侧重的角度也有所不同。例如,对于管理信息系统来说,比较侧重于从逻辑视图和开发视图来描述系统,而对于实时控制系统来说,则比较注重于从进程视图和物理视图来描述系统。
9.2 架构需求与软件质量属性
架构的基本需求主要是在满足功能属性的前提下,关注软件质量属性,架构设计则是为满足架构需求(质量属性)寻找适当的“战术”。
软件属性包括功能属性和质量属性,但是,软件架构(及软件架构设计师)重点关注的是质量属性。因为,在大量的可能结构中,可以使用不同的结构来实现同样的功能性,即功能性在很大程度上是独立于结构的,架构设计师面临着决策(对结构的选择),而功能性所关心的是它如何与其他质量属性进行交互,以及它如何限制其他质量属性。
9.2.1 软件质量属性
《GB/T16260-1996(idt ISO/IEC9126:1991)信息技术软件产品评价质量特性及其使用指南》中描述的软件质量特性包括功能性、可靠性、易用性、效率、可维护性、可移植性等 6个方面,每个方面都包含若干个子特性。
功能性:适合性、准确性、互操作性、依从性、安全性;
可靠性:成熟性、容错性、易恢复性;
易用性:易理解性、易学性、易操作性;
效率:时间特性、资源特性;
可维护性:易分析性、易改变性、稳定性、易测试性;
可移植性:适应性、易安装性、遵循性、易替换性;
正如上述列举与分类,软件的质量属性很多,也有各种不同的分类法和不同的表述。虽然术语没有统一的定义,但其含义可以认为业界已有共识。下面选取常用的质量属性术语,并做逐一说明。
1. 运行期质量属性
性能:性能是指软件系统及时提供相应服务的能力。包括速度、吞吐量和持续高速性三方面的要求。
安全性:指软件系统同时兼顾向合法用户提供服务,以及阻止非授权使用的能力。
易用性:指软件系统易于被使用的程度。
可伸缩性:指当用户数和数据量增加时,软件系统维持高服务质量的能力。例如,通过增加服务器来提高能力。
互操作性:指本软件系统与其他系统交换数据和相互调用服务的难易程度。
可靠性:软件系统在一定的时间内无故障运行的能力。
持续可用性:指系统长时间无故障运行的能力。与可靠性相关联,常将其纳入可靠性中。
鲁棒性:是指软件系统在一些非正常情况(如用户进行了非法操作、相关的软硬件系统发生了故障等)下仍能够正常运行的能力。也称健壮性或容错性。
2.开发期质量属性易理解性:指设计被开发人员理解的难易程度。
可扩展性:软件因适应新需求或需求变化而增加新功能的能力。也称为灵活性。
可重用性:指重用软件系统或某一部分的难易程度。
可测试性:对软件测试以证明其满足需求规范的难易程度。
可维护性:当需要修改缺陷、增加功能、提高质量属性时,定位修改点并实施修改的难易程度;
可移植性:将软件系统从一个运行环境转移到另一个不同的运行环境的难易程度。
在实践中,架构设计师追求质量属性常常陷入“鱼和熊掌”的两难境地,这就需要架构设计师的决策智慧了。表 9-1 反映了质量属性之间的相互制约关系(正相关或负相关),其中“+”代表“行属性”能促进“列属性”;而“-”则相反。例如,第一列符号说明许多属性(行)对性能(列)有副作用,第一行符号说明性能(行)对许多属性(列)有副作用,认识这一点,对于架构决策的权衡很重要。
9.2.2 6 个质量属性及实现
本节从架构关注点来研究质量属性实现,将质量属性分为 6 种:可用性、可修改性、性能、安全性、可测试性、易用性。其他的质量属性一般可纳入这几个属性中(在其他文献中为了强调常单列出来),例如,可扩充性可归入可修改性中(修改系统容量),可移植性也可以作为平台的可修改性来获得。对于未能纳入的其他质量属性,可以用本章的方法进行研究。
那么,如何描述质量属性需求呢?采用质量属性场景作为一种描述规范,它由以下6个部分组成,如图 9-2 所示。
刺激源:生成该刺激的实体(人、计算机系统或其他激励器);
刺激:刺激到达系统时可能产生的影响(即需要考虑和关注的情况);
环境:该刺激在某条件内发生。如系统可能正处于过载情况;
制品:系统中受刺激的部分(某个制品被刺激);
响应:刺激到达后所采取的行动;
响应度量:当响应发生时,应能够以某种方式对应其度量,用于对是否满足需求的测试。
需要将一般的质量属性场景(一般场景)与具体的质量属性场景(具体场景)区别开来,前者是指独立于具体系统、适合于任何系统的一般性场景;而后者是指适合于正在考虑的某个特定系统的场景,具体场景通常是指从一般场景中抽取特定的、面向具体系统的内容。下面几个小节中为每个质量属性提供一张表,该表中给出了质量属性场景每部分的一些可能取值,整体上形成一个一般场景的表格描述。在实际应用时,根据系统的具体情况,从该表中选取适当的值,就能变成具体场景(可读性强、可应用),可以把具体场景的集合作为系统的质量属性需求。
实现这些质量属性的基本设计决策,称为“战术”,而把战术的集合称为“架构策略”。
这些架构策略供架构设计师选择。下面几个小节将对各质量属性的战术进行示例性的总结。
“战术”作为逻辑部件位于图 9-2 的制品中,它旨在控制对刺激的响应。
1. 可用性及其实现战术
- (1)可用性的描述。可用性的描述如表 9-2 所示。
可用性一般场景可以用图 9-3 表示。
对一般场景进行具体化可以得到可用性具体场景,如图 9-4 所示。
- (2)可用性战术。可用性战术的目标是阻止错误发展成故障,至少能够把错误的影响限制在一定范围内,从而使修复成为可能。战术分为:错误检测、错误恢复、错误预防。
- ① 错误检测命令/响应:一个构件发出一个命令,并希望在预定义的时间内收到一个来自审查构件的响应,例如远程错误的检测。
心跳(计时器):一个构件定期发出一个心跳消息,另一个构件收听到消息,如果未收到心跳消息,则假定构件失败,并通知错误纠正构件。
异常:当出现异常时,异常处理程序开发执行。
- ② 错误恢复表决:通过冗余构件(或处理器)与表决器连接,构件按相同的输入及算法计算输出值交给表决器,由表决器按表决算法(如多数规则)确定是否有构件出错,表决通常用在控制系统中。
主动冗余(热重启、热备份):所有的冗余构件都以并行的方式对事件做出响应。它们都处在相同的状态,但仅使用一个构件的响应,丢弃其余构件的响应。错误发生时通过切换的方式使用另一个构件的响应。
被动冗余(暧重启/双冗余/三冗余):一个构件(主构件)对事件做出响应,并通知其他构件(备用的)必须进行的状态更新(同步)。当错误发生时,备用构件从最新同步点接替主构件的工作。
备件:备件是计算平台配置用于更换各种不同的故障构件。
状态再同步:主动和被动冗余战术要求所恢复的构件在重新提供服务前更新其状态。更新方法取决于可以承受的停机时间、更新的规模及更新的内容多少。
检查点/回滚:检查点就是使状态一致的同步点,它或者是定期进行,或者是对具体事件做出响应。当在两检查点之间发生故障时,则以这个一致状态的检查点(有快照)和之后发生的事务日志来恢复系统(数据库中常使用)。
- ③ 错误预防从服务中删除:如删除进程再重新启动,以防止内存泄露导致故障的发生。
事务:使用事务来保证数据的一致性,即几个相关密切的步骤,要么全成功,要么都不成功。
进程监视器:通过监视进程来处理进程的错误。
2. 可修改性及其实现战术
- (1)可修改性的描述。可修改性的描述如表 9-3 所示。
对于可修改性一般场景的图示及可修改性具体场景,读者可仿照前面可用性的描述方式,自行练习。
- (2)可修改性战术。包括局部化修改、防止连锁反应、推迟绑定时间。
- ① 局部化修改。在设计期间为模块分配责任,以便把预期的变更限制在一定的范围内,从而降低修改的成本。
维持语义的一致性:语义的一致性指的是模块中责任之间的关系,使这些责任能够协同工作,不需要过多地依赖其他模块。耦合和内聚指标反映一致性,应该根据一组预期的变更来度量语义一致性。使用“抽象通用服务”(如应用框架的使用和其他中间软件的使用)来支持可修改性是其子战术。
预期期望的变更:通过对变更的预估,进行预设、准备,从而使变更的影响最小。
泛化该模块:使一个模块更通用、更广泛的功能。
限制可能的选择:如在更换某一模块(如处理器)时,限制为相同家族的成员。
- ② 防止连锁反应。由于模块之间有各种依赖性,因此,修改会产生连锁反应。防止连锁反应的战术如下。
信息隐藏:就是把某个实体的责任分解为更小的部分,并选择哪些信息成为公有的,哪些成为私有的,通过接口获得公有责任。
维持现有的接口:尽可能维持现有接口的稳定性。例如通过添加接口(通过新的接口提供新的服务)可以达到这一目的。
限制通信路径:限制与一个给定的模块共享数据的模块。这样可以减少由于数据产生/使用引入的连锁反应。
仲裁者的使用:在具有依赖关系的两个模块之间插入一个仲裁者,以管理与该依赖相关的活动。仲裁者有很多种类型,例如:桥、调停者、代理等就是可以提供把服务的语法从一种形式转换为另一种形式的仲裁者。
- ③ 推迟绑定时间。系统具备在运行时进行绑定并允许非开发人员进行修改(配置)。
运行时注册:支持即插即用。
配置文件:在启动时设置参数。
多态:在方法调用的后期绑定。
构件更换:允许载入时绑定。
3. 性能及其实现战术
- (1)性能的描述。性能描述如表 9-4 所示。
对于性能一般场景的图示及性能具体场景,读者可仿照前面可用性的描述方式,自行练习。
- (2)性能战术。性能与时间相关,影响事件的响应时间有两个基本因素。
资源消耗:事件到达后进入一系列的处理程序,每一步处理都要占用资源,而且在处理过程中消息在各构件之间转换,这些转换也需要占用资源。
闭锁时间:指对事件处理时碰到了资源争用、资源不可用或对其他计算的依赖等情况,就产生了等待时间。
性能的战术有如下几种。
- ① 资源需求减少处理事件流所需的资源:提高计算效率(如改进算法)、减少计算开销(如在可修改性与性能之间权衡,减少不必要的代理构件)。
减少所处理事件的数量:管理事件率、控制采样频率。
控制资源的使用:限制执行时间(如减少迭代次数)、限制队列大小。
- ② 资源管理引入并发:引入并发对负载平衡很重要。
维持数据或计算的多个副本:C/S 结构中客户机 C 就是计算的副本,它能减少服务器计算的压力;高速缓存可以存放数据副本(在不同速度的存储库之间的缓冲)。
增加可用资源:在成本允许时,尽量使用速度更快的处理器、内存和网络。
- ③ 资源仲裁
资源仲裁战术是通过如下调度策略来实现的。
先进/先出(FIFO);
固定优先级调度:先给事件分配特定的优先级,再按优先级高低顺序分配资源;
动态优先级调度:轮转调度、时限时间最早优先;
静态调度:可以离线确定调度。
4. 安全性及其实现战术
- (1)安全性的描述。安全性的描述如表 9-5 所示。
对于安全性一般场景的图示及安全性具体场景,读者可仿照前面可用性的描述方式,自行练习。
- (2)安全性战术:包括抵抗攻击、检测攻击和从攻击中恢复。
- ① 抵抗攻击对用户进行身份验证:包括动态密码、一次性密码、数字证书及生物识别等;
对用户进行授权:即对用户的访问进行控制管理;
维护数据的机密性:一般通过对数据和通信链路进行加密来实现;
维护完整性:对数据添加校验或哈希值;
限制暴露的信息;
限制访问:如用防火墙、DMZ 策略。
- ② 检测攻击。一般通过“入侵检测”系统进行过滤、比较通信模式与历史基线等方法。
- ③ 从攻击中恢复。
恢复:与可用性中的战术相同;
识别攻击者:作为审计追踪,用于预防性或惩罚性目的。
5. 可测试性及其实现战术
- (1)可测试性的描述。可测试性的描述如表 9-6 所示。
对于可测试性一般场景的图示及可测试性具体场景,读者可仿照前面可用性的描述方式,自行练习。
- (2)可测试性战术:包括输入/输出和内部监控。
- ① 输入/输出记录/回放:指捕获跨接口的信息,并将其作为测试专用软件的输入;
将接口与实现分离:允许使用实现的替代(模拟器)来支持各种测试目的;
优化访问线路/接口:用测试工具来捕获或赋予构件的变量值。
- ② 内部监控。当监视器处于激活状态时,记录事件(如通过接口的信息)。
6. 易用性及其实现战术
- (1)易用性的描述。易用性的描述如表 9-7 所示。
对于易用性一般场景的图示及易用性具体场景,读者可仿照前面可用性的描述方式,自行练习。
- (2)易用性战术:包括运行时战术、设计时战术和支持用户主动操作。
- ① 运行时战术任务的模型:维护任务的信息,使系统了解用户试图做什么,并提供各种协助;
用户的模型:维护用户的信息,例如使系统以用户可以阅读页面的速度滚动页面;
系统的模型:维护系统的信息,它确定了期望的系统行为,并向用户提供反馈。
- ② 设计时战术。将用户接口与应用的其余部分分离开来,预计用户接口会频繁发生变化,因此,单独维护用户接口代码将实现变更局部化。这与可修改性相关。
- ③ 支持用户主动操作。支持用户的主动操作,如支持“取消”、“撤销”、“聚合”和 “显示多个视图”。
9.3 软件架构风格
软件架构设计的一个核心问题是能否使用重复的软件架构模式,即能否达到架构级别的软件重用。也就是说,能否在不同的软件系统中,使用同一架构。基于这个目的,学者们开始研究和实践软件架构的风格和类型问题。
软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式( idiomaticparadigm)。架构风格定义了一个系统家族,即一个架构定义一个词汇表和一组约束。词汇表中包含一些构件和连接件类型,而这组约束指出系统是如何将这些构件和连接件组合起来的。架构风格反映了领域中众多系统所共有的结构和语义特性,并指导如何将各个模块和子系统有效地组织成一个完整的系统。按这种方式理解,软件架构风格定义了用于描述系统的术语表和一组指导构建系统的规则。
对软件架构风格的研究和实践促进了对设计的重用,一些经过实践证实的解决方案也可以可靠地用于解决新的问题。架构风格不变的部分使不同的系统可以共享同一个实现代码。
只要系统是使用常用的、规范的方法来组织,就可使别的设计者很容易地理解系统的架构。
例如,如果某人把系统描述为客户/服务器模式,则不必给出设计细节,我们立刻就会明白系统是如何组织和工作的。
软件架构风格为大粒度的软件重用提供了可能。然而,对于应用架构风格来说,由于视点的不同,系统设计师有很大的选择余地。要为系统选择或设计某一个架构风格,必须根据特定项目的具体特点,进行分析比较后再确定,架构风格的使用几乎完全是特定的。
9.3.1 软件架构风格分类
讨论架构风格时要回答的问题是:
- (1)设计词汇表是什么?
- (2)构件和连接件的类型是什么?
- (3)可容许的结构模式是什么?
- (4)基本的计算模型是什么?
- (5)风格的基本不变性是什么?
- (6)其使用的常见例子是什么?
- (7)使用此风格的优缺点是什么?
- (8)其常见的特例是什么?
这些问题的回答包括了架构风格的最关键的四要素内容,即提供一个词汇表、定义一套配置规则、定义一套语义解释原则和定义对基于这种风格的系统所进行的分析。Garlan 和Shaw 根据此框架给出了通用架构风格的分类:
- (1)数据流风格:批处理序列;管道/过滤器。
- (2)调用/返回风格:主程序/子程序;面向对象风格;层次结构。
- (3)独立构件风格:进程通信;事件系统。
- (4)虚拟机风格:解释器;基于规则的系统。
- (5)仓库风格:数据库系统;超文本系统;黑板系统。
9.3.2 数据流风格
数据流风格的软件架构是一种最常见,结构最为简单的软件架构。这样的架构下,所有的数据按照流的形式在执行过程中前进,不存在结构的反复和重构,就像工厂中的汽车流水线一样,数据就像汽车零部件一样在流水线的各个节点上被加工,最终输出所需要的结果(一部完整的汽车)。在流动过程中,数据经过序列间的数据处理组件进行处理,然后将处理结果向后传送,最后进行输出。
数据流风格架构主要包括两种具体的架构风格:批处理序列和管道-过滤器。
1. 批处理序列
批处理风格的每一步处理都是独立的,并且每一步是顺序执行的。只有当前一步处理完,后一步处理才能开始。数据传送在步与步之间作为一个整体。(组件为一系列固定顺序的计算单元,组件间只通过数据传递交互。每个处理步骤是一个独立的程序,每一步必须在前一步结束后才能开始,数据必须是完整的,以整体的方式传递)批处理的典型应用:
- (1)经典数据处理;
- (2)程序开发;
- (3)Windows 下的 BAT 程序就是这种应用的典型实例。
2. 管道和过滤器
在管道/过滤器风格的软件架构中,每个构件都有一组输入和输出,构件读输入的数据流,经过内部处理,然后产生输出数据流。这个过程通常通过对输入流的变换及增量计算来完成,所以在输入被完全消费之前,输出便产生了。因此,这里的构件被称为过滤器,这种风格的连接件就像是数据流传输的管道,将一个过滤器的输出传到另一过滤器的输入。此风格特别重要的过滤器必须是独立的实体,它不能与其他的过滤器共享数据,而且一个过滤器不知道它上游和下游的标识。一个管道/过滤器网络输出的正确性并不依赖于过滤器进行增量计算过程的顺序。
图 9-5 是管道/过滤器风格的示意图。一个典型的管道/过滤器架构的例子是以 UNIXshell 编写的程序。UNIX 既提供一种符号,以连接各组成部分(UNIX 的进程),又提供某种进程运行时机制以实现管道。另一个著名的例子是传统的编译器。传统的编译器一直被认为是一种管道系统,在该系统中,一个阶段(包括词法分析、语法分析、语义分析和代码生成)
的输出是另一个阶段的输入。
管道/过滤器风格的软件架构具有许多很好的特点:
- (1)使得软构件具有良好的隐蔽性和高内聚、低耦合的特点;
- (2)允许设计者将整个系统的输入/输出行为看成是多个过滤器的行为的简单合成;
- (3)支持软件重用。只要提供适合在两个过滤器之间传送的数据,任何两个过滤器都可被连接起来;
- (4)系统维护和增强系统性能简单。新的过滤器可以添加到现有系统中来;旧的可以被改进的过滤器替换掉;
- (5)允许对一些如吞吐量、死锁等属性的分析;
- (6)支持并行执行。每个过滤器是作为一个单独的任务完成,因此可与其他任务并行执行。
但是,这样的系统也存在着若干不利因素。
- (1)通常导致进程成为批处理的结构。这是因为虽然过滤器可增量式地处理数据,但它们是独立的,所以设计者必须将每个过滤器看成一个完整的从输入到输出的转换;
- (2)不适合处理交互的应用。当需要增量地显示改变时,这个问题尤为严重;
- (3)因为在数据传输上没有通用的标准,每个过滤器都增加了解析和合成数据的工作,这样就导致了系统性能下降,并增加了编写过滤器的复杂性。
3. 批处理序列风格与管道过滤器风格对比
把批处理序列风格与管道过滤器风格比较:
共同点:把任务分成一系列固定顺序的计算单元(组件)。组件间只通过数据传递交互。
区别:批处理是全部的、高潜伏性的,输入时可随机存取,无合作性、无交互性。而管道过滤器是递增的,数据结果延迟小,输入时处理局部化,有反馈、可交互。批处理强调数据传送在步与步之间作为一个整体,而管理过滤器无此要求。
9.3.3 调用/返回风格
调用返回风格顾名思义,就是指在系统中采用了调用与返回机制。利用调用-返回实际上是一种分而治之的策略,其主要思想是将一个复杂的大系统分解为一些子系统,以便降低复杂度,并且增加可修改性。程序从其执行起点开始执行该构件的代码,程序执行结束,将控制返回给程序调用构件。
调用/返回风格架构主要包括三种具体的架构风格:主程序/子程序;面向对象风格;层次结构。
1. 主程序/子程序
主程序/子程序风格是结构化开发时期的经典架构风格。这种风格一般采用单线程控制,把问题划分为若干处理步骤,构件即为主程序和子程序。子程序通常可合成为模块。过程调用作为交互机制,即充当连接件。调用关系具有层次性,其语义逻辑表现为子程序的正确性,取决于它调用的子程序的正确性。
2. 面向对象风格
抽象数据类型概念对软件系统有着重要作用,目前软件界已普遍使用面向对象系统。这种风格建立在数据抽象和面向对象的基础上,数据的表示方法和它们的相应操作封装在一个抽象数据类型或对象中。这种风格的构件是对象,或者说是抽象数据类型的实例。对象是一种被称作管理者的构件,因为它负责保持资源的完整性。对象是通过函数和过程的调用来交互的。
图 9-6 是数据抽象和面向对象风格的示意图。
这种风格的两个重要特征为:
- (1)对象负责维护其表示的完整性;
- (2)对象的表示对其他对象而言是隐蔽的。因为一个对象对它的客户隐藏了自己的表示,所以这些对象可以不影响它的客户就能改变其实现方法。
面向对象的系统有许多优点,并早已为人所知:
- (1)因为对象对其他对象隐藏它的表示,所以可以改变一个对象的表示,而不影响其他的对象;
- (2)设计者可将一些数据存取操作的问题分解成一些交互的代理程序的集合。
但是,面向对象的系统也存在着某些问题:
- (1)为了使一个对象和另一个对象通过过程调用等进行交互,必须知道对象的标识。
只要一个对象的标识改变了,就必须修改所有其他明确调用它的对象;
- (2)必须修改所有显式调用它的其他对象,并消除由此带来的一些副作用。例如,如果 A 使用了对象 B,C 也使用了对象 B,那么,C 对 B 的使用所造成的对 A 的影响可能是料想不到的。
2. 层次结构风格
层次系统组织成一个层次结构,每一层为上层服务,并作为下层客户。在一些层次系统中,除了一些精心挑选的输出函数外,内部的层只对相邻的层可见。这样的系统中构件在一些层实现了虚拟机(在另一些层次系统中层是部分不透明的)。连接件通过决定层间如何交互的协议来定义,拓扑约束包括对相邻层间交互的约束。
这种风格支持基于可增加抽象层的设计。这样,允许将一个复杂问题分解成一个增量步骤序列的实现。由于每一层最多只影响两层,同时只要给相邻层提供相同的接口,允许每层用不同的方法实现,同样为软件重用提供了强大的支持。
图 9-7 是层次系统风格的示意图。层次系统最广泛的应用是分层通信协议。在这一应用领域中,每一层提供一个抽象的功能,作为上层通信的基础。较低的层次定义低层的交互,最低层通常只定义硬件物理连接。
层次系统有许多可取的属性:
- (1)支持基于抽象程度递增的系统设计,使设计者可以把一个复杂系统按递增的步骤进行分解;
- (2)支持功能增强,因为每一层至多和相邻的上下层交互,因此功能的改变最多影响相邻的上下层;
- (3)支持重用。只要提供的服务接口定义不变,同一层的不同实现可以交换使用。这样,就可以定义一组标准的接口,而允许各种不同的实现方法。
但是,层次系统也有其不足之处:
- (1)并不是每个系统都可以很容易地划分为分层的模式,甚至即使一个系统的逻辑结构是层次化的,出于对系统性能的考虑,系统设计师不得不把一些低级或高级的功能综合起来;
- (2)很难找到一个合适的、正确的层次抽象方法。
9.3.4 独立构件风格
独立构件风格主要强调系统中的每个构件都是相对独立的个体,它们之间不直接通信,以降低耦合度,提升灵活性。独立构件风格主要包括:进程通讯和事件系统子风格。
1. 进程通信架构风格进程通信架构风格:构件是独立的过程,连接件是消息传递。这种风格的特点是构件通常是命名过程,消息传递的方式可以是点到点、异步和同步方式及远过程调用等。
2. 事件系统风格基于事件的隐式调用风格的思想是构件不直接调用一个过程,而是触发或广播一个或多个事件。系统中的其他构件中的过程在一个或多个事件中注册,当一个事件被触发,系统自动调用在这个事件中注册的所有过程,这样,一个事件的触发就导致了另一模块中的过程的调用。
从架构上说,这种风格的构件是一些模块,这些模块既可以是一些过程,又可以是一些事件的集合。过程可以用通用的方式调用,也可以在系统事件中注册一些过程,当发生这些事件时,过程被调用。
基于事件的隐式调用风格的主要特点是事件的触发者并不知道哪些构件会被这些事件影响。这样不能假定构件的处理顺序,甚至不知道哪些过程会被调用,因此,许多隐式调用的系统也包含显式调用作为构件交互的补充形式。
支持基于事件的隐式调用的应用系统很多。例如,在编程环境中用于集成各种工具,在数据库管理系统中确保数据的一致性约束,在用户界面系统中管理数据,以及在编辑器中支持语法检查。例如在某系统中,编辑器和变量监视器可以登记相应 Debugger 的断点事件。
当 Debugger 在断点处停下时,它声明该事件,由系统自动调用处理程序,如编辑程序可以卷屏到断点,变量监视器刷新变量数值。而 Debugger 本身只声明事件,并不关心哪些过程会启动,也不关心这些过程做什么处理。
隐式调用系统的主要优点有:
- (1)为软件重用提供了强大的支持。当需要将一个构件加入现存系统中时,只需将它注册到系统的事件中。
- (2)为改进系统带来了方便。当用一个构件代替另一个构件时,不会影响到其他构件的接口。
隐式调用系统的主要缺点有:
- (1)构件放弃了对系统计算的控制。一个构件触发一个事件时,不能确定其他构件是否会响应它。而且即使它知道事件注册了哪些构件的过程,它也不能保证这些过程被调用的顺序。
- (2)数据交换的问题。有时数据可被一个事件传递,但另一些情况下,基于事件的系统必须依靠一个共享的仓库进行交互。在这些情况下,全局性能和资源管理便成了问题。
- (3)既然过程的语义必须依赖于被触发事件的上下文约束,关于正确性的推理存在问题。
9.3.5 虚拟机风格
虚拟机风格的基本思想是人为构建一个运行环境,在这个环境之上,可以解析与运行自定义的一些语言,这样来增加架构的灵活性,虚拟机风格主要包括解释器和规则为中心两种架构风格。
1.解释器
一个解释器通常包括完成解释工作的解释引擎,一个包含将被解释的代码的存储区,一个记录解释引擎当前工作状态的数据结构,以及一个记录源代码被解释执行进度的数据结构。
具有解释器风格的软件中含有一个虚拟机,可以仿真硬件的执行过程和一些关键应用。
解释器通常被用来建立一种虚拟机以弥合程序语义与硬件语义之间的差异。其缺点是执行效率较低。典型的例子是专家系统。
2. 规则为中心
基于规则的系统包括规则集、规则解释器、规则/数据选择器及工作内存。
9.3.6 仓库风格
在仓库(repository)风格中,有两种不同的构件:中央数据结构说明当前状态,独立构件在中央数据存储上执行,仓库与外构件间的相互作用在系统中会有大的变化。
仓库风格包括的子风格有:数据库系统、超文本系统、黑板风格。
数据库架构是库风格最常见的形式。构件主要有两大类,一个是中央共享数据源,保存当前系统的数据状态;另一个是多个独立处理元素,处理元素对数据元素进行操作。而超文本系统的典型代表,就是早期的静态网页。三种架构子风格中,最复杂的是黑板系统。
黑板系统是在抽象与总结语言理解系统 HEARSAY-11 的基础上产生的,适合于解决复杂的非结构化的问题,能在求解过程中综合运用多种不同知识源,使得问题的表达、组织和求解变得比较容易。黑板系统是一种问题求解模型,是组织推理步骤、控制状态数据和问题求解之领域知识的概念框架。它将问题的解空间组织成一个或多个应用相关的分级结构。分级结构的每一层信息由一个唯一的词汇来描述,它代表了问题的部分解。领域相关的知识被分成独立的知识模块,它将某一层次中的信息转换成同层或相邻层的信息。各种应用通过不同知识表达方法、推理框架和控制机制的组合来实现。影响黑板系统设计的最大因素是应用问题本身的特性,但是支撑应用程序的黑板体系结构有许多相似的特征和构件。
对于特定应用问题,黑板系统可通过选取各种黑板、知识源和控制模块的构件来设计;
也可以利用预先定制的黑板体系结构的编程环境。图 9-8 是黑板系统的组成。黑板系统的传统应用是信号处理领域,如语音和模式识别。另一应用是松耦合代理数据共享存取。
我们从图 9-8 中可以看出,黑板系统主要由三部分组成:
- (1)知识源。知识源中包含独立的、与应用程序相关的知识,知识源之间不直接进行通信,它们之间的交互只通过黑板来完成。
- (2)黑板数据结构。黑板数据是按照与应用程序相关的层次来组织的解决问题的数据,知识源通过不断地改变黑板数据来解决问题。
- (3)控制。控制完全由黑板的状态驱动,黑板状态的改变决定使用的特定知识。
9.4 层次系统架构风格
本章 9.3.3 节中已对层次系统架构风格进行了初步的介绍,下面将对几种经典的层次风格进行详细说明。
9.4.1 二层及三层 C/S 架构风格
C/S 架构是基于资源不对等,且为实现共享而提出来的,是 20 世纪 90 年代成熟起来的技术,C/S 结构将应用一分为二,服务器(后台)负责数据管理,客户机(前台)完成与用户的交互任务。
C/S 软件架构具有强大的数据操作和事务处理能力,模型思想简单,易于人们理解和接受。但随着企业规模的日益扩大,软件的复杂程度不断提高,传统的二层 C/S 结构存在以下几个局限:
二层 C/S 结构为单一服务器且以局域网为中心,所以难以扩展至大型企业广域网或Internet;
软、硬件的组合及集成能力有限;
服务器的负荷太重,难以管理大量的客户机,系统的性能容易变坏;
数据安全性不好。因为客户端程序可以直接访问数据库服务器,那么,在客户端计算机上的其他程序也可想办法访问数据库服务器,从而使数据库的安全性受到威胁。
正是因为二层 C/S 有这么多缺点,因此,三层 C/S 结构应运而生。三层 C/S 结构是将应用功能分成表示层、功能层和数据层三个部分,如图 9-9 所示。
表示层是应用的用户接口部分,它担负着用户与应用间的对话功能。它用于检查用户从键盘等输入的数据,并显示应用输出的数据。在变更用户接口时,只需改写显示控制和数据检查程序,而不影响其他两层。检查的内容也只限于数据的形式和取值的范围,不包括有关业务本身的处理逻辑。
功能层相当于应用的本体,它是将具体的业务处理逻辑编入程序中。而处理所需的数据则要从表示层或数据层取得。表示层和功能层之间的数据交往要尽可能简洁。
数据层就是数据库管理系统,负责管理对数据库数据的读写。数据库管理系统必须能迅速执行大量数据的更新和检索。因此,一般从功能层传送到数据层的要求大都使用 SQL 语言。
三层 C/S 的解决方案是:对这三层进行明确分割,并在逻辑上使其独立。原来的数据层作为数据库管理系统已经独立出来,所以,关键是要将表示层和功能层分离成各自独立的程序,并且还要使这两层间的接口简洁明了。
一般情况是只将表示层配置在客户机中,如果将功能层也放在客户机中,与二层 C/S 结构相比,其程序的可维护性要好得多,但是其他问题并未得到解决。客户机的负荷太重,其业务处理所需的数据要从服务器传给客户机,所以系统的性能容易变差。
如果将功能层和数据层分别放在不同的服务器中,则服务器和服务器之间也要进行数据传送。但是,由于在这种形态中三层是分别放在各自不同的硬件系统上的,所以灵活性很高,能够适应客户机数目的增加和处理负荷的变动。例如,在追加新业务处理时,可以相应增加装载功能层的服务器。因此,系统规模越大这种形态的优点就越显著。
9.4.2 B/S 架构风格
在三层 C/S 架构中,表示层负责处理用户的输入和向客户的输出(出于效率的考虑,它可能在向上传输用户的输入前进行合法性验证)。功能层负责建立数据库的连接,根据用户的请求生成访问数据库的 SQL 语句,并把结果返回给客户端。数据层负责实际的数据库存储和检索,响应功能层的数据处理请求,并将结果返回给功能层。
浏览器/服务器(Browser/Server,简称 B/S)风格就是上述三层应用结构的一种实现方式,其具体结构为:浏览器/Web 服务器/数据库服务器。采用 B/S 结构的计算机应用系统的基本框架如图 9-10 所示。
B/S 架构主要是利用不断成熟的 WWW 浏览器技术,结合浏览器的多种脚本语言,用通用浏览器就实现了原来需要复杂的专用软件才能实现的强大功能,并节约了开发成本。从某种程度上来说,B/S 结构是一种全新的软件架构。
在 B/S 结构中,除了数据库服务器外,应用程序以网页形式存放于 Web 服务器上,用户运行某个应用程序时只需在客户端上的浏览器中键入相应的网址,调用 Web 服务器上的应用程序并对数据库进行操作完成相应的数据处理工作,最后将结果通过浏览器显示给用户。可以说,在 B/S 模式的计算机应用系统中,应用(程序)在一定程度上具有集中特征。
基于 B/S 架构的软件,系统安装、修改和维护全在服务器端解决。用户在使用系统时,仅需要一个浏览器就可运行全部的模块,真正达到了“零客户端”的功能,很容易在运行时自动升级。B/S 架构还提供了异种机、异种网、异种应用服务的联机、联网、统一服务的最现实的开放性基础。
B/S 结构出现之前,管理信息系统的功能覆盖范围主要是组织内部。B/S 结构的“零客户端”方式,使组织的供应商和客户(这些供应商和客户有可能是潜在的,也就是说可能是事先未知的!)的计算机方便地成为管理信息系统的客户端,进而在限定的功能范围内查询组织相关信息,完成与组织的各种业务往来的数据交换和处理工作,扩大了组织计算机应用系统的功能覆盖范围,可以更加充分利用网络上的各种资源,同时应用程序维护的工作量也大大减少。另外,B/S 结构的计算机应用系统与 Internet 的结合也使新近提出的一些新的企业计算机应用(如电子商务,客户关系管理)的实现成为可能。
与 C/S 架构相比,B/S 架构也有许多不足之处,例如:
- (1)B/S 架构缺乏对动态页面的支持能力,没有集成有效的数据库处理功能。
- (2)采用 B/S 架构的应用系统,在数据查询等响应速度上,要远远地低于 C/S 架构。
- (3)B/S 架构的数据提交一般以页面为单位,数据的动态交互性不强,不利于在线事务处理(OnLine Transaction Processing,简称 OLTP)应用。
9.4.3 MVC 架构风格
MVC 全名是 Model ViewController,是模型(model)-视图(view)-控制器(controller)的缩写,它是分层架构风格的一种。MVC 是由挪威的计算机专家 Trygve M.H. Reenskau 于1979年提出的软件架构模式,MVC 最初用于 SmallTalk。MVC 提出的基本思想是进行关注点分离。一个典型的人机交互应用具有三个主要的关注点:数据在可视化界面上的呈现、UI 处理逻辑和业务逻辑。如果按传统的自治视图模式(即将与 UI 相关的逻辑都定义在针对视图的相关元素的事件上),将三者混合在一起,势必会带来一系列问题:
- (1)业务逻辑是与 UI 无关的,应该最大限度地被重用。由于业务逻辑定义在自治视图中,相当于完全与视图本身绑定在一定,如果我们能够将 UI 的行为抽象出来,基于抽象化 UI 的处理逻辑也是可以被共享的。但是定义在自治视频中的 UI 处理逻辑完全丧失了重用的可能。
- (2)业务逻辑具有最强的稳定性,UI 处理逻辑次之,而可视化界面上的呈现最差(比如我们经常会为了更好地呈现效果来调整 HTML)。如果将具有不同稳定性的元素融为一体,那么具有最差稳定性的元素决定了整体的稳定性。
- (3)任何涉及 UI 的组件都不易测试。UI 是呈现给人看的,并且用于人机交互,用机器来模拟活生生的人来对组件实施自动化测试不是一件容易的事,自治视图严重损害了组件的可测试性。
正是为了解决以上的问题,所以我们需要采用关注点分离的方针来将可视化界面呈现、UI 处理逻辑和业务逻辑三者分离出来,并且采用合理的交互方式将它们之间的依赖降到最低。
MVC 中各个部分的分工与协作是这样的:
- (1)Model 是对应用状态和业务功能的封装,我们可以将它理解为同时包含数据和行为的领域模型。Model 接受 Controller 的请求并完成相应的业务处理,在状态改变的时候向 View 发出相应的通知。
- (2)View 实现可视化界面的呈现并捕捉最终用户的交互操作(例如鼠标和键盘的操作)。
- (3)View 捕获到用户交互操作后会直接转发给 Controller,后者完成相应的 UI 逻辑。
如果需要涉及业务功能的调用,Controller 会直接调用 Model。在完成 UI 处理后,Controller会根据需要控制原 View 或者创建新的 View 对用户交互操作予以响应。
MVC 模式的基本结构如图 9-11 所示。
9.4.4 MVP 架构风格
MVP 的全称为 Model-View-Presenter,Model 提供数据,View 负责显示,Controller/Presenter 负责逻辑的处理。MVP 是从经典的模式 MVC 演变而来,它们的基本思想有相通的地方:Controller/Presenter 负责逻辑的处理,Model 提供数据,View 负责显示。当然 MVP与 MVC 也有一些显著的区别,MVC 模式中元素之间“混乱”的交互主要体现在允许 View和 Model 直接进行“交流”,这在 MVP 模式中是不允许的。在 MVP 中 View 并不直接使用 Model,它们之间的通信是通过 Presenter (MVC 中的 Controller)来进行的,所有的交互都发生在 Presenter 内部,而在 MVC 中 View 会直接从 Model 中读取数据而不是通过 Controller。
MVP 不仅仅避免了 View 和 Model 之间的耦合,还进一步降低了 Presenter 对 View的依赖。Presenter 依赖的是一个抽象化的 View,即 View 实现的接口 IView,这带来的最直接的好处,就是使定义在 Presenter 中的 UI 处理逻辑变得易于测试。由于 Presenter 对View 的依赖行为定义在接口 IView 中,我们只需要一个实现了这个接口的 View 就能对Presenter 进行测试。MVP 的结构如图 9-12 所示。
下面我们来分析 MVP 的优缺点。MVP 的优点包括:
- (1)模型与视图完全分离,我们可以修改视图而不影响模型。
- (2)可以更高效地使用模型,因为所有的交互都发生在一个地方—Presenter 内部。
- (3)我们可以将一个 Presenter 用于多个视图,而不需要改变 Presenter 的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
- (4)如果我们把逻辑放在 Presenter 中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。
MVP 的缺点包括:
由于对视图的渲染放在了 Presenter 中,所以视图和 Presenter 的交互会过于频繁。还有一点需要明白,如果 Presenter 过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么 Presenter 也需要变更了。比如说,原本用来呈现 HTML的 Presenter 现在也需要用于呈现 PDF 了,那么视图很有可能也需要变更。
9.5 面向服务的架构
迄今为止,对于面向服务的架构(Service-Oriented Architecture,SOA)还没有一个公认的定义。许多组织从不同的角度和不同的侧面对 SOA 进行了描述,较为典型的有以下三个:
- (1)W3C 的定义:SOA 是一种应用程序架构,在这种架构中,所有功能都定义为独立的服务,这些服务带有定义明确的可调用接口,能够以定义好的顺序调用这些服务来形成业务流程。
- (2)Service-architecture.com 的定义:服务是精确定义、封装完善、独立于其他服务所处环境和状态的函数。SOA 本质上是服务的集合,服务之间彼此通信,这种通信可能是简单的数据传送,也可能是两个或更多的服务协调进行某些活动。服务之间需要某些方法进行连接。
- (3)Gartner 的定义:SOA 是一种 C/S 架构的软件设计方法,应用由服务和服务使用者组成,SOA 与大多数通用的 C/S 架构模型不同之处,在于它着重强调构件的松散耦合,并使用独立的标准接口。
9.5.1 SOA 概述
SOA 是一种在计算环境中设计、开发、部署和管理离散逻辑单元(服务)模型的方法。
SOA 并不是一个新鲜事物,而只是面向对象模型的一种替代。虽然基于 SOA 的系统并不排除使用 OOD 来构建单个服务,但是其整体设计却是面向服务的。由于 SOA 考虑到了系统内的对象,所以虽然 SOA 是基于对象的,但是作为一个整体,它却不是面向对象的。
SOA 系统原型的一个典型例子是 CORBA,它已经出现很长时间,其定义的概念与 SOA相似。SOA 建立在 XML 等新技术的基础上,通过使用基于 XML 的语言来描述接口,服务已经转到更动态且更灵活的接口系统中,CORBA 中的 IDL 无法与之相比。图 9-13 描述了一个完整的 SOA 模型。
在 SOA 模型中,所有的功能都定义成了独立的服务。服务之间通过交互和协调完成业务的整体逻辑。所有的服务通过服务总线或流程管理器来连接。这种松散耦合的架构使得各服务在交互过程中无需考虑双方的内部实现细节,以及部署在什么平台上。
1. 服务的基本结构
一个独立的服务基本结构如图 9-14 所示。
由图 9-14 可以看出,服务模型的表示层从逻辑层分离出来,中间增加了服务对外的接口层。通过服务接口的标准化描述,使得服务可以提供给在任何异构平台和任何用户接口使用。这允许并支持基于服务的系统成为松散耦合、面向构件和跨技术实现,服务请求者很可能根本不知道服务在哪里运行、是由哪种语言编写的,以及消息的传输路径,而是只需要提出服务请求,然后就会得到答案。
2.SOA 设计原则
在 SOA 架构中,继承了来自对象和构件设计的各种原则,例如,封装和自我包含等。
那些保证服务的灵活性、松散耦合和复用能力的设计原则,对 SOA 架构来说同样是非常重要的。关于服务,一些常见的设计原则如下:
- (1)明确定义的接口。服务请求者依赖于服务规约来调用服务,因此,服务定义必须长时间稳定,一旦公布,不能随意更改;服务的定义应尽可能明确,减少请求者的不适当使用;不要让请求者看到服务内部的私有数据。
- (2)自包含和模块化。服务封装了那些在业务上稳定、重复出现的活动和构件,实现服务的功能实体是完全独立自主的,独立进行部署、版本控制、自我管理和恢复。
- (3)粗粒度。服务数量不应该太多,依靠消息交互而不是远程过程调用,通常消息量比较大,但是服务之间的交互频度较低。
- (4)松耦合。服务请求者可见的是服务的接口,其位置、实现技术、当前状态和私有数据等,对服务请求者而言是不可见的。
- (5)互操作性、兼容和策略声明。为了确保服务规约的全面和明确,策略成为一个越来越重要的方面。这可以是技术相关的内容,例如,一个服务对安全性方面的要求;也可以是与业务有关的语义方面的内容,例如,需要满足的费用或者服务级别方面的要求,这些策略对于服务在交互时是非常重要的。
3. 服务构件与传统构件
服务构件架构(Service Component Architecture,SCA)是基于 SOA 的思想描述服务之间组合和协作的规范,它描述用于使用 SOA 构建应用程序和系统的模型。它可简化使用SOA 进行的应用程序开发和实现工作。SCA 提供了构建粗粒度构件的机制,这些粗粒度构件由细粒度构件组装而成。SCA 将传统中间件编程从业务逻辑分离出来,从而使程序员免受其复杂性的困扰。它允许开发人员集中精力编写业务逻辑,而不必将大量的时间花费在更为底层的技术实现上。
SCA 服务构件与传统构件的主要区别在于,服务构件往往是粗粒度的,而传统构件以细粒度居多;服务构件的接口是标准的,主要是服务描述语言接口,而传统构件常以具体 API形式出现;服务构件的实现与语言是无关的,而传统构件常绑定某种特定的语言;服务构件可以通过构件容器提供 QoS 的服务,而传统构件完全由程序代码直接控制。
9.5.2 SOA 的关键技术
SOA 伴随着无处不在的标准,为企业的现有资产或投资带来了更好的复用性。SOA 能够在最新的和现有的系统之上创建应用,借助现有的应用产生新的服务,为企业提供更好的灵活性来构建系统和业务流程。SOA 是一种全新的架构,为了支持其各种特性,相关的技术规范不断推出。与 SOA 紧密相关的技术主要有 UDDI、WSDL、SOAP 和 REST 等,而这些技术都是以 XML 为基础而发展起来的。
1. UDDI
UDDI(Universal DescriptionDiscovery and Integration,统一描述、发现和集成)提供了一种服务发布、查找和定位的方法,是服务的信息注册规范,以便被需要该服务的用户发现和使用它。UDDI 规范描述了服务的概念,同时也定义了一种编程接口。通过 UDDI 提供的标准接口,企业可以发布自己的服务供其他企业查询和调用,也可以查询特定服务的描述信息,并动态绑定到该服务上。
在 UDDI 技术规范中,主要包含以下三个部分的内容:
- (1)数据模型。UDDI 数据模型是一个用于描述业务组织和服务的 XML Schema。
- (2)API。UDDI API 是一组用于查找或发布 UDDI 数据的方法,UDDI API 基于 SOAP。
- (3)注册服务。UDDI 注册服务是 SOA 中的一种基础设施,对应着服务注册中心的角色。
2.WSDLWSDL(Web ServiceDescription Language,Web 服务描述语言)是对服务进行描述的语言,它有一套基于 XML 的语法定义。WSDL 描述的重点是服务,它包含服务实现定义和服务接口定义,如图 9-15 所示。
采用抽象接口定义对于提高系统的扩展性很有帮助。服务接口定义就是一种抽象的、可重用的定义,行业标准组织可以使用这种抽象的定义来规定一些标准的服务类型,服务实现者可以根据这些标准定义来实现具体的服务。
服务实现定义描述了给定服务提供者如何实现特定的服务接口。服务实现定义中包含服务和端口描述。一个服务往往会包含多个服务访问入口,而每个访问入口都会使用一个端口元素来描述,端口描述的是一个服务访问入口的部署细节,例如,通过哪个地址来访问,应当使用怎样的消息调用模式来访问等。
3.SOAP
SOAP(Simple ObjectAccess Protocol,简单对象访问协议)定义了服务请求者和服务提供者之间的消息传输规范。SOAP 用 XML 来格式化消息,用 HTTP 来承载消息。通过 SOAP,应用程序可以在网络中进行数据交换和远程过程调用(Remote Procedure Call, RPC)。SOAP主要包括以下四个部分:
- (1)封装。SOAP 封装定义了一个整体框架,用来表示消息中包含什么内容,谁来处理这些内容,以及这些内容是可选的还是必需的。
- (2)编码规则。SOAP 编码规则定义了一种序列化的机制,用于交换系统所定义的数据类型的实例。
- (3)RPC 表示。SOAP RPC 表示定义了一个用来表示远程过程调用和应答的协议。
- (4)绑定。SOAP 绑定定义了一个使用底层传输协议来完成在节点之间交换 SOAP 封装的约定。
SOAP 消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求/应答的模式。所有的 SOAP 消息都使用 XML 进行编码。SOAP 消息包括以下三个部分:
- (1)封装(信封)。封装的元素名是 Envelope,在表示消息的 XML 文档中,封装是顶层元素,在 SOAP 消息中必须出现。
- (2)SOAP 头。SOAP 头的元素名是 Header,提供了向 SOAP 消息中添加关于这条SOAP 消息的某些要素的机制。SOAP 定义了少量的属性用来表明这项要素是否可选以及由谁来处理。SOAP 头在 SOAP 消息中可能出现,也可能不出现。如果出现的话,必须是 SOAP封装元素的第一个直接子元素。
- (3)SOAP 体。SOAP 体的元素名是 Body,是包含消息的最终接收者想要的信息的容器。SOAP 体在 SOAP 消息中必须出现且必须是 SOAP 封装元素的直接子元素。如果有头元素,则 SOAP 体必须直接跟在 SOAP 头元素之后;如果没有头元素,则 SOAP 体必须是SOAP 封装元素的第一个直接子元素。
4.REST
REST(RepresentationalState Transfer,表述性状态转移)是一种只使用 HTTP 和 XML 进行基于 Web 通信的技术,可以降低开发的复杂性,提高系统的可伸缩性。它的简单性和缺少严格配置文件的特性,使它与 SOAP 很好地隔离开来,REST 从根本上来说只支持几个操作(POST、GET、PUT 和 DELETE),这些操作适用于所有的消息。REST 提出了如下一些设计概念和准则:
- (1)网络上的所有事物都被抽象为资源。
- (2)每个资源对应一个唯一的资源标识。
- (3)通过通用的连接件接口对资源进行操作。
- (4)对资源的各种操作不会改变资源标识。
- (5)所有的操作都是无状态的。
9.5.3 SOA 的实现方法
SOA 只是一种概念和思想,需要借助于具体的技术和方法来实现它。从本质上来看,SOA 是用本地计算模型来实现一个分布式的计算应用,也有人称这种方法为“本地化设计,分布式工作”模型。CORBA、DCOM 和 EJB 等都属于这种解决方式,也就是说,SOA 最终可以基于这些标准来实现。有关这些标准的知识,已经在 13.1.1 节中详细介绍。另外,这些标准分别使用的 ORB、RPC 和 RMI(Remote Method Invocation,远程方法调用)等技术,将在 17.1.2 节中介绍,此处不再赘述。
从逻辑上和高层抽象来看,目前,实现 SOA 的方法也比较多,其中主流方式有 WebService、企业服务总线和服务注册表。
1.Web Service
在 Web Service(Web 服务)的解决方案中,一共有三种工作角色,其中服务提供者和服务请求者是必需的,服务注册中心是一个可选的角色。它们之间的交互和操作构成了 SOA的一种实现架构,如图 9-16 所示。
- (1)服务提供者。服务提供者是服务的所有者,该角色负责定义并实现服务,使用 WSDL对服务进行详细、准确、规范地描述,并将该描述发布到服务注册中心,供服务请求者查找并绑定使用。
- (2)服务请求者。服务请求者是服务的使用者,虽然服务面向的是程序,但程序的最终使用者仍然是用户。从架构的角度看,服务请求者是查找、绑定并调用服务,或与服务进行交互的应用程序。服务请求者角色可以由浏览器来担当,由人或程序(例如,另外一个服务)来控制。
- (3)服务注册中心。服务注册中心是连接服务提供者和服务请求者的纽带,服务提供者在此发布他们的服务描述,而服务请求者在服务注册中心查找他们需要的服务。不过,在某些情况下,服务注册中心是整个模型中的可选角色。例如,如果使用静态绑定的服务,服务提供者则可以把描述直接发送给服务请求者。
Web Service 模型中的操作包括发布、查找和绑定,这些操作可以单次或反复出现。
- (1)发布。为了使用户能够访问服务,服务提供者需要发布服务描述,以便服务请求者可以查找它。
- (2)查找。在查找操作中,服务请求者直接检索服务描述或在服务注册中心查询所要求的服务类型。对服务请求者而言,可能会在生命周期的两个不同阶段中涉及查找操作,首先是在设计阶段,为了程序开发而查找服务的接口描述;其次是在运行阶段,为了调用而查找服务的位置描述。
- (3)绑定。在绑定操作中,服务请求者使用服务描述中的绑定细节来定位、联系并调用服务,从而在运行时与服务进行交互。绑定可以分为动态绑定和静态绑定。在动态绑定中,服务请求者通过服务注册中心查找服务描述,并动态地与服务交互;在静态绑定中,服务请求者已经与服务提供者达成默契,通过本地文件或其他方式直接与服务进行绑定。
在采用 Web Service 作为 SOA 的实现技术时,应用系统大致可以分为六个层次,分别是底层传输层、服务通信协议层、服务描述层、服务层、业务流程层和服务注册层。
- (1)底层传输层。底层传输层主要负责消息的传输机制,HTTP、JMS(Java MessagingService,Java 消息服务)和 SMTP 都可以作为服务的消息传输协议,其中 HTTP 使用最广。
- (2)服务通信协议层。服务通信协议层的主要功能是描述并定义服务之间进行消息传递所需的技术标准,常用的标准是 SOAP 和 REST 协议。
- (3)服务描述层。服务描述层主要以一种统一的方式描述服务的接口与消息交换方式,相关的标准是 WSDL。
- (4)服务层。服务层的主要功能是将遗留系统进行包装,并通过发布的 WSDL 接口描述被定位和调用。
- (5)业务流程层。业务流程层的主要功能是支持服务发现,服务调用和点到点的服务调用,并将业务流程从服务的底层调用抽象出来。
- (6)服务注册层的主要功能是使服务提供者能够通过 WSDL 发布服务定义,并支持服务请求者查找所需的服务信息。相关的标准是 UDDI。
2. 服务注册表
服务注册表(service registry)虽然也具有运行时的功能,但主要在 SOA 设计时使用。
它提供一个策略执行点(Policy Enforcement Point,PEP),在这个点上,服务可以在 SOA 中注册,从而可以被发现和使用。服务注册表可以包括有关服务和相关构件的配置、依从性和约束文件。从理论上来说,任何帮助服务注册、发现和查找服务合约、元数据和策略的信息库、数据库、目录或其他节点都可以被认为是一个注册表。大多数商用服务注册产品支持服务注册、服务位置和服务绑定功能。
- (1)服务注册。服务注册是指服务提供者向服务注册表发布服务的功能(服务合约),包括服务身份、位置、方法、绑定、配置、方案和策略等描述性属性。使用服务注册表实现SOA 时,要限制哪些新服务可以向注册表发布、由谁发布以及谁批准和根据什么条件批准等,以便使服务能够有序的注册。
- (2)服务位置。服务位置是指服务使用者,帮助它们查询已注册的服务,寻找符合自身要求的服务。这种查找主要是通过检索服务合约来实现的,在使用服务注册表实现 SOA时,需要规定哪些用户可以访问服务注册表,以及哪些服务属性可以通过服务注册表进行暴露等,以便服务能得到有效的、经过授权的使用。
- (3)服务绑定。服务使用者利用查找到的服务合约来开发代码,开发的代码将与注册的服务进行绑定,调用注册的服务,以及与它们实现互动。可以利用集成的开发环境自动将新开发的服务与不同的新协议、方案和程序间通信所需的其他接口绑定在一起。
3. 企业服务总线
ESB 的概念是从 SOA 发展而来的,它是一种为进行连接服务提供的标准化的通信基础结构,基于开放的标准,为应用提供了一个可靠的、可度量的和高度安全的环境,并可帮助企业对业务流程进行设计和模拟,对每个业务流程实施控制和跟踪、分析并改进流程和性能。
在一个复杂的企业计算环境中,如果服务提供者和服务请求者之间采用直接的端到端的交互,那么随着企业信息系统的增加和复杂度的提高,系统之间的关联会逐渐变得非常复杂,形成一个网状结构,这将带来昂贵的系统维护费用,同时也使得 IT 基础设施的复用变得困难重重。ESB 提供了一种基础设施,消除了服务请求者与服务提供者之间的直接连接,使得服务请求者与服务提供者之间进一步解耦。
ESB 是由中间件技术实现并支持 SOA 的一组基础架构,是传统中间件技术与 XML、Web Service 等技术结合的产物,是在整个企业集成架构下的面向服务的企业应用集成机制。
具体来说,ESB 具有以下功能:
- (1)支持异构环境中的服务、消息和基于事件的交互,并且具有适当的服务级别和可管理性。
- (2)通过使用 ESB,可以在几乎不更改代码的情况下,以一种无缝的非侵入方式使现有系统具有全新的服务接口,并能够在部署环境中支持任何标准。
- (3)充当缓冲器的 ESB(负责在诸多服务之间转换业务逻辑和数据格式)与服务逻辑相分离,从而使不同的系统可以同时使用同一个服务,不用在系统或数据发生变化时,改动服务代码。
- (4)在更高的层次,ESB 还提供诸如服务代理和协议转换等功能。允许在多种形式下通过像 HTTP、SOAP 和 JMS 总线的多种传输方式,主要是以网络服务的形式,为发表、注册、发现和使用企业服务或界面提供基础设施。
- (5)提供可配置的消息转换翻译机制和基于消息内容的消息路由服务,传输消息到不同的目的地。
- (6)提供安全和拥有者机制,以保证消息和服务使用的认证、授权和完整性。
在企业应用集成方面,与现存的、专有的集成解决方案相比,ESB 具有以下优势:
- (1)扩展的、基于标准的连接。ESB 形成一个基于标准的信息骨架,使得在系统内部和整个价值链中可以容易地进行异步或同步数据交换。ESB 通过使用 XML、SOAP 和其他标准,提供了更强大的系统连接性。
- (2)灵活的、服务导向的应用组合。基于 SOA,ESB 使复杂的分布式系统(包括跨多个应用、系统和防火墙的集成方案)能够由以前开发测试过的服务组合而成,使系统具有高度可扩展性。
- (3)提高复用率,降低成本。按照 SOA 方法构建应用,提高了复用率,简化了维护工作,进而减少了系统总体成本。
- (4)减少市场反应时间,提高生产率。ESB 通过构件和服务复用,按照 SOA 的思想简化应用组合,基于标准的通信、转换和连接来实现这些优点。
9.5.4 微服务
微服务顾名思义,就是很小的服务,所以它属于面向服务架构的一种。通俗一点来说,微服务类似于古代著名的发明,活字印刷术,每个服务都是一个组件,通过编排组合的方式来使用,从而真正做到了独立、解耦、组件化、易维护、可复用、可替换、高可用、最终达到提高交付质量、缩短交付周期的效果。
从专业的角度来看,微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于 HTTP 协议的 RESTfulAPI)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
所以总结起来,微服务的核心特点为:小, 且专注于做⼀件事情、轻量级的通信机制、松耦合、独立部署。
1.微服务的优势
微服务之所以能盛行,必然是有它独特优势的,下面我们来分析微服务有哪些方面的优势。
- (1)技术异构性
在微服务架构中,每个服务都是一个相对独立的个体,每个服务都可以选择适合于自身的技术来实现。如,要开发一个社交平台,此时,我们可能使用文档型数据库来存储帖子的内容,使用图数据来存储朋友圈的这些关系等,这样可以把每一块的性能都充分发挥出来。
同时,在应用新技术时,微服务架构也提供了更好的试验场。因为对于单块的系统而言,采用一个新的语言、数据库或者框架都会对整个系统产生巨大的影响,这样导致我们想尝试新技术时,望而却步。但微服务不同,我们完全可以只在一个微服务中采用新技术,待技术使用熟练之后,再推广到其他服务。
- (2)弹性
弹性主要讲的是系统中一部分出现故障会引起多大问题。在单块系统中,一个部分出现问题,可能导致整体系统的问题。而微服务架构中,每个服务可以内置可用性的解决方案与功能降级方案,所以比单块系统强。
- (3)扩展
单块系统中,我们要做扩展,往往是整体进行扩展。而在微服务架构中,可以针对单个服务进行扩展。
- (4)简化部署
在大型单块系统中,即使修改一行代码,也需要重新部署整个应用系统。这种部署的影响很大、风险很高,因此不敢轻易地重新部署。而微服务架构中,每个服务的部署都是独立的,这样就可以更快地对特定部分的代码进行部署。
- (5)与结织结构相匹配
我们都知道,团队越大越难管理,同时团队越大也代表系统规模越大代码库越大,这样容易引起一系列的问题。且当团队是分布式的时候,问题更严重。微服务架构就能很好地解决这个问题,微服务架构可以将架构与组织结构相匹配,避免出现过大的代码库,从而获得理想的团队大小及生产力。服务的所有权也可以在团队之间迁移,从而避免异地团队的出现。
- (6)可组合性
在微服务架构中,系统会开放很多接口供外部使用。当情况发生改变时,可以使用不同的方式构建应用,而整体化应用程序只能提供一个非常粗粒度的接口供外部使用。
- (7)对可替代性的优化
在单块系统中如果删除系统中的上百行代码,也许不知道会发生什么,引起什么样的问题,因为单块系统中关联性很强。但在微服务架构中,我们可以在需要时轻易地重写服务,或者删除不再使用的服务。
2. 微服务面临的挑战
软件开发业内有一句名言“软件开发没有银弹”,虽然前面介绍了微服务很多方面的优势,但微服务并不能解决所有问题。下面我们来分析在使用微服务架构时可能面临的一些挑战。
- (1)分布式系统的复杂度
使用微服务实现分布式系统的复杂度要比单块系统高。这表现在多个方面,如:性能方面微服务是拆分成多个服务进行部署,服务间的通信都是通过网络,此时的性能会受影响。
同时可靠性也会受影响。数据一致性也需要严格控制,其成本也比单块系统高。
- (2)运维成本
相比传统的单块架构应用,微服务将系统分成多个独立的部分,每个部分都是可以独立部署的业务单元。这就意味着,原来适用于单块架构的集中式的部署、配置、监控或者日志收集等方式,在微服务架构下,随着服务数量的增多,每个服务都需要独立的配置、部署、监控、日志收集等,因此成本呈指数级增长。
- (3)部署自动化
传统单块系统部署往往是以月、周为单位,部署频度很低,在这种情况下,手动部署是可以满足需求的。而对于微服务架构而言,每个服务都是一个独立可部署的业务单元,每个服务的修改都需要独立部署。这样部署的成本是比较高的,如亚马逊,每天都要执行数十次、甚至上百次的部署,此时仍用人工部署是行不通的,要使用自动化部署。如何有效地构建自动化部署流水线,降低部署成本、提高部署频率,是微服务架构下需要面临的一个挑战。
- (4)DevOps 与组织结构
传统单块架构中,团队通常是按技能划分,如开发部、测试部、运维部,并通过项目的方式协作,完成系统交付。而在微服务架构的实施过程中,除了如上所述的交付、运维上存在的挑战,在组织或者团队层面,如何传递 DevOps 文化的价值,让团队理解 DevOps 文化的价值,并构建全功能团队,也是一个不小的挑战。
微服务不仅表现出一种架构模型,同样也表现出一种组织模型。这种新型的组织模型意味着开发人员和运维的角色发生了变化,开发者将承担起服务整个生命周期的责任,包括部署和监控,而运维也越来越多地表现出一种顾问式的角色,尽早考虑服务如何部署。因此,如何在微服务的实施中,按需调整组织架构,构建全功能的团队,是一个不小的挑战。
- (5)服务间依赖测试
由于微服务架构是把系统拆分为若干个可独立部署的服务,所以需要进行服务间的依赖测试。在服务数量较多的情况下,如何有效地保证服务之间能有效按照接口的约定正常工作,成为微服务实施过程中必须面临的巨大挑战。
- (6)服务间依赖管理
传统的单块系统,功能实现比较集中,大部分功能都运行在同一个应用中,同其他系统依赖较少。而微服务架构则不同,在将系统功能拆分成相互协作的独立服务之后,随着微服务个数的增多,如何清晰有效地展示服务之间的依赖关系,成为了一个挑战。
3.微服务与 SOA微服务可以讲是 SOA 的一种,但仔细分析与推敲,我们又能发现他们的一些差异。这种差异表现在多个方面,具体如表 9-8 所示。
这些差异自然影响到其实现,在实现方面的主要差异如表 9-9 所示。
9.6 架构设计
9.2 节讨论了实现软件质量属性的战术,这些战术可以看做设计的基本“构建块”,通过这些构建块,就可以精心设计系统的软件架构了。
架构模式也称为架构风格,它是适当地选取战术的结果,这些固定的结果(模式)在高层抽象层次上具有普遍实用性和复用性。
通过架构模式,架构设计师可以借鉴和复用他人的经验,看看类似的问题别人是如何解决的。但不要把模式看成是一个硬性的解决方法,它只是一种解决问题的思路。Martin Fowler曾说:“模式和业务构件的区别就在于模式会引发你的思考。”
在本章的后续部分将进一步讨论架构模式。
1. 演变交付生命周期
业界已开发出各种软件生命周期模型,其中把架构放在一个适当位置的模型中,典型的有演变交付生命周期模型,如图 9-17 所示。
在生命周期模型中,架构设计就是从初步的需求分析开始逐步进行循环迭代(图 9-17中的反向箭头说明了这一点)。即:一方面在了解系统需求前,不能开始设计架构;另一方面,刚开始进行设计架构时并不需要等到全部需求都收集到。架构是由“架构驱动”因素“塑造”的,架构因素是指少数关键的、优先级别最高的业务目标质量需求。架构由少数关键需求决定并在循环迭代中处于基本稳定状态,它作为演变的基础设施。
2. 属性驱动设计法
上述模型强调先建立软件架构,再把架构作为骨架,在骨架上循环迭代,逐步长出有血有肉的系统之躯。属性驱动设计法(Attribute-Driven Design,ADD)就是一种定义软件架构的方法,该方法将分解过程建立在软件必须满足的质量属性之上。ADD 的输入为:功能需求(一般表示为用例)、限制条件和质量需求(一组特定于系统的质量场景)。
ADD 的步骤如下。
- (1)选择要分解的模块。通常是整个系统,要求上述输入都是可获得的(限制条件、功能需求、质量需求)。从系统开始,然后分解为子系统,进一步将子系统分解为子模块。
- (2)根据如下步骤对模块进行求精:
从具体的质量场景和功能需求集合中选择架构驱动因素。并不是同等看待所有需求,而是在满足了最重要的需求的条件下,才满足不太重要的需求,即针对架构需求有优先级。
选择满足架构驱动因素的架构模式,根据前面的战术创建(或选择)模式。其目标是建立一个由模块类型组成的总体架构模式。
实例化模块并根据用例分配功能,使用多个视图进行表示。
定义子模块的接口。
验证用例和质量场景,并对其进行求精,使它们成为子模式的限制。
- (3)对需要进一步分解的每个模块重复上述步骤。
3. 按架构组织开发团队
在架构的模块分解结构的最初几个层次相对稳定之后,就可以把这些模块分配给开发小组,其结果就是工作视图。像软件系统一样,开发小组也应该努力做到松耦合、高内聚。即每个模块都构成自己的小领域(专门知识或专门技术),并与其他模块的接口清晰,这样,不同的模块分到不同的开发小组中,就能减少各开发小组之间的沟通成本,而在各开发小组内部,由于是处理小领域的问题,容易建立起有效的沟通机制,如成员有这个小领域的背景知识(或培训获得)、共享决策信息。
同时,项目计划在架构确定之后可以结合分工进一步明细化,特别要规划好接口提供的时间点,保证项目开发的整体协调性。
4. 开发骨架系统
演变交付生命周期模型中有两个循环,第一个循环是通过迭代的方式开发出软件架构,第二个循环是在架构的基础上通过迭代的方式开发出交付的最终版本。开发骨架系统就是第二个循环的第一步。这一步就是以架构为指导,开发一个可运行的原型(骨架系统)。在骨架系统开发过程中要注意对接口进行充分协商,避免先开发的部分强制随后部分满足其不合理的接口要求。骨架系统完成后,就可以在其上进行增量开发,直到软件开发完成。
5. 利用商用构件进行开发
模式本来就是针对特定问题的解,因此,针对需求的特点,也可以选用相应的模式来设计架构,并利用对应于该模式的商用构件进行软件开发。例如可以使用 J2EE/EJB 进行开发面向对象的分布式系统。
9.7 软件架构文档化
记录软件架构的活动就是架构编档过程,也就是架构的文档化。它包含两个方面:一是过程,编档过程能促使架构设计师进一步思考,使得架构更加完善;二是结果,描述架构的文档将作为架构开发的成果,供项目关系人使用。
1. 架构文档的使用者
架构文档的使用者是架构的项目关系人。编写技术文档(尤其是软件架构文档)最基本的原则之一是要从读者的角度来编写,易于编写但很难阅读的文档是不受欢迎的。
架构的主要用途是充当项目关系人之间进行交流的工具,文档则促进了这种交流—— 架构项目关系人希望从架构文档中获得自己所关心的架构信息,如:系统实现人员希望文档提供关于开发活动的不能违反的限制及可利用的自由;测试人员和集成人员希望能从文档中得到必须组合在一起的各部分,并以此得到一个正确的测试黑箱;项目经理希望根据所确定的工作任务组建开发小组,规划和分配项目资源。
2. 合理的编档规则编写架构文档和编写其他文档一样,必须遵守一些基本规则,这里将任何软件编档(包括软件架构编档)的规则归纳为 7 条:
- (1)从读者的角度编写文档。
- (2)避免出现不必要的重复。
- (3)避免歧义。
- (4)使用标准结构。
- (5)记录基本原理。
- (6)使文档保持更新,但更新频率不要过高。
- (7)针对目标的适宜性对文档进行评审。
3. 视图编档
视图是最重要的软件架构编档概念,本书将在 9.11 节中专门讨论架构的视图。视图的概念为架构设计师提供了进行软件架构编档的基本原则。架构文档化就是将相关视图编成文档,并补充多个视图的关联关系。
视图编档的组织结构(内容及编排次序、大纲)虽然目前还没有工业标准模板,研究者(Bachmann 等人)提出的文档组织结构包含 7 个部分,如图 9-18 所示。
- (1)视图概述:对系统进行概括性的描述,包含视图的主要元素和元素间的关系(但并不包含所有元素和元素间的关系,如:与错误处理相关的内容可以放在支持文档中)。主要表示可用多个形式:图形、表格、文本,通常用图形形式,使用UML 语言来描述。
- (2)元素目录:对主要表示中所描述的元素及其关系进行详细描述,包括:元素及其属性、关系及其属性、元素接口、元素行为。
这部分是文档的主要组成部分,其中要注意:
—对元素及其协同工作的行为进行编档,如用UML 的顺序图和状态图描述行为;
—对接口进行编档,图9-19 说明了这部分的内容。
- (3)上下文图:用图形展示系统如何与其环境相关。
- (4)可变性指南:描述架构的可变化点,如在软件产品线中,产品线架构通过变化,适用于多个系统,因此,文档中应包含这些变化点,如各系统要做出选择的选项、做出选择的时间。
- (5)架构背景:为架构的合理性提供足够的、令人信服的论据。包括:基本原理、分析结果及设计中所反映的假定。
- (6)术语表:对文档中每个术语进行简要说明。
- (7)其他信息:描述不属于架构方面的必要信息,如管理信息(创作者、配置控制数据及变更历史)。
4. 跨视图文档
软件架构由多个视图文档来反映,按前面所述的要求完成每个视图的文档后,需要对这些文档进行一个整体的“打包”工作,这就是跨视图文档。它包括如下内容:
- (1)文档有哪些内容,它们是如何组织的:视图目录(含哪些视图);视图模板(即前面描述的视图文档,企业可以通过规范化来定义统一的、公共的视图模板)。
- (2)架构概述:它描述系统的目的、视图之间的关联、元素表及索引、项目词汇。
- (3)为什么架构是这样的(基本原理):跨视图基本原理解释了整体架构实际上是其需求的一个解决方案。即解释了做出决策的原因、方案的限制、改变决策时的影响及意义。
5. 使用 UML
UML 已经成为对软件架构进行文档化的事实上的标准表示法。在视图文档的组织结构中,UML 主要用于表示元素或元素组的行为。
有关 UML 的详细内容,请阅读 8.4.3 节。
6. 软件架构重构
前面已论述了架构编档,即在架构设计时完成编档工作。但是还有另外一种情况:系统已经存在,但不知其架构,即架构没有通过文档很好地保留下来(文档的缺失/失效)。如何维护这样的系统并管理其演变?其关键就是要找到软件架构,软件架构重构就是研究解决这一问题的方法,它是反向工程之一。
架构重构需要工具的支持,但任何一个工具或工具集对架构重构都是不够的。因为:
工具往往是面向特定语言的;
数据提取工具经常返回不完整的或错误的结果,因此,应在多个工具提供的结果间进行补充、验证和判断;
重构的目的(文档的用途)不同,决定了需要提取什么数据,这反过来影响了工具的选择。
以此为原则,就是架构重构的工作台方法,如 SEI 开发的 Dali。
软件架构重构由以下活动组成,这些活动以迭代方式进行,如图 9-20 所示。
- (1)信息提取(View Extraction)。可以使用各种工具进行信息提取,如解析器、语法分析器等;可以利用 build 和 makefile 文件中关于模块的依赖关系;可以从源代码、编译时制品和设计制品中提取静态信息;可以使用分析工具提取动态信息。
- (2)数据库构造(Database Construction):将提取的信息转化为标准的形式,并置于数据库中。
- (3)视图融合(View Fusion):将数据库中的信息组合在一起,生成该架构的一个内聚的视图。
- (4)重构(Reconstruction):构建数据抽象和各种表示以生成架构表示,主要由两个活动组成:可视化和交互、模式定义和识别。最后生成需要的架构文档(Documentation)。
上述过程中,架构是由重构人员通过对系统做出一组假定来获得,为了最有效地生成这些假定并对其进行验证,必须让熟悉系统的人参与此项工作,包括过去参与系统开发的人员或现在正在对其进行维护的人员。
9.8 软件架构评估
软件架构评估是在对架构分析、评估的基础上,对架构策略的选取进行决策。它也可以灵活地运用于对软件架构进行评审等工作中。
9.8.1 软件架构评估的方法
业界已开发出多种软件架构评估的方法,按基于的技术手段来看,可以分为三类:基于调查问卷或检查表的方式、基于场景的方式和基于度量的方式。
- (1)基于调查问卷或检查表的方式:该方式的关键是要设计好问卷或检查表,它充分利用系统相关人员的经验和知识,获得对架构的评估。其缺点是在很大程度上依赖于评估人员的主观推断。
- (2)基于场景的方式:基于场景的方式由 SEI 首先提出并应用在架构权衡分析法(Architecture Tradeoff Analysis Method,ATAM)和软件架构分析方法(Software ArchitectureAnalysis Method,SAAM)中。它是通过分析软件架构对场景(也就是对系统的使用或修改活动)的支持程度,从而判断该架构对这一场景所代表的质量需求的满足程度。下节将对ATAM 进行重点介绍。
- (3)基于度量的方式:它是建立在软件架构度量的基础上的,涉及三个基本活动,首先需要建立质量属性和度量之间的映射原则,即确定怎样从度量结果推出系统具有什么样的质量属性;然后从软件架构文档中获取度量信息;最后根据映射原则分析推导出系统的质量属性。它能提供更为客观和量化的质量评估,但它对评估人员及其使用的技术有较高的要求。ATAM 中也使用了度量的思想(度量效用)。
9.8.2 架构的权衡分析法
从技术角度对软件架构进行评估,旨在通过分析来预见软件的质量;通过分析来创建、选择、评估与比较不同的架构。例如,Kazman 等人在 2000 年提出的架构的 ATAM 方法。
ATAM 方法不但能够揭示架构如何满足特定的质量需求(例如,性能和可修改性),而且还提供了分析这些质量需求之间交互作用的方法。使用 ATAM 方法评价一个软件架构的目的是理解架构设计满足系统质量需求的结果。
ATAM 产生如下结果。
- (1)一个简洁的架构表述:ATAM 的一个要求是在一小时内表述架构,这样就得到了一个简洁、可理解的、面向普通项目关系人的架构表述。它是从架构文档中提炼形成的。
- (2)表述清楚的业务目标。
- (3)用场景集合捕获质量需求。
- (4)架构决策到质量需求的映射。
- (5)所确定的敏感点和权衡点集合:这个集合是一些对一个或多个质量属性具有显著影响的架构决策。如:备份数据库就是这样一个架构决策,它对可靠性产生正面影响,而对系统性能产生负面影响,因此需要进行权衡。
- (6)有风险决策和无风险决策。
- (7)风险主题的集合。找到这些风险主题旨在采取相应的措施。
- (8)产生一些附属结果。评估过程形成的文档(经受住了评估的考验)可以作为经验保留下来。
- (9)还产生一些无形结果,如能够使项目关系人产生“团队感”,提供了一个交流平台和沟通渠道,使大家更好地理解架构(优势及弱点)。
ATAM 的 9 个步骤如下。
- (1)ATAM 方法的表述:评估负责人向参加会议的项目代表介绍 ATAM(简要描述 ATAM步骤和评估的结果)。
- (2)商业动机的表述。项目决策者从商业角度介绍系统的概况。
- (3)架构的表述。对架构进行详略适当的介绍。设计师要描述用来满足需求的架构方法或模式,还应描述技术约束条件及与其他系统的交互等。
- (4)对架构方法进行分类。通过研究架构文档及倾听上一步的表述,了解系统使用的架构模式和方法(进行明确命名)。
- (5)生成质量属性效用树。可以选取这样一棵树:根——质量属性——属性求精(细分)——场景(叶)。修剪这棵树,保留重要场景(不超过 50 个),再对场景按重要性给定优先级(用 H/M/L 的形式),再按场景实现的难易度来确定优先级(用 H/M/L 的形式),这样对所选定的每个场景就有一个优先级对(重要度,难易度),如(H,L)表示该场景重要且易实现。
- (6)分析架构方法。评估小组按优先级对上述效用树的场景进行分析(小组成员提问,设计师回答、解释),探查实现场景的架构方法。评估小组把相关架构决策编成文档,确定其有风险决策、无风险决策、敏感点、权衡点,并对其进行分类(分别用表格列出)。
- (7)集体讨论并确定场景的优先级。由于项目关系人的不同角色及所关心的场景不一致,因此,应鼓励项目关系人考虑效用树中尚未分析过的场景。集体讨论后,可通过投票的方式获得各场景的优先级。通过把集体讨论确定了优先级的一组场景与效用树中的那组场景进行比较,能发现设计师所想的与项目关系人实际所要的是否存在差距,这一差距是否导致风险。
- (8)分析架构方法。类似于第 6 步,这时,评估小组引导设计师实现在第 7 步中得到的优先级最高的场景。
- (9)结果的表述。把在 ATAM 分析中得到的各种信息进行归纳总结,并呈现给项目关系人。主要有:
已编写了文档的架构方法;
经过讨论得到的场景集合及其优先级;
效用树;
所发现的有风险决策;
已编成文档的无风险决策;
所发现的敏感点和权衡点。
9.8.3 成本效益分析法
在大型复杂系统中最大的权衡通常必须考虑经济性,因此,需要从经济角度建立成本、收益、风险和进度等方面软件的“经济”模型。成本效益分析法(the Cost Benefit AnalysisMethod,CBAM)是在 ATAM 上构建,用来对架构设计决策的成本和收益进行建模,是优化此类决策的一种手段。CBAM 的思想就是架构策略影响系统的质量属性,反过来这些质量属性又会为系统的项目关系人带来一些收益(称为“效用”),CBAM 协助项目关系人根据其投资回报(ROI)选择架构策略。CBAM 在 ATAM 结束时开始,它实际上使用了 ATAM 评估的结果。
CBAM 的步骤如下。
- (1)整理场景。整理 ATAM 中获取的场景,根据商业目标确定这些场景的优先级,并选取优先级最高的 1/3 的场景进行分析。
- (2)对场景进行求精。为每个场景获取最坏情况、当前情况、期望情况和最好情况的质量属性响应级别。
- (3)确定场景的优先级。项目关系人对场景进行投票,其投票是基于每个场景“所期望的”响应值,根据投票结果和票的权值,生成一个分值(场景的权值)。
- (4)分配效用。对场景的响应级别(最坏情况、当前情况、期望情况和最好情况)确定效用表。
- (5)架构策略涉及哪些质量属性及响应级别,形成相关的策略—场景—响应级别的对应关系。
- (6)使用内插法确定“期望的”质量属性响应级别的效用。即根据第 4 步的效用表以及第 5 步的对应关系,确定架构策略及其对应场景的效用表。
- (7)计算各架构策略的总收益。根据第 3 步的场景的权值及第 6 步的架构策略效用表,计算出架构策略的总收益得分。
- (8)根据受成本限制影响的 ROI(Return On Investment,投资报酬率)选择架构策略。
根据开发经验估算架构策略的成本,结合第 7 步的收益,计算出架构策略的 ROI,按 ROI 排序,从而确定选取策略的优先级。
9.9 构件及其复用
软件企业为了提高开发效率,越来越注重软件元素的复用(也称重用),因此,架构设计师在进行架构设计时,必须关注复用,例如,考虑丰富企业构件和充分使用已有的构件。
本节从构件角度研究如何使用软件复用技术,下一节重点讨论基于产品线的软件复用。
与复用技术密切相关的概念是构件(component,组件),业界对构件还没有公认的定义,如下为几种常见的定义。
定义 1:构件是指软件系统中可以明确辨识的构成成分。而可复用构件(reusablecomponent)是指具有相对独立的功能和可复用价值的构件。
定义 2:构件是一个组装单元,它具有约定式规范的接口及明确的依赖环境。
定义 3:构件是软件系统中具有相对独立功能、可以明确辨识、接口由契约指定、和语境有明显依赖关系、可独立部署的可组装软件实体。
对构件更广义的理解是把所有种类的工作成品(例如,各类文档、方案、计划、测试案例、代码)都看成是可复用的构件。
9.9.1 商用构件标准规范
当前,主流的商用构件标准规范包括 OMG(Object Management Group,对象管理组织)
的 CORBA、SUN 的 J2EE 和 Microsoft 的 DNA。
1. CORBA
CORBA(Common ObjectRequest Broker Architecture,公共对象请求代理架构)主要分为3 个层次:对象请求代理、公共对象服务和公共设施。最底层的对象请求代理(Object RequestBroker,ORB),规定了分布对象的定义(接口)和语言映射,实现对象间的通信和互操作,是分布对象系统中的“软总线”;在 ORB 之上定义了很多公共服务,可以提供诸如并发服务、名字服务、事务(交易)服务、安全服务等各种各样的服务;最上层的公共设施则定义了构件框架,提供可直接为业务对象使用的服务,规定业务对象有效协作所需的协定规则。
CORBA CCM(CORBA ComponentModel,CORBA 构件模型)是 OMG 组织制定的一个用于开发和配置分布式应用的服务器端构件模型规范,它主要包括如下 3 项内容。
- (1)抽象构件模型:用以描述服务器端构件结构及构件间互操作的结构。
- (2)构件容器结构:用以提供通用的构件运行和管理环境,并支持对安全、事务、持久状态等系统服务的集成。
- (3)构件的配置和打包规范:CCM 使用打包技术来管理构件的二进制、多语言版本的可执行代码和配置信息,并制定了构件包的具体内容和文档内容标准。
2.J2EE
在 J2EE 中,SUN 给出了完整的基于 Java 语言开发面向企业分布的应用规范,其中,在分布式互操作协议上,J2EE 同时支持 RMI(Remote Method Invocation,远程方法调用)
和 IIOP(Internet Inter-ORB Protocol,互联网内部对象请求代理协议),而在服务器端分布式应用的构造形式,则包括了 Java Servlet、JSP、EJB 等多种形式,以支持不同的业务需求,而且 Java 应用程序具有跨平台的特性,使得 J2EE 技术在发布计算领域得到了快速发展。
其中,EJB 给出了系统的服务器端分布构件规范,这包括了构件、构件容器的接口规范,以及构件打包、构件配置等的标准规范内容。EJB 技术的推出,使得用 Java 基于构件方法开发服务器端分布式应用成为可能。从企业应用多层结构的角度,EJB 是业务逻辑层的中间件技术,与 JavaBeans 不同,它提供了事务处理的能力,自从三层结构提出以后,中间层,也就是业务逻辑层,是处理事务的核心,从数据存储层分离,取代了存储层的大部分地位。
从 Internet 技术应用的角度,EJB 和 Servlet,JSP 一起成为新一代应用服务器的技术标准,EJB 中的 Bean 可以分为会话 Bean 和实体 Bean,前者维护会话,后者处理事务,通常由Servlet 负责与客户端通信,访问 EJB,并把结果通过 JSP 产生页面传回客户端。
3. DNA 2000
Microsoft DNA 2000 是 Microsoft 在推出 Windows 2000 系列操作系统平台的基础上,在扩展了分布计算模型,以及改造 Back Office 系列服务器端分布计算产品后发布的新的分布计算架构和规范。在服务器端,DNA 2000 提供了 ASP、COM、Cluster 等的应用支持。
DNA 2000 融合了当今最先进的分布计算理论和思想,例如,事务处理、可伸缩性、异步消息队列、集群等内容。DNA 可以开发基于 Microsoft 平台的服务器构件应用,其中,如数据库事务服务、异步通信服务和安全服务等,都由底层的分布对象系统提供。
Microsoft 的 DCOM/COM/COM+技术,在 DNA 2000 分布计算结构基础上,展现了一个全新的分布构件应用模型。首先,DCOM/COM/COM+ 的构件仍然采用普通的 COM(Component Object Model,构件对象模型)模型。COM 最初作为 Microsoft 桌面系统的构件技术,主要为本地的 OLE(Object Linking and Embedding,对象连接与嵌入)应用服务,但是随着 Microsoft 服务器操作系统 Windows NT 和 DCOM(Distributed Component ObjectModel,分布式构件对象模型)的发布,COM 通过底层的远程支持使得构件技术延伸到了分布应用领域。DCOM/COM/COM+更将其扩充为面向服务器端分布应用的业务逻辑中间件。
通过 COM+的相关服务设施,如负载均衡、内存数据库、对象池、构件管理与配置等,DCOM/COM/COM+将 COM、DCOM、MTS(Microsoft Transaction Server,微软事物处理服务器)的功能有机地统一在一起,形成了一个概念、功能强的构件应用架构。
通过购买商用构件(平台)并遵循其开发标准来进行应用开发,是提高应用软件开发效率的常见选择
9.9.2 应用系统簇与构件系统
除专门开发构件的企业外,开发应用系统的企业也会发展自己的构件应用体系:通常是随着企业的不断成熟,逐步从已开发的应用系统中整理出来一些构件,反过来,将这些构件复用到优化与整合已有应用系统中或复用于开发新的应用系统。
一个应用系统中的复用率毕竟有限,通常在应用系统簇中进行软件构件复用。当要开发若干相关的应用系统时,可以先按复用的要求,界定这一组应用系统的共同“特性”,根据这些共同特性,建立模型,并按照复用的要求,将模型分解成恰当规模和结构的构件,对这些构件进行设计、实现、打包、编写文档,形成方便使用的可复用构件。这批可复用构件将用于支持该应用簇的各个应用系统的开发工作。这里的构件特指一个封装的代码模块或大粒度的运行模块。
软件企业将相关的构件有机地组织在一起,形成构件系统(较构件库层次更高),实施复用的软件企业通常拥有多个构件系统,有的是购置的,有的是自己开发的。
应用系统和构件系统都是系统产品(而不是工作产品)。它们都可以采用模型和结构的类型定义出来。一般情况下,构件系统只在开发单位内部使用,而应用系统提供给外部客户,与应用系统相比,构件系统具有通用性,可复用性,这就要求构件系统的开发过程应当实施更为严格的工程规范。
一个构件系统是能提供一系列可复用特性的系统产品。构件系统中的构件应当是高内聚、低耦合的,但构件之间应有若干种关系;可以发送消息给其他构件;可以与其他构件联合,支持协同工作。构件系统应当是易于理解和易于使用的,对构件应当是仔细地进行建模、实现、制作文档、测试等,便于以后的有效维护和改进。通常为支持构件的复用,应开发与构件系统相配的工具箱。
应用系统可以向构件系统输入构件(构件的需求源于应用系统或应用系统中的模块),反过来,构件系统向应用系统输出构件。这就是构件系统如何获得构件和如何提供构件的方式。
9.9.3 基于复用开发的组织结构
基于复用的开发组织与传统的开发组织结构不同,它需要有一部分用于开发可复用资产的资源,这部分资源应同具体应用系统的开发资源分开,以确保不被占用。
一种较平衡的组织结构如图 9-21 所示,它有三类职能部门:一是构件系统开发部门,它开发可复用资产;二是应用系统项目开发部(多个),它复用资产;三是支持部门,这个部门是可选的,它进一步隔离上述两主体部门,虽然牺牲了一些效率,但保证了构件的规范性。它的主要职责是对构件开发部门所提供的可复用资产进行确认、对构件库进行分类编目、向开发应用系统的工程师们发通告和分发可复用资产、提供必要的文档、从复用者处收集反馈信息和缺陷报告。可以这样理解:构件系统开发部门开发的构件系统由支持部门向应用开发部门推广,即支持部门是企业内的推广部。外部购买的商用构件也由支持部门维护与管理。
这三个平行部门之上有一个高层经理(复用经理),他关注总目标,协调相互关系。
一方面,构件开发者应当尽量接近应用开发者,以使其开发出的构件能尽量符合实际需要;另一方面,构件开发者与应用开发者分属两个并列的部门,使构件开发者能摆脱应用项目的日常压力,保证可复用资产的开发和持续改进。复用经理应当在构件开发和应用项目开发利益之间进行权衡,保证长期目标不受近期项目压力的影响。
9.10 产品线及系统演化
软件企业追求长远的发展,通常采用产品线模型及系统演化策略,它实质上是用架构技术构建产品线,并在此基础上借助复用技术持续演化,不断地推出新产品,满足市场追求产品升级换代的需求。
9.10.1 复用与产品线
软件产品线是指一组软件密集型系统,它们共享一个公共的、可管理的特性集,满足某个特定市场或任务的具体需要,是以规定的方式用公共的核心资产集成开发出来的。即围绕核心资产库进行管理、复用、集成新的系统。核心资产库包括软件架构及其可剪裁的元素,更广泛地,它还包括设计方案及其文档、用户手册、项目管理的历史记录(如预算和进度)、软件测试计划和测试用例。复用核心资产(特别是软件架构),更进一步采用产品线将会惊人地提高生产效率、降低生产成本和缩短上市时间。
创建一个成功的产品线取决于软件工程、技术管理和组织管理等多个方面的协调,这里只对软件架构方面进行讨论。
基于产品间共性的“软件”产品线代表了软件工程中一个创新的、不断发展的概念。软件产品线的本质是在生产产品家族时,以一种规范的、策略性的方法复用资产。可复用的资产非常广,包括以下几点。
需求:许多需求与早期开发的系统相同或部分相同,如网上银行交易与银行柜面交易。
架构设计:原系统在架构设计方面花费了大量的时间与精力,系统成功验证了架构的合理性,如果新产品能复用已有的架构,将会取得很好的效益。
元素:元素复用不只是简单的代码复用,它旨在捕获并复用设计中的可取之处,避免(不要重复)设计失败的地方。
建模与分析:各类分析方法(如性能分析)及各类方案模型(如容错方案、负载均衡方案)
都可以在产品中得到复用。
测试:采用产品线可积累大量的测试资源,即在考虑测试时不是以项目为单位,而是以产品线为单位,这样,整个测试环境都可以得到复用,如测试用例、测试数据、测试工具,甚至测试计划、过程、沟通渠道都可以得到复用。
项目规划:利用经验对项目的成本、预算、进度及开发小组的安排等进行预测,即不必每次都建立工作分解结构。
过程、方法和工具:有了产品线这面旗帜,企业就可以建立产品线级的工作流程、规范、标准、方法和工具环境,供产品线中所有产品复用。如编码标准就是一例。
人员:以产品线来培训的人员,适应于整个系列的各个产品的开发。
样本系统:将已部署(投产)的产品作为高质量的演示原型和工程设计原型。
缺陷消除:产品线开发中积累的缺陷消除活动,可使新系统受益,特别是整个产品家族中的性能、可靠性等问题的一次性解决,能取得很高的回报。同时也使得开发人员和客户心中有“底”。
9.10.2 基于产品线的架构
软件产品线架构是针对一系列产品而设计的通用架构,并在此基础上,进一步将系列产品共用的模块事先实现,供直接重用;将架构用框架的形式予以实现,供定制使用。这就是通常所说的“平台”。
产品线架构较之单个产品架构,有如下三点特别之处:
- (1)产品线架构必须考虑一系列明确许可的变化;
- (2)产品线架构一定要文档化;
- (3)产品线架构必须提供“产品创建者指南”(开发指南),描述架构的实例化过程。
产品线的软件架构应将不变的方面提出来(正因为有不变,才产生了产品线),同时,识别允许的变化,并提供实现它们的机制。通常应考虑三个方面。
- (1)确定变化点:确定变化是一项需要持续进行的活动,可以在开发过程的任何时间确定变化。
- (2)支持变化点:对变化的支持可以有多种形式,举例如下。
包含或省略元素:如条件编译。
包含不同数量的复制元素:如通过添加更多的服务器产生高容量的变体。
具有相同的接口但具有不同的行为或质量特性的元素版本的选择:如静态库和动态链接库的使用。
在面向对象的系统中,通过特化或泛化特定的类实现变化。
通过参数配置来实现变化。
- (3)对产品线架构的适宜性进行评估。
评审方法:见下节的软件架构评估。
评估的内容:必须把评估的重点放在变化点上,以确保它(变化点)提供了足够的灵活性,从而能覆盖产品线的预期范围;它们支持快速构建产品。
评估的时间:应该对用于构建产品线中的一个或多个产品的架构的实例或变体进行评估。产品架构评估的结果通常能提供有用的反馈,推动架构的改进。当提议开发的一个新产品不在最初的产品线的范围内时,则可以重新对产品线架构进行评估,看如何使其容纳新的产品或是否有必要产生一个新的产品线。
9.10.3 产品线的开发模型
开发(确定)产品线的方法有两种模型:
- (1)“前瞻性”产品线:利用在应用领域的经验、对市场和技术发展趋势的了解及商业判断力等进行产品线设计,它反映了企业的战略决策。通常是自上而下地采用产品线方法。
- (2)“反应性”模型:企业根据以前的产品构建产品家族,并随着新产品的开发,扩展架构和设计方案,它的核心资产库是根据“已经证明”为共有、而非“预先计划”为共有的元素构建的。通常是自下而上地采用产品线方法。
一旦确定了产品线,就进入其演变阶段,它是产品线不断向前的发展过程。
9.10.4 特定领域软件架构
架构的本质在于其抽象性。它包括两个方面的抽象:业务抽象和技术抽象。其中业务抽象面向特定的应用领域。
特定领域软件架构(Domain Specific Software Architecture,DSSA)可以看做开发产品线的一个方法(或理论),它的目标就是支持在一个特定领域中有多个应用的生成。DSSA 的必备特征有:
- (1)一个严格定义的问题域或解决域;
- (2)具有普遍性,使其可以用于领域中某个特定应用的开发;
- (3)对整个领域的合适程度的抽象;
- (4)具备该领域固定的、典型的在开发过程中的可复用元素。
从功能覆盖的范围角度理解 DSSA 中领域的含义有两种方法:
- (1)垂直域。定义了一个特定的系统族,导出在该领域中可作为系统的可行解决方案的一个通用软件架构。
- (2)水平域。定义了在多个系统和多个系统族中功能区域的共有部分,在子系统级上涵盖多个系统(族)的特定部分功能。
DSSA 的活动阶段如下。
- (1)领域分析:主要目标是获得领域模型。即通过分析领域中系统的需求(领域需求),确定哪些需求是被领域中的系统广泛共享的,从而建立领域模型。
- (2)领域设计:这个阶段的目标是获得 DSSA,它是一个能够适应领域多个系统的需求的一个高层次的设计。由于领域模型中的领域需求具有一定的变化性,DSSA 也要相应地具有变化性,它可以通过表示多选一的、可选的解决方案等来做到这一点。
- (3)领域实现:主要目标是依据领域模型和 DSSA 开发与组织可复用信息。这些复用信息可以是从现有系统中提取得到的,也可能通过新的开发得到。这个阶段可以看作复用基础设施的实现阶段。
在上述工作中,获得领域模型是基础也是关键,领域建模专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。通常领域模型可用 UML 的类图和状态图表示。
希赛教育专家提示:对于中等复杂度的项目,应该在系统的领域模型中找到大约50 到100 个类。
领域模型的主要作用如下:
- (1)领域模型为需求定义了领域知识和领域词汇,这较之单一的项目需求更有较好的大局观;
- (2)软件界面的设计往往和领域模型关系密切;
- (3)领域模型的合理性将严重影响软件系统的可扩展性;
- (4)在分层架构的指导下,领域模型精化后即成为业务层的骨架;
- (5)领域模型也是其数据模型的基础;
- (6)领域模型是团队交流的基础,因为它规定了重要的领域词汇表,并且这些词汇的定义是严格的、大家共同认可的。
9.10.5 架构及系统演化
架构虽然为系统的变化提供了一定的自由度,但是系统的较大变化必然导致架构的改变。
架构(系统)演化是指向既定的方向、可控地改变。架构(系统)演化可以形成产品线,反过来,架构(系统)可以在规划的产品线中进行演化。
架构(系统)演化过程包含 7 个步骤,如图 9-22 所示。
- (1)需求变动归类。首先,必须对用户需求的变化进行归类,使变化的需求与已有构件对应。对找不到对应构件的变动,也要做好标记,在后续工作中,将创建新的构件,以对应这部分变化的需求。
- (2)制订架构演化计划。在改变原有结构之前,开发组织必须制订一个周密的架构演化计划,作为后续演化开发工作的指南。
- (3)修改、增加或删除构件。在演化计划的基础上,开发人员可根据在第(1)步得到的需求变动的归类情况,决定是否修改或删除存在的构件、增加新构件。最后,对修改和增加的构件进行功能性测试。
- (4)更新构件的相互作用。随着构件的增加、删除和修改,构件之间的控制流必须得到更新。
- (5)构件组装与测试。通过组装支持工具把这些构件的实现体组装起来,完成整个软件系统的连接与合成,形成新的架构。然后,对组装后的系统整体功能和性能进行测试。
- (6)技术评审。对以上步骤进行确认,进行技术评审。评审组装后的架构是否反映需求变动,符合用户需求。如果不符合,则需要在第(2)到第(6)步之间进行迭代。
- (7)产生演化后的架构。在原来系统上所作的所有修改必须集成到原来的架构中,完成一次演化过程。
9.11 软件架构视图
9.10 节从软件架构本身的特点出发讨论了架构建模及与特定应用领域密切相关的架构风格。本节将从对架构编档的角度对软件架构视图及其风格进行讨论。
9.11.1 软件视图的分类
现代软件系统非常复杂,通常在某个具体的时间内只需将注意力集中在某几个结构上(就像看病时,医生只是将注意力集中在某方面的人体结构上,骨科医生与心血管科医生关心不同的结构),结构是元素本身的集合,而视图则是捕获和表达结构(文档描述),虽然它们有区别,但在实际使用时则不严格区分,即从系统体系的角度说是结构,从文档角度说是视图,因此,本节将不再区分结构和视图术语。
软件架构是一种无法以简单的一维方式进行说明的复杂实体,从不同侧面的描述就是视图。架构的优势也在于使用视图:每个视图强调系统的某一个方面,同时忽视系统的其他方面,以便有助于处理或理解当前问题,描述完整的系统架构必须具备完整的视图集, “4+1”
方法就是一类完备视图集。
软件视图通常分为三种类型:
- (1)模块视图类型:为系统的主要模块实现单元编档。
- (2)构件和连接件视图类型:为系统的构件和连接件执行单元编档。
- (3)分配视图类型:为软件的开发和执行环境之间的关系编档。每一视图类型中,又有一些常用的形态,可以把这些形态归纳成架构风格(简称风格),
大量的架构风格供架构设计师选用,例如客户机/服务器是一种常见的架构风格,它是构件和连接件视图类型中的一员。架构风格是对元素和关系类型的特化,它还包括如何使用这些元素和关系类型的一组限制条件。架构结构/视图分类如表 9-10 所示。下面各小节中再分别对这三种类型及其风格从元素、关系及特征方面做进一步总结。
9.11.2 模块视图类型及其风格
模块将遵循某种方式将软件系统分解成可管理的功能单元。架构模块视图是通过文档来枚举系统的主要实现单元或模块,及这些单元之间的关系。
任务完整的架构文档必须包含有模块视图,它为源代码提供蓝图。该类型如表 9-11 所示。
下面对模块视图的四种风格进行总结。
- (1)分解风格能展示向模块分配责任的方式。该风格总结如表 9-12 所示。
- (2)使用风格能展示模块相互依赖的方式。该风格总结如表 9-13 所示。
- (3)分层风格能将系统分割成一组虚拟机,通过“允许使用”关系相互关联,分层风格能帮助实现可移植性和可修改性。该风格总结如表 9-14 所示。
- (4)泛化风格能展示一个模块如何成为另一个模块的泛化或特化,从而使模块之间产生关联。它广泛应用于面向对象的系统,能展示继承性,并能用来使用模块之间的共性。该风格总结如表 9-15 所示。
9.11.3 C&C 视图类型及其风格
C&C 视图能定义由具有某种运行时存在的元素模型,这些元素包括进程、对象、客户机、服务器及数据存储器等。此外,它还包含作为元素的交互路径,如通信链路和协议、信息流及共享存储器访问。通常,可利用复杂的基础结构(如中间件框架、分布式通信信道和进程调度)来执行这些交互操作。该类型总结如表 9-16 所示。
C&C 视图风格是 C&C 视图类型的特化,C&C 视图风格为数不少,下面对 C&C 视图的几种风格进行总结。
- (1)管道和过滤器风格中的交互模式表现出数据流连续变换的特征。数据抵达过滤器并经过转换后由管理传送给下一个过滤器。该风格总结如表 9-17 所示。
- (2)共享数据风格通过保留持久数据来支配交互模式,持久数据由多个数据存取器和至少一个储存库保留。该风格总结如表 9-18 所示。
- (3)发布-订阅风格用于向一组未知接受者发送事件和消息。可在不修改生产者的情况下添加新的接受者(订阅者)。在发布-订阅风格中,构件通过事件发布进行交互。构件可订阅一组事件。该风格总结如表 9-19 所示。
- (4)客户机-服务器风格能展示构件通过请求其他构件的服务进行交互的过程,将功能划分成客户机和服务器后即可基于运行时准则把它们单独分配给各个级。该风格总结如表9-20 所示。
点击查看大图
- (5)对等连接系统能通过构件之间的直接交换支持服务交换。它是一种调用/返回风格。
该风格总结如表 9-21 所示。
- (6)通信-进程风格的特征表现在通过各种连接件机制并发执行构件的交互,如通过同步、消息传递、数据交换、启动和停止等进行交互。该风格总结如表 9-22 所示。
9.11.4 分配视图类型及其风格
硬件、文件系统和团队结构都会与软件架构进行交互,将软件架构映射到其环境的一般形式称为“分配视图类型”。该类型总结如表 9-23 所示。
分配视图类型的三种常见风格为:
部置风格:能描述构件和连接件对硬件的映射,硬件是软件执行的场所。
实现风格:能描述模块对包含它们的文件系统的映射。
工作任务风格:能描述模块对承担模块开发任务的人员、团队或小组的映射。
- (1)部置风格体现为 C&C 风格(如通信-进程风格)的元素被分配到执行平台。该风格总结如表 9-24 所示。
- (2)实现风格能将模块视图类型中的模块映射到开发基础结构。实现一个模块总会产生许多独立文件,必须对这些文件进行组织,以免失去对系统的控制及系统的完整性。通常利用配置管理技术进行文件管理。该风格总结如表 9-25 所示。
- (3)软件项目的时间和预算估计取决于工作分解结构(WBS),而工作分解结构则取决于软件架构。工作任务风格将软件架构映射到由人组成的团队之中,实现这一项目管理的目的。该风格总结如表 9-26 所示。
工作任务风格与模块分解风格关系密切,它能将模块分解风格用作其分配映射的基础。
这种风格能通过添加与开发工具、测试工具和配置管理系统等对应的模块分解进行扩展。工作任务风格还通常与其他风格联合使用,例如,团队工作任务可以是模块分解风格中的模块,可以是分层图中的层,也可以是多进程系统中的任务或进程。
9.11.5 各视图类型间的映射关系
为了完整地描述一个架构,必须使用多个视图,这些视图必须遵守一定的映射关系。
- (1)模块视图类型中的视图通常会映射到构件和连接件视图类型中的视图。模块实现单元将映射到运行时构件。
- (2)系统的构件和连接件视图和模块视图之间的关系可能会非常复杂。同样的代码模块可由 C&C 视图的许多元素执行。反之,C&C 视图的单一构件可执行由许多模块定义的代码。同样,C&C 构件可能会拥有许多与环境进行交互的点,每个交互点由同一模块接口定义。
- (3)分配视图类型是为有效地实现软件架构的辅助性视图,它将其他视图类型中的软件元素映射到软件环境中,即反映其他视图与软件环境之间的关系。