Layered Application(分层应用程序)

Layered Application(分层应用程序)

 

发布日期: 2004-04-19 | 更新日期: 2004-04-19

版本:1.0.1

本页内容
上下文上下文
问题问题
影响因素影响因素
解决方案解决方案
示例示例
结果上下文结果上下文
致谢致谢

上下文

您正在设计复杂的企业应用程序,该应用程序由跨越多个抽象级别的大量组件组成。

问题

如何构造应用程序,以支持诸如可维护性、可重用性、可伸缩性、可靠性和安全性等运行要求?

影响因素

构造应用程序时,必须协调环境内的下列影响因素:

仅限于对解决方案的某一部分进行更改以便尽量降低对其他部分的影响,从而减少调试和纠错的工作量,使应用程序易于维护,并增强应用程序的总体灵活性。

将所关注的问题分隔在不同的组件中(例如,将用户界面与业务逻辑分隔开来,并将业务逻辑与数据库分隔开来),以增强灵活性、可维护性和可伸缩性。

组件应该可被多个应用程序重用。

独立的团队应该能够在处理解决方案的各个部分时尽量减少对其他团队的依赖,并且应该能够针对定义明确的接口展开开发工作。

各个组件应保持内聚性。

无关的组件应保持松散耦合。

应按照不同的时间安排,独立部署、维护和更新解决方案的各个组件。

跨越过多的组件边界会对性能造成不利的影响。

要使 Web 应用程序既安全又可访问,需要将应用程序分布在多个物理级。这使您可以保护应用程序位于防火墙后面的部分,并使其他组件可从 Internet 被访问到。

要确保高性能和高可靠性,解决方案必须是可测试的。

解决方案

将解决方案的组件分隔到不同的层中。每一层中的组件应保持内聚性,并且应大致在同一抽象级别。每一层都应与它下面的各层保持松散耦合。 Pattern-Oriented Software Architecture, Vol 1 [Buschmann96]对分层过程的描述如下:

从最低级别的抽象开始--称为第 1 层。这是系统的基础。通过将 第 J 层放置在第 J-1 层的上面逐步向上完成抽象阶梯,直到到达功能的最高级别 - 称为第 N 层。

图 1 显示了此分层架构的图示。

Arc_Layered Application_Fig01

1:

结构

Layered Application的关键是依赖性管理。一层中的组件只能与同一级别中的对等实体或较低级别中的组件交互。这有助于减少不同级别中的组件之间的依赖性。有两种通用的分层方法:严格分层和松散分层。

严格分层方法限制一层中的组件只能与对等实体以及与它紧邻的下面一层进行交互。例如,如果应用程序的分层如图 1 所示,那么,第 J 层只能与第 J-1 层中的组件进行交互,第 J-1 层只能与第 J-2 层进行交互,依次类推。

松散的分层应用程序放宽了此限制,它允许组件与位于它下面的任意层中的组件进行交互。因此,在图 1 中,第 J 层不仅可以与第 J-1 层交互,而且可以与第 J-2 层和第 J-3 层交互。

松散方法可以改善效率,因为系统不必将简单调用从一层转发到下一层。另一方面,松散方法在层之间不提供相同的隔离级别,并使得在不影响较高层的情况下换出较低层变得更困难。

对于包含许多软件组件的大型解决方案,常见的方法是使不内聚的大量组件处于同一抽象级别。在这种情况下,每一层可以进一步分解为一个或多个内聚的子系统。图 2 显示了由多个子系统组成的表示层的可能的 UML 表示法。

Arc_Layered Application_Fig02

2: 由子系统组成的多层的 UML 表示

通常使用下列技术扩充基本的 Layered Application 模式。

Layer Supertype(层超类型)[Fowler03]. 如果一层中的组件具有相同的一组行为,就可以将这些行为提取到一个公共类或组件中,并使层中的所有组件都继承该公共类或组件。这不仅简化了维护并提高了可重用性,还允许通过对超类型(而不是特定组件)的运行时引用来调用公共行为,从而减少了层之间的依赖性。

抽象接口。抽象接口是为较高级别中的组件所调用的某一层中的每个组件定义的。较高层通过抽象接口(而不是直接调用组件)来访问较低级别组件。这使得可以在不影响较高级别组件的情况下更改较低级别组件的实现。

层外观 (Layer Facade)对于大型系统,常见的方法是使用 Facade 模式来为层或子系统提供单个统一接口,而不是为每个公开的组件分别开发一个抽象接口 [Gamma95]。这使得层之间具有最低的耦合,因为较高级别组件仅直接引用外观。请务必认真设计外观。在后期改变外观是非常困难的,因为有许多组件都将依赖于它。

动力

分层应用程序中存在两种基本的交互模式:

由上而下

由下而上

在由上而下模式中,外部实体与栈中的最高层交互。最高层使用较低级别层的一个或多个服务。反过来,每个较低级别都使用它下面的层,直到到达最低层。

由于本文仅仅进行讨论,因此此模式假定外部实体是客户端应用程序,并且分层应用程序是将其功能作为一组服务公开的基于服务器的应用程序。图 3 是描述常见的由上而下方案的 UML 序列图。

Arc_Layered Application_Fig03

3:由上而下方案的序列图

在此方案中,客户端应用程序使用基于服务器的应用程序提供的一组服务。这些服务由服务器应用程序的最高层公开。因此,客户端必须只与最高层交互,而无法直接了解任何较低的层。有几个因素需注意。

首先,一个传入调用会导致多个传出调用。第 N 层上的服务 1 的调用说明了这种情况。当较高级别服务聚合几个较低级别服务的结果,或协调多个必须按特定的顺序执行的较低级服务的执行时,通常会发生这种情况。例如,ASP.NET 页可以将客户域组件的输出提供给订单组件,而订单组件的输出将提供给发票组件。

第二,此方案说明了松散的分层方法。服务 2 的实现绕过了所有中间层而直接调用第 1 层。这种情况的常见示例是绕过任何中间业务逻辑层而直接访问数据访问层的表示层。数据维护应用程序通常使用这种方法。

第三,顶层服务的调用并不一定会调用所有层。这一概念是通过服务 1 到操作 2 顺序来说明的。当较高级别可以处理自身中的调用,或者缓存了较早的某个请求的结果时,就会发生这种情况。例如,域组件通常缓存数据库查询的结果,这使得将来调用时不必调用数据访问层。

在由下而上模式中,第 1 层检测影响较高级别的情形。下面的方案假定第 1 层监视某些外部实体(例如,服务器应用程序所运行的服务器的文件系统)的状态。图 4 以 UML 序列图的形式描述了典型的由下而上方案。

Arc_Layered Application_Fig04

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 [Fowler03] 一书。

测试考虑事项

Layered Application 在下面几个方面增强了可测试性:

由于每一层都只通过定义明确的接口与其他层交互,因此很容易插入层的备用实现。这允许在某一层所依赖的其他层完成之前即可对它进行某些测试。此外,如果备用实现可以立即返回一组已知的良好数据,那么可以用备用实现来替代需要很长时间才能计算出正确答案的层,从而加快测试的执行。如果使用分层超类型、抽象接口和层外观技术,那么这一能力会大大增强,因为它们进一步减少了层之间的依赖性。

测试各个组件变得更容易,因为组件之间的依赖性受到约束:较高级别组件只能调用较低级别组件。这有助于为进行测试而隔离各个组件,并便于使用特殊用途的测试组件换出较低级别组件。

示例

对于企业应用程序体系结构而言,将其解决方案组合成下列三层是很常见的:

表示. 这一层负责与用户的交互。

业务. 这一层实现解决方案的业务逻辑。

数据. 这一层封装访问持续的数据存储(如关系数据库)的代码。

有关详细信息,请参阅 Three-Layered Services Application 模式。

结果上下文

Layered Application 具有下列优缺点:

优点

由于层之间的低耦合、层之间的高内聚,以及交换层接口的不同实现的能力,解决方案的维护和增强变得更容易。

其他解决方案应该能够重用各个层所公开的功能,尤其在设计层接口时考虑到了重用的情况下更应如此。

如果分布式开发可以分布在层边界,那么此项工作将会变得更容易。

将层分布在多个物理级可以改善可伸缩性、容错和性能。有关详细信息,请参阅Tiered Distribution 模式。

具有定义明确的层接口以及交换层接口的各个实现的能力提高了可测试性。

缺点

穿越各层(而不是直接调用组件)所需的额外开销会对性能造成不利的影响。要帮助弥补性能损失,可以使用松散的分层方法。通过这种方法,较高层可以直接调用较低层。

如果分层禁止使用与数据库直接交互的用户界面组件,那么开发用户密集的应用程序有时可能需要更长的时间。

层的使用有助于控制和封装大型应用程序的复杂性,但增加了简单应用程序的复杂性。

对较低级别接口的改变可能会渗透到较高级别,尤其是在使用了松散的分层方法的情况下可能性更大。

致谢

Buschmann96] Buschmann, Frank, et al. Pattern-Oriented Software Architecture, Vol 1. Wiley & Sons, 1996.

[Fowler03] Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.

[Gamma95] Gamma, Helm, Johnson, and Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值