分享

如何组织应用程序的结构

 yingqiu_sunny 2013-12-06

适用于

应用程序使用的软件设计和构架

总结

软件架构一般定义为应用程序的结构。在定义这些结构的时候,软件架构师的目标就是使用不同级别的抽象,通过根据关注点把功能进行分割来最小化复杂度。我们会从最高层的抽象以及不同的关注点开始研究。因为设计的过程中需要不断深入这些层次、扩展关注点直到定义了结构为止。

内容

目标

概览

概要步骤

第一步——选择我们的分层策略

第二步——定义层之间的接口

第三步——选择我们的部署策略

第四步——选择通讯协议

其它资源

目标

找出并选择分层策略

定义层之间的接口

找出并选择部署策略

选择合适的通讯协议

概览

在开始应用程序设计的时候,我们第一个任务就是关注最高层次的抽象并且开始把功能分组到不同的层中。然后,我们需要根据我们所设计的应用程序的类型为每一层定义公共的接口。在定义了层和接口之后我们就需要决定应用程序是如何部署的。最后,最后一个步骤包含选择会用于在应用程序不同逻辑层和物理层之间通讯的协议。

概要步骤

第一步——选择我们的分层策略

第二步——定义层之间的接口

第三步——选择我们的部署策略

第四步——选择通讯协议

第一步——选择我们的分层策略

层表示组件按照不同关注点的逻辑分组。例如,业务逻辑表示应该分组到某层的一个关注点。这可能是应用程序最初设计中最重要的一个抉择。然而,有很多种方式可以把功能进行分层。在完成了本步骤之后你应该能理解如何来组织应用程序的分层结构。

下图演示了用于大多数业务应用程序设计的主要层。在这个示例中,功能按照表现逻辑、服务逻辑、业务逻辑和数据访问逻辑进行分组。如果服务没有公开的话,就不需要服务层,那么我们应该只有表现、业务和数据访问层。


 

这只是一个示例,不是对应用程序进行分层的唯一方式。然而,一般来说分层在软件设计中很常见。底层的操作系统设计使用了RING的方式,RING0(内核)表示最低的层,它可以直接访问诸如CPU和内存等硬件资源。设备驱动作为外层RING通过RING0提供的接口和硬件进行交互。应用程序代码包含在最外部的RING中,它们通过设备驱动来和硬件交互。

除了层,我们还有一些功能会跨越层,它们通常称作横切。此类功能包含诸如日志以及异常管理等。有不同的方式来处理这种功能,比如公共类库、使用元数据直接在编译输出中插入横切代码的面向方面编程(AOP)等。

在为应用程序选择分层策略的时候,如下事项是我们需要考虑的关键点:

决定我们是否需要层

选择我们需要的层

决定我们是否需要合并层

决定层之间交互的规则

决定我们是否需要层

在大多数情况下总是需要把功能进行分层。然而,在有些特例下不需要。例如,如果我们构建一个非常小的应用程序,可能就会考虑在用户界面事件处理程序中实现表现、业务逻辑以及数据访问功能。此外,一些软件工程师会认为跨层边界或增加层带来的开销会对性能有负面影响。

 

是否使用分层的决定说白了就是性能对可维护性。虽然不使用分层可以提升性能,因为所有的东西都紧密耦合在了一起。但是,这种耦合会严重影响应用程序的扩展和维护能力。说实话,对可扩展性和可维护性的影响远比获得的那一点性能的提升重大。不管怎么始终应该考虑分层,我们的目标其实是决定应用程序需要怎么样合适的分层。

选择我们需要的层

有几种不同的方式来把功能进行分层。例如一种分层的方式就是表现、服务和数据访问。许多关注业务领域的面向对象设计使用又一种不同的分层策略,最底层用于基础结构代码,提供类似数据访问的支持。上面一层就是领域层,用于包含业务领域对象,最顶层包含应用程序代码。

对于使用微软.NET Framework开发的大多数业务应用程序来说,按照表现、服务、业务以及数据访问对功能进行分组是不错的选择。下面描述了每一层中的一些组件,以及什么时候不需要这层。

表现 -这层包含用于处理用户接口请求以及呈现用户接口输出的组件。如果我们的应用程序没有用户接口的话就不需要表现层。

服务-这层包含用于处理服务请求的组件。例如,对于使用Windows Communication Foundation (WCF)的应用程序,我们就会找到定义契约的组件、用于实现接口以及提供在实现类和外部契约类之间提供转换支持的组件。如果我们的应用程序不提供服务,则不需要在设计中包含服务层。

Business业务-这层包含用于处理表现或服务层请求的组件。在这层中的组件包括业务处理、业务实体以及业务工作流。在一些情况下,我们不需要有任何的业务层并且只需要从数据源获取数据,也就是说这种情况下不需要业务层。

数据访问-这层包含用于管理和数据源交互的组件。例如,使用ADO.NET的话在数据访问层中就会有连接和命令组件。如果我们的应用程序不使用外部数据的话那么我们就不需要实现数据访问层。

决定我们是否需要合并层

例如,应用程序具有很有限的业务规则并且主要关注验证的话可以在一层中实现业务和表现逻辑。
在以下情况考虑合并层:

 如果我们的业务规则很有限,在一层中实现业务和表现逻辑是合理的。

如果我们的应用程序从服务获取数据并且显示数据,可以直接为表现层添加服务引用并且直接使用服务数据。这样的话就可以合并数据访问和表现。

 

如果数据访问服务直接访问数据库中的表或视图的话可以考虑在服务层中实现数据访问功能。

还有一些适用于合并分层的示例。然而,基本准则是我们总是应该把功能分层。在一些情况下,层只不过是一层调用并且没有提供什么功能。然而,如果把功能进行分离的话我们就可以在影响很小或没有影响的情况下扩展其功能。

决定层之间的交互规则

分层策略带来的一个问题是我们需要定义层之间如何交互的规则。指定这样的一种交互规则的主要原因是最小化依赖以及消除循环引用。例如,如果两个层依赖另外那个层的组件就很可能带来循环依赖。最常见的准则是只允许层之间的单向交互。

从上到下的交互 比较高的层可以和其下层进行交互,但是低层不可以和其上的层进行交互。在设计中总是应该强制这样的准则来避免层之间的循环依赖。

紧密交互每一层只能直接和其下的层进行交互。这个准则可以强制严格的关注分离,应该每一层只知道其直接下属层。这个准则的好处是对某一层的修改只会影响其直接上层。

松散交互 高层可以跳过一些层直接和底层进行交互。这可以增加性能,但是也会增加依赖。换句话说,对底层的修改可能会影响其上的多个层。


考虑使用紧密交互规则:

如果我们设计的应用程序会不断修改来增加新的功能,并且我们希望最小化改变的影响。

如果我们设计的应用程序可能会跨物理层进行分布。


考虑使用松散交互规则:

如果我们设计的应用程序肯定不会跨物理层进行分布。例如是安装在客户机上的独立富客户端应用程序。

如果我们设计一个很小的应用程序,影响多层的改动工作量不大。

检查点

在结束本步骤之前,你应该可以回答如下问题:

你是否找到了应用程序需要的功能并且把功能分层?

你是否定义了层之间如何交互的规则?

第二步——定义层之间的接口

对于为层定义接口,最主要的原则是在层之间使用松散耦合。意思就是层不应该公开内部组件让其它层来依赖。而是,层的接口应该设计为最小的依赖性,提供公共接口并且隐藏层中组件的细节。隐藏细节叫做抽象,有很多种方式实现抽象。

如下设计方式用来为层定义接口:

抽象接口可以通过定义抽象基类或类型定义来实现。基类或类型定义了所有层消费者用于和层进行交互的公共接口。通过使用实现抽象接口的测试对象,有的时候又叫做mock对象,可以增进可测试性。

公共设计类型许多设计模式定义表示不同层中接口的对象类型。这些对象类型提供了一个抽象,它隐藏了层中的细节。例如,表数据入口模式定义了表示数据库中表的对象类型。这些对象负责实现和数据库交互的必要SQL代码。对象的消费者不需要知道使用的SQL甚至不需要知道连接数据库和执行命令的细节。

依赖倒置 这是一种编程模式,抽象接口定义在任何层的外部或不依赖于任何层。层不依赖于公共接口,而不是一个层依赖于另一个层。依赖注入模式是依赖导致的常见实现方式。通过依赖注入组件,一层可以通过使用公共抽象接口来注入到其它层的组件中。

基于消息 基于消息的接口可以用于提供层之间的交互,而不是直接和组件进行交互。有多种消息解决方案,比如web服务和支持跨机器和进程边界的微软消息队列。然而,你也可以组合抽象接口和用于定义要交互的数据结构的公共消息类型。

 

基于消息最主要的区别是层之间的交互使用公共结构,它封装了交互的细节。这个结构可以定义操作、数据架构、错误契约、安全信息以及其它和层之间通讯相关的细节。

考虑使用抽象接口:

如果你希望使用接口具体实例来实现不同行为的能力。

如果你希望使用mock对象来增加应用程序的可测试性。


考虑使用公共设计类型:

如果你在为层中的接口实现设计模式。许多设计模式基于抽象接口,然而,一些基于具体类。

如果你希望一种快捷而简单的方式实现层接口。比如表数据入口模式。


考虑使用依赖导致:

如果你希望提供最大的可测试性,这个方式允许你诸如具体的测试类到设计中的不用层中。


考虑使用基于消息的:

如果你要实现一个web应用程序并且在表现层和业务层中定义接口。

如果你有一个应用程序层需要支持多个客户端类型。

如果你希望提供跨物理和进程边界交互。

如果你希望以公共接口进行交互。

如果你要和一个无状态的接口进行交互,状态信息使用消息进行携带。

 

对于web应用程序表现层和业务逻辑层之间的交互推荐使用基于消息的接口。一般通过使用Windows Communication Foundation (WCF)或提供公共消息类型的抽象接口来实现。在表现层和业务层之间使用基于消息的接口的原因是因为web应用程序的业务层不维护调用间的状态。换句话说,每一次表现层和业务层中的调用都代表一个新的上下文。通过使用基于消息的接口我们可以随请求传递上下文信息并且为表现层中的异常和错误处理提供一种公共的模型。

检查点

在结束本步骤之前,你应该可以回答如下问题:

你是否需要使用抽象接口提供测试性?

你的接口是否需要提供跨进程和机器边界交互?

你的业务层是否需要支持多个客户端类型?

你是否会使用基于消息的接口在web应用程序的表现层和业务层之间进行交互?

第三步——选择部署策略

对于大多数解决方案中都可以找到几种常见的模式,它们表示了应用程序的部署结构。如果要为我们的应用程序决定最佳部署解决方案,首先需要了解常见的模式。在理解了不同的模式之后,然后你就可以考虑场景、需求以及安全约束来选择某一种或多种模式。

客户端-服务器

这个模式表示具有两个主要组件:客户端和服务器的基本结构。对于这种情况,客户端和服务器可以在相同机器上也可以跨越两个机器。如下示例演示了常见的web应用程序场景,客户端和web服务器进行交互。

N

这个模式表示应用程序组件跨域一个或多个服务器的结构。下图表示了2层部署,所有应用程序代码位于客户端而数据库位于分离的服务器中。

3

3层设计中,客户端和部署在独立服务器的应用程序软件进行交互,和数据库进行交互的应用程序服务器也是单独的服务器。这是许多web应用程序和web服务常见的模式。

 4

对于这种情况,web服务器从物理上和应用服务器分离。这么做通常处于安全的目的,web服务器部署在隔离区域(DMZ)中而应用程序服务器位于不同的子网。我们一般会在客户端和web层以及web层和应用层或业务逻辑层之间假设2道防火墙。


考虑客户端服务器或2层:

*                               如果我们在开发一个需要访问应用服务器的客户端。

*                               如果我们在开发一个访问外部数据库的独立客户端。


考虑3层:

*                               如果我们在开发基于局域网的应用程序,所有服务器都在一个私有网络中。

*                               如果我们在开发基于Internet的应用程序,并且安全需求不约束在公共的web/应用服务器中实现业务逻辑。


考虑4层:

*                               如果安全需求规定业务逻辑不能在DMZ中部署业务逻辑。

*                               如果我们的应用程序使用服务器上的资源很厉害并且我们希望把功能分离到另一个服务器。


在大多数情况下推荐让所有的应用程序代码放在相同服务器。任何需要跨物理边界的需求都会影响性能因为数据需要在这些边界进行序列化。然而,有一些情况下我们需要跨服务器分离功能。此外,根据服务器的位置我们可以选择性能最优化的通讯协议。

检查点

在结束本步骤之前,你应该可以回答如下问题:

*                               安全需求是否规定业务逻辑不能部署在公开的DMZ中?

*                               我们是否在开发独立的只需要访问数据库的客户端?

*                               我们是否在开发web应用程序或web服务?

*                               我们是否在开发有多个客户端的应用程序?

 

第四步——选择通讯协议

说到设计中用于跨逻辑层或物理层进行通讯的物理协议,通讯协议的选择对我们应用程序的性能起巨大作用。选择通讯协议的一个主要的地方就是使用服务和数据库进行交互。

*                               Windows Communication Foundation (WCF) 直接支持四种协议:

*                                                       HTTP 用于在Internet上公开的服务。

*                                                       TCP 用于在网络内部署的服务。

*                                                       NamedPipes 用于服务和其使用者部署在同一服务器的服务。

*                                                       MessageQueue 用于通过微软消息队列实现进行访问的服务。

*                               Database支持和WCF一样的许多协议。

*                                                       HTTP 用于在Internet上公开的数据库。

*                                                       TCP 用于在网络内部署的数据库。

*                                                       NamedPipes用于数据库和其使用者部署在同一服务器的数据库。

 

考虑使用HTTP

如果我们需要为服务或数据库在Internet上提供公共访问。

如果我们需要利用Web服务扩展(WS-*)带来的对于HTTP协议安全的支持。


考虑使用TCP

*                               如果通讯局限于同一网络中的服务器。

*                               如果使用虚拟专用网络(VPN)来访问服务或数据库。

如果不需要使用Web服务扩展来提供安全。



考虑使用命名管道:

*                               如果服务或数据库和其使用者部署在同一服务器。

*                               如果不需要使用Web服务扩展来提供安全。


考虑使用消息队列:

如果服务使用微软消息队列通过消息总线设计来访问。

检查点

在结束本步骤之前,你应该可以回答如下问题:

*                               服务或数据穿越的是哪种类型的边界?

*                               我们是否使用微软消息队列使用消息总线设计?

*                               我们是否需要使用web服务扩展?

 

其它资源

*                               更多分层应用程序的信息 http://msdn.microsoft.com/en-us/library/ms978678.aspx

*                               更多三层服务应用程序的信息http://msdn.microsoft.com/en-us/library/ms978689.aspx

*                               更多分层分布式的信息http://msdn.microsoft.com/en-us/library/ms978701.aspx

*                               更多三层分布式的信息http://msdn.microsoft.com/en-us/library/ms978694.aspx

*                               更多服务模式的信息http://msdn.microsoft.com/en-us/library/ms998508.aspx

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多