在构建企业类解决方案时,不仅涉及到开发自定义软件,而且还涉及到将该软件部署到生产服务器环境中。这是软件开发工作与系统基础结构工作的交叉点。如果将这两个原则放在一起,则更加要求您对所涉及到的问题有一个基本的了解,并具备一套强大的应用程序和系统基础结构技能。单个小组极少具备所需的全部技能;因 此,部署活动通常需要几个具备专门技能的小组通过协作完成。为了简化讨论,本章假定有两个小组:应用程序开发小组和系统基础结构小组。
将小组联系在一起
应用程序开发小组负责开发和维护一组能够满足应用程序要求的软件组件。该小组主要在乎的是能否快速而又灵活地满足功能要求。其成员设法通过创建使系统便于扩展和维护的软件抽象来管理复杂性。
系统基础结构小组负责构建和维护服务器及网络基础结构。它的成员主要关心诸如安全性、可用性、可靠性和性能等操作要求。稳定性和可预知性是成功的关键因素,它们主要通过控制更改和管理已知的良好配置来解决。
影响应用程序开发小组的因素与影响系统基础结构小组的因素有很大的区别,其结果是在这两个小组之间存在固有的牵制。如果这种牵制得不到解决,则最终的解决方 案对于其中的一个小组来说可能是最优的,但它将不是最优的业务解决方案。为了提供一个完整的、针对业务整体需求而优化的软件密集型企业解决方案,解决该牵制是一个关键因素。
本章中的模式通过指导如何以最优方式构造可高效满足解决方案要求的应用程序和技术基础结构,有助于减小小组之间的牵制。随后,通过这些模式讨论如何将软件结构映射到硬件结构。特别是,本章提供了一组使您能够实现下列功能的模式:
- 按逻辑分层组织软件应用程序。
- 优化逻辑分层方法以提供和使用服务。
- 按物理级组织硬件以便扩展。
- 优化三级配置中的物理层策略。
- 通过部署规划将进程分配给处理器。
模式概述
尽管层和级的概念通常可互换,但是本章中的模式对这两个术语进行了明显的区分。层是构成软件解决方案的元素的逻辑结构机制;级是系统基础结构的物理结构机制。该群集中的第一组模式按层处理软件应用程序的逻辑结构。第二组模式按级展开系统基础结构的物理结构。图 1 显示了这两组模式及其相互关系。
图1部署群集
应用程序模式
该群集中的第一个模式是 Layered Application, 分层应用程序),它按一组逻辑分层来组织软件应用程序,以便管理依赖性并创建可插入的组件。该模式确切定义了何为层,然后描述了如何定义自己的层。它还描 述了基于分层应用程序构建并扩大了其价值的其他一些技术。Layered Application 的一个主要好处在于其完善定义的接口和强大的依存关系管理使您可以非常灵活地部署应用程序。虽然很难在多个服务器之间分布单层应用程序,但是在层边界处划 分应用程序并将不同的部件分布到多个服务器更容易。但是,由于形成分层决策的因素不同于形成分布决策的因素,所以并非所有的层边界都形成了很好的分布边界。
Layered Application 在软件开发世界被广泛应用。针对企业应用程序的常见模式实现是 Layered Application。这种实现定义了三个层:表示、业务和数据。虽然您可以添加更多层,但是对于企业业务应用程序,几乎总是需要这三层。
大多数企业应用程序现在都使用基于组件的方法进行开发。尽管组件有多种定义,但其中最简单的定义是:组件是一个可单独部署的独立软件功能。运行时,可在公开一组约定接口的执行环境中插入和拔出组件。这种可插拔性在部署期间提供了巨大的灵活性。组件的独立特性使它们成为了可针对其进行部署决策的最小单位。
Three-Layered Services Application (三层服务应用程序)优化Layered Application,以便为那些与面向服务的更大体系结构中的其他企业应用程序协作的企业应用程序提供具体的构造指导。它详述前面描述的三个典型层,并为每个层定义了一组组件类型。
基础结构模式
该群集中的下一组模式强调物理基础结构。这些模式的上下文是支持跨多个服务器分布应用程序的基础结构。特别是,这些模式不涉及大型机或其他大型多处理器基础结构配置。
Tiered Distribution(分级分布)按一组物理级来组织系统基础结构,以便提供针对特定操作要求和系统资源使用而优化的特定服务器环境。单级基础结构不够灵活;一般来说,服务器必须按照最严格的操作要求进行配置和设计,并且必须支持用户最多时在高峰期对系统资源的使用。与之相反,多级支持多个环境。您可以针对一组特定的操作要求和系 统资源使用来优化每个环境。然后,可以将组件部署到最能满足它们的资源需求并使它们能够以最佳方式满足其操作要求的级上。使用的级数越多,部署每个组件时 供您选择的范围就越大。
Three-Tiered Distribution(三级分布)优化了Tiered Distribution ,以便为构造具有基本安全性和其他操作要求的 Web 应用程序提供特定的指导。该模式建议按三级组织解决方案服务器:客户端、Web 应用程序和数据。客户端级和数据级的功能不言自明;Web 应用程序级作为应用程序业务组件以及 Web 表示组件的宿主。对于具有更严格的安全性和操作要求的解决方案,您可能应该考虑将 Web 功能移到单独一级上。
将应用程序和基础结构组合在一起
该群集中的最后一个模式是 Deployment Plan(部署规划),它描述用来将组件分配到级的过程。在分配过程中,一定要确保应用程序开发小组和系统基础结构小组之间良好的沟通。前面讲述的所有模式都能够增加 软件应用程序和系统基础结构的部署灵活性。Deployment Plan 取决于部署灵活性,这样小组在解决利益冲突时选择范围更广。解决这些冲突之后,可以有更多的机会来提供具有最优业务价值的解决方案。作为对该模式的总结, 最后描述了通常在将该过程应用于企业应用程序时生成的四种常见模型:简单 Web 应用程序、复杂 Web 应用程序、扩展企业和富客户端。
Layered Application(分层应用程序)
设计复杂的企业应用程序,该应用程序由跨越多个抽象级别的大量组件组成。
如何构造应用程序,以支持诸如可维护性、可重用性、可伸缩性、可靠性和安全性等运行要求?
构造应用程序时,必须协调环境内的下列影响因素:
- 仅限于对解决方案的某一部分进行更改以便尽量降低对其他部分的影响,从而减少调试和纠错的工作量,使应用程序易于维护,并增强应用程序的总体灵活性。
- 将所关注的问题分隔在不同的组件中(例如,将用户界面与业务逻辑分隔开来,并将业务逻辑与数据库分隔开来),以增强灵活性、可维护性和可伸缩性。
- 组件应该可被多个应用程序重用。
- 独立的团队应该能够在处理解决方案的各个部分时尽量减少对其他团队的依赖,并且应该能够针对定义明确的接口展开开发工作。
- 各个组件应保持内聚性。
- 无关的组件应保持松散耦合。
- 应按照不同的时间安排,独立部署、维护和更新解决方案的各个组件。
- 跨越过多的组件边界会对性能造成不利的影响。
- 要使 Web 应用程序既安全又可访问,需要将应用程序分布在多个物理级。这使您可以保护应用程序位于防火墙后面的部分,并使其他组件可从 Internet 被访问到。
- 要确保高性能和高可靠性,解决方案必须是可测试的。
解决方案
将解决方案的组件分隔到不同的层中。每一层中的组件应保持内聚性,并且应大致在同一抽象级别。每一层都应与它下面的各层保持松散耦合。 Pattern-Oriented Software Architecture, Vol 1对分层过程的描述如下:
从最低级别的抽象开始--称为第 1 层。这是系统的基础。通过将 第 J 层放置在第 J-1 层的上面逐步向上完成抽象阶梯,直到到达功能的最高级别 - 称为第 N 层。
图 1 显示了此分层架构的图示。
图 1: 层
结构
Layered Application的关键是依赖性管理。一层中的组件只能与同一级别中的对等实体或较低级别中的组件交互。这有助于减少不同级别中的组件之间的依赖性。有两种通用的分层方法:严格分层和松散分层。
严格分层方法限制一层中的组件只能与对等实体以及与它紧邻的下面一层进行交互。例如,如果应用程序的分层如图 1 所示,那么,第 J 层只能与第 J-1 层中的组件进行交互,第 J-1 层只能与第 J-2 层进行交互,依次类推。
松散的分层应用程序放宽了此限制,它允许组件与位于它下面的任意层中的组件进行交互。因此,在图 1 中,第 J 层不仅可以与第 J-1 层交互,而且可以与第 J-2 层和第 J-3 层交互。
松散方法可以改善效率,因为系统不必将简单调用从一层转发到下一层。另一方面,松散方法在层之间不提供相同的隔离级别,并使得在不影响较高层的情况下换出较低层变得更困难。
对于包含许多软件组件的大型解决方案,常见的方法是使不内聚的大量组件处于同一抽象级别。在这种情况下,每一层可以进一步分解为一个或多个内聚的子系统。图 2 显示了由多个子系统组成的表示层的可能的 UML 表示法。
图 2: 由子系统组成的多层的 UML 表示
通常使用下列技术扩充基本的 Layered Application 模式。
- Layer Supertype (层超类型). 如果一层中的组件具有相同的一组行为,就可以将这些行为提取到一个公共类或组件中,并使层中的所有组件都继承该公共类或组件。这不仅简化了维护并提高了可重用性,还允许通过对超类型(而不是特定组件)的运行时引用来调用公共行为,从而减少了层之间的依赖性。
- 抽象接口。抽象接口是为较高级别中的组件所调用的某一层中的每个组件定义的。较高层通过抽象接口(而不是直接调用组件)来访问较低级别组件。这使得可以在不影响较高级别组件的情况下更改较低级别组件的实现。
- 层外观 (Layer Facade) 。对于大型系统,常见的方法是使用 Facade 模式来为层或子系统提供单个统一接口,而不是为每个公开的组件分别开发一个抽象接口 [Gamma95]。这使得层之间具有最低的耦合,因为较高级别组件仅直接引用外观。请务必认真设计外观。在后期改变外观是非常困难的,因为有许多组件都 将依赖于它。
动力
分层应用程序中存在两种基本的交互模式:
- 由上而下
- 由下而上
在由上而下模式中,外部实体与栈中的最高层交互。最高层使用较低级别层的一个或多个服务。反过来,每个较低级别都使用它下面的层,直到到达最低层。
由于本文仅仅进行讨论,因此此模式假定外部实体是客户端应用程序,并且分层应用程序是将其功能作为一组服务公开的基于服务器的应用程序。图 3 是描述常见的由上而下方案的 UML 序列图。
图 3: 由上而下方案的序列图
在此方案中,客户端应用程序使用基于服务器的应用程序提供的一组服务。这些服务由服务器应用程序的最高层公开。因此,客户端必须只与最高层交互,而无法直接了解任何较低的层。有几个因素需注意。
首 先,一个传入调用会导致多个传出调用。第 N 层上的服务 1 的调用说明了这种情况。当较高级别服务聚合几个较低级别服务的结果,或协调多个必须按特定的顺序执行的较低级服务的执行时,通常会发生这种情况。例 如,ASP.NET 页可以将客户域组件的输出提供给订单组件,而订单组件的输出将提供给发票组件。
第二,此方案说明了松散的分层方法。服务 2 的实现绕过了所有中间层而直接调用第 1 层。这种情况的常见示例是绕过任何中间业务逻辑层而直接访问数据访问层的表示层。数据维护应用程序通常使用这种方法。
第三,顶层服务的调用并不一定会调用所有层。这一概念是通过服务 1 到操作 2 顺序来说明的。当较高级别可以处理自身中的调用,或者缓存了较早的某个请求的结果时,就会发生这种情况。例如,域组件通常缓存数据库查询的结果,这使得将来调用时不必调用数据访问层。
在由下而上模式中,第 1 层检测影响较高级别的情形。下面的方案假定第 1 层监视某些外部实体(例如,服务器应用程序所运行的服务器的文件系统)的状态。图 4 以 UML 序列图的形式描述了典型的由下而上方案。
图 4: 由下而上方案的序列图
在此方案中,第1层监视本地文件系统的状态。当它检测到更改时,将激发由第 J-1 层中的组件公开的事件。然后,该组件调用第 J 层(更新域层的状态时所处的位置)的回调委派。然后,域组件通知第 N 层它已被第 N 层为此目的而提供的委派更新。
与第一种方案相同,在一个级别中的输入会导致多个输出。较低层可以通知高于它的任何层,而不仅仅是它上面的那一层。最后,通知不一定必须穿过整个链。
请 注意由下而上方案中层的交互与由上而下方案中层的交互有哪些不同。在由上而下方案中,较高层直接调用较低层,因此依赖于这些层。但是,在由下而上方案中, 较低层通过事件、回调和委派来与较高层通信。要防止较低层依赖于较高层,这种级别的非直接性是必需的。使较低层依赖于较高层减少了分层体系结构所提供的许 多优点。
实现
实现Layered Application 模式有两种基本的方法:
- 创建您自己的分层架构。
- 重用现有的分层架构。
创建您自己的分层架构
Buschmann提供了关于自己实现分层应用程序的深入讨论。这里提供的仅仅是简述。如果您需要定义自己的分层应用程序,那么强烈建议您研究 Buschmann 中的 Layers 模式。此过程概述如下:
- 使用定义明确的一组标准将解决方案的功能组织成一组层,并定义每一层所提供的服务。这是一个繁复的过程,即您可能需要尝试标准、级别数、功能分解和服务分配的多种组合。描述层和解决方案组件交互作用的 UML 序列图是帮助您了解每一种备选的分层方案优缺点的理想工具。
- 定 义每一级别之间的接口以及它们彼此通信所需的协议。要避免使较低级别依赖于较高级别,应对需要穿越栈的通信使用异步消息传递、回调和事件等技术。同 样,UML 序列图是确保您的接口组完整一致的理想工具。该图为您判断接口和协议的粒度或详细性提供了一种可视化线索。请特别注意给定方案中跨越层边界的次数,并寻找 机会以便重新调整设计以减少跨越边界的次数。关键的设计决策是确定级别之间应存在什么程度的耦合。第 J 层中的组件是否直接访问第 J-1 中的组件?这使得较高级别依赖于较低级别的实现细节。应研究 Facade 等模式以及其他去耦合技术,以便将这种类型的耦合控制在最小。
- 设 计层的实现。传统的面向对象的设计技术很适合于这一任务。请务必考虑 Adapter、Bridge 和 Strategy [Gamma95] 等模式,以便可以通过多种方法实现给定层的接口。当要测试接口和级别实现时,这一能力尤为重要。另一关键的设计决策是考虑如何处理错误。必须定义对于所有 级别而言一致的错误处理策略。当设计错误处理策略时,应考虑下列因素:
- 尽可能在最低级别处理错误
- 避免通过异常处理机制将较低级别抽象公开给较高级别。
- 如果您必须使异常沿栈上移,那么应将较低级别异常转化为对于处理层而言有一定意义的异常。
重用现有的分层架构
另 一种方法是重用现有的参考分层应用程序,以便为您的应用程序提供所需的结构。规范的三层应用程序由下列三层组成:表示、域和数据源。即便像这么简单的事物 也需要经过很多的努力才能实现 Layered Application 模式所具有的优点。规范模型的增强版本在 Layered Services Application 中讨论。
Martin Fowler 已发现在表示层和域层之间以及域层和数据源层之间使用调解层有时会很有用。有关详细信息,请参阅 Fowler 撰写的 Patterns of Enterprise Application Architecture一书。
测试考虑事项
Layered Application 在下面几个方面增强了可测试性:
- 由于每一层都只通过定义明确的接口与其他层交互,因此很容易插入层的备用实现。这允许在某一层所依赖的其他层完成之前即可对它进行某些测试。此外,如果备用 实现可以立即返回一组已知的良好数据,那么可以用备用实现来替代需要很长时间才能计算出正确答案的层,从而加快测试的执行。如果使用分层超类型、抽象接口 和层外观技术,那么这一能力会大大增强,因为它们进一步减少了层之间的依赖性。
- 测试各个组件变得更容易,因为组件之间的依赖性受到约束:较高级别组件只能调用较低级别组件。这有助于为进行测试而隔离各个组件,并便于使用特殊用途的测试组件换出较低级别组件。
示例
对于企业应用程序体系结构而言,将其解决方案组合成下列三层是很常见的:
- 表示 . 这一层负责与用户的交互。
- 业务 . 这一层实现解决方案的业务逻辑。
- 数据 . 这一层封装访问持续的数据存储(如关系数据库)的代码。
Layered Application 具有下列优缺点:
优点
- 由于层之间的低耦合、层之间的高内聚,以及交换层接口的不同实现的能力,解决方案的维护和增强变得更容易。
- 其他解决方案应该能够重用各个层所公开的功能,尤其在设计层接口时考虑到了重用的情况下更应如此。
- 如果分布式开发可以分布在层边界,那么此项工作将会变得更容易。
- 将层分布在多个物理级可以改善可伸缩性、容错和性能。有关详细信息,请参阅Tiered Distribution 模式。
- 具有定义明确的层接口以及交换层接口的各个实现的能力提高了可测试性。
缺点
- 穿越各层(而不是直接调用组件)所需的额外开销会对性能造成不利的影响。要帮助弥补性能损失,可以使用松散的分层方法。通过这种方法,较高层可以直接调用较低层。
- 如果分层禁止使用与数据库直接交互的用户界面组件,那么开发用户密集的应用程序有时可能需要更长的时间。
- 层的使用有助于控制和封装大型应用程序的复杂性,但增加了简单应用程序的复杂性。
对较低级别接口的改变可能会渗透到较高级别,尤其是在使用了松散的分层方法的情况下可能性更大。