术语表
逻辑:Logical
物理:Physical
构架:Architecture
框架:Framework
表现层:Presentation
用户界面:User Interface
业务逻辑:Business Logic
数据访问:Data Access
数据和存储管理:Data and Storage Management
图形用户接口:Graphical User Interface
胖客户端:Richer Client-side
智能客户端:Smart Client
应用程序:Application
服务器端控件:Server-side control
事件驱动:Event-driven
可重用组件:Reusable component
用户控件:User Control
元数据驱动:Metadata-driven
对象关系映射:object-relational mapping
本文将探讨5层逻辑构架的程序设计。这种类型的构架一旦建立成功,就可以配置成适用于各种各样的物理构架,从而为Windows窗体、Web窗体、Web服务提供最佳的服务。
这种构架由 图1所示的五部分构成:
应该时刻记得使用 N层逻辑构架的好处是将功能清楚地划分成角色或者组,以便提升程序的可读性和可维护性。下面我们来仔细的定义一下这几个层的功能。
表现层
首先,可能你很想知道为什么我会将表现层从用户界面层分离出去。的确,从Windows应用程序的角度来看,表现层和用户界面是一回事:它们都是些可以与用户进行交互的图形用户接口。
然而从Web程序的角度来看(或者从一个基于终端的程序来看),区别应该是很明显的。典型地,浏览器仅仅是将内容显示给用户,并采集用户的输入信息。在这种情况下,所有实际的交互逻辑――产生内容输出或者分析用户输入的代码――都在Web服务器端运行(或者主框架中),而不是在客户端机器上。
当然,在现实环境中,浏览器可能会运行JavaScript脚本或者是胖客户端代码。但是,这些代码都是不可信任的(译注:可以联想下客户端表单验证)。这些代码必须被视为与你运行于服务器端的代码进行交互的独立代码。所以,即使有代码运行于浏览器中,你的应用程序的用户界面代码仍然是运行于Web服务器上的。
应该了解到,当前讨论的逻辑构架必须同时支持智能客户端和基于Web的客户端(甚至是更多其他受到一些限制的客户端,比如说手机或者其他移动设备),在很多情况下,意识到表现层必须在物理上与用户界面逻辑分离是很重要的。为了能实现这种分离,设计程序时就很有必要围绕着这个主题来进行。
注意:表现层的技术类型是非常多的,并且每个都会伴随一个新的、相对不兼容的、我们必须使用的技术。实际上,不可能创建一个完全从表现层抽象出来的程序框架。正因为如此,构架和框架将仅仅是支持不同的表现层,而不是自动创建它们。所以,我们将把注意力主要集中在简化构架中的其他层,这些层中的技术相对稳定一些。
用户界面
之前我已经讲述了表现层和用户界面之间的区别,那么后者的目的现在应该已经很清楚了。这一层的程序逻辑决定了用户看到什么、导航的路径以及如何处理用户的输入。在一个Windows应用程序中,这些是置于表单之后的代码。实际上,这些也是在Web应用程序中置于表单之后的代码,但是,在这种情况下,用户界面层也包含服务器端控件中的代码。从逻辑上来讲,这属于同一层的不同部分。
在很多应用程序中,用户界面代码非常复杂。首先,它必须对于用户非流线型的请求做出响应(很难控制用户是否点击控件,进入或离开一个表单或者页面)。用户界面代码还必须在逻辑上与业务层进行交互,以验证用户输入、执行任何需要进行的任务,或者做任何与业务层相关的功能。
基本上,用户界面层的目的是编写接受用户输入然后提供输入信息到业务逻辑层的代码,这些用户输入信息在业务逻辑层中被验证、处理或者进行其他任何的操作。用户界面层必须对用户的请求做出响应,显示与业务逻辑层交互所产生的结果。用户输入的数值正确么?如果不正确,那么是哪里除了问题?等等。
在.Net中,用户界面几乎总是事件驱动的。Windows窗体总是在用户输入或者点击表单项时做出相应,Web窗体总是在一次用户动作的客户端/服务器往返中对事件做出响应。尽管Windows窗体和Web窗体技术大量使用了面向对象设计,典型的写在用户界面中的代码却不是面向对象的,而是事件驱动的。
这就是说,创建一个支持某种类型用户界面的框架和可重用的组件是非常有价值的。当创建一个Window窗体的用户界面,开发者可以利用继承和其他面向对象技术去简化窗体的创建。当创建一个Web窗体用户界面,开发者可以使用ASP.NET用户控件和自定义服务器控件去提供可重用组件以便简化页面的开发。
业务逻辑
业务逻辑层包含了全部的业务规则,数据验证、操作、处理以及应用程序安全性。微软对于业务逻辑层的定义是这样的:业务逻辑层是企业商业运作方式的操作集合,这些操作包括验证用户输入、登录校验、数据库查询、商业条款、转换算法等。
注意:再说一次,当你进行一个用户校验时,如果校验代码运行于浏览器或者是外部的客户端上,那么这些代码是不可信的。你必须将这些程序放在你所能控制的业务逻辑层中去实现,因为只有这样才算是真正的验证。
业务逻辑必须放置于与用户界面代码完全分离的独立层之中。有时,你可能想要复制一些业务逻辑层的代码到用户界面层中去,以便提供一个更好的用户体验,但是,业务逻辑层必须实现所有的业务规则,因为它才是集中管理和维护的关键所在。
我相信,如果你想获得一个更好的可维护性和可重用性,这种业务逻辑与用户界面层功能的分离是绝对关键的。这是因为任何写入用户界面层的业务逻辑都存在于某一特殊的用户界面代码中,这样它将不能适用于日后创建的新的用户界面。
如何写入Windows窗体用户界面中的业务逻辑代码对于Web窗体程序或者Web服务来说,都是毫无意义的,并且必须重写一遍。这毫无疑问导致了如同噩梦般的重复性的代码。将这两层分离可以通过明确定义程序模型、面向对象设计和变成 的技术来实现。
在一个典型的应用程序中,意识到将会以很多种方式使用业务逻辑是很重要的。大多数应用程序都会做一些用户交互,比如说一些提供用户输入或者提供显示内容的表单。很多应用程序也会有一些完全不需要交互的过程,比如开发表、分发财产清单,或者计算税率等。
在理想情况下,当用户直接对应用程序发送数据时,业务逻辑层将有着非常丰富的交互功能。举例来说,当一个用户正在访问一个订单,他期望在他输入时,诸如税率的计算、订单的价格总计等正确的数据会即时的显示出来。这就暗示着从物理角度考虑,业务逻辑层可能会被部署在客户端工作站上,或者Web服务器上,以便满足高级的用户交互需要。
从另一方面来说,为了支持非交互的过程,业务逻辑层经常需要被部署在一个尽可能靠近数据库服务器的应用程序服务器上。举例来说,保险费用的计算需要使用大量的数据库查询并伴随少量的复杂业务规则处理。这就是发生在服务器上,用户显示器背后的事情。
幸运的是,在多个物理层上部署一个逻辑层是可能的。做这项工作非常需要一些长远的计划和技术设计,然而,最终的结果是一个分别部署在客户端工作站(或者Web服务器)和应用程序服务器上的独立业务逻辑层。这就使得应用程序具备在用户直接与应用程序对话时,提供高级用户交互体验和高效的处理非交互性操作的能力。
数据访问
数据访问层的代码与数据管理层进行交互,以便获取、插入、更新、移动数据。数据访问层并不实际的管理或者存储数据。它仅仅是提供一个业务逻辑层与数据库之间的接口。
就像表现层与用户界面层分离的原因一样,数据访问层拥有着自己的逻辑。在一些情况下,数据访问层将放置于某台机器上,这台机器与运行用户界面 或者(和) 业务逻辑层的机器并非同一台机器。在另外一些情况下,数据访问层可能与用户界面 或者(和) 业务逻辑层放置在同一台机器上,以便提高性能和容错性。
注意:把数据访问层和业务逻辑层放在同一台机器上会提高容错性听上去可能会很奇怪,但是请考虑一下Web窗体,在这种情况下每个Web服务器对于其他服务器来说都是一样的。将数据访问层代码放置于所有Web服务器上产生数据访问层、业务逻辑层、用户界面层的自动冗余。
添加一个仅仅用于数据访问的物理层将会使容错性很难实施,因为它增大了需要实施数据冗余的层数。还有一个负面效应,添加更多的物理层数也会降低单用户情况下的运行速度,所以这并不是无关紧要的事情。
逻辑上定义一个独立的数据访问层使得业务逻辑与数据库(或者其他任何数据源)之间的任何交互都分离了。这种分离对于以后是否将数据访问代码运行于现在的系统或是其他新的系统,提供了很大的灵活性。它同样可以使得在不改变应用程序的情况下更换一个数据源变得很容易。这个是很重要的,因为在一些情况下它使得可以轻易的从一种数据库转换到另一种。
说这种分离是非常有用的还有另外一个原因:微软习惯每三年就改变一次数据访问技术,这就意味着需要重新书写数据访问代码以跟进潮流(回想一下DAO, RDO, ADO 1.0, ADO2.0, 以及现在的ADO.NET)。通过将数据访问层代码分离到一个独立的层中,这种变化所造成的影响被限制在应用程序的一个很小的范围以内。
典型地,数据访问机制以一系列服务的形式被实现,每个服务都是单独的一个由业务逻辑层调用的功能,以便进行数据的获取、插入、更新。尽管这些服务通常都是以对象的形式被创建,意识到一个高效的数据访问层应该是非常流线型的是很重要的。对于一个关系数据库的访问采用尽可能多的面向对象的设计常常会导致更高的程序复杂性和更低的执行效率。
我认为实现数据访问层的最好方法是写一系列的方法,但是将这些方法封装在一个对象中以便使它们有更好的组织。
注意:如果你正在使用一个面向对象的数据库而非一个关系数据库,那么当然,数据访问代码应该是非常的面向对象的。然而,只有很少的人拥有着这样的机会,因为几乎所有的数据都是存储在关系数据库当中的。
在一些情况下,数据访问层是可以是非常简单的,仅仅由使用ADO.NET来直接获取或者存储数据的一系列方法组成。在另一些情况下,数据访问层要复杂得多,提供更加抽象或甚至是元数据驱动的方式去获取数据的方法。在这些情况下,数据访问层会包含大量的复杂代码以便提供这种更加抽象的数据访问模式。
数据访问层经常扮演的另一个角色是提供面向对象的业务逻辑与存储在数据库中的数据之间的映射。一个好的面向对象模型几乎从来不与一个好的关系数据库模型相同。对象经常包含来自于多个表甚至多个数据库的数据。将数据从一个关系模型的表中提取出来,并提供给面向对象模型的过程,被称作 对象关系映射(ORM)。
数据存储和管理
最后,还剩下数据存储和管理层。类似于SQL Server 和Oracle这样的数据库服务器通常担当这样的角色,但是逐渐地,其他的应用程序可能通过类似于Web服务这样的技术提供同样的功能。
这一层的核心是它在物理上提供了对数据的创建、获取、更新和删除。这与数据访问层是不同的,数据访问层仅仅是提出对数据进行创建、获取、更新和删除的请求。数据管理层实际地在数据库中和磁盘文件上实施这些操作。
业务逻辑层(通过数据访问层)调用数据管理层,但是这一层常常包含额外的逻辑去验证数据的正确性以及它们与其他数据之间的关系。很多时候,这是来自于数据库端的真正的关系数据模型;另一些时候,这是来自于外部应用程序的业务逻辑规则。这就意味着一个典型的数据管理层应该也包括在业务逻辑层实现的业务规则。这种情况下,复制是不可避免的,因为关系数据库被设计成为强制性的实施关系完整性;而这不过是业务逻辑的另一种形式而已。
总的来说,不管你是在SQL Server中使用存储过程,还是使用Web服务调用另一个应用程序,典型地,数据存储和管理是由一系列的可以在需要时被调用的服务和存储过程来处理的。就像数据访问层一样,意识到数据存储和管理层的设计是非常流线型(译注:流线型是相对于面向对象来说的)的是很重要的。
表1. 五层逻辑层所担任的角色
层 |
角色 |
表现层 |
提供内容显示和收集用户输入。 |
用户界面 |
作为用户和业务逻辑之间的中介,获取用户输入并提供给业务逻辑层,并且将结果返回给用户 |
业务逻辑 |
提供所有的业务规则、数据校验、操作、处理,以及应用程序安全 |
数据访问 |
作为业务逻辑层与数据管理层之间的中介。同样封装了所有需要用到的数据访问技术(比如ADO.NET) |
数据存储和管理 |
物理上创建、获取、更新、删除数据,并且进行对数据的维护。 |