0%

官方系统架构设计师教程-ch16-层次式架构设计

层次式架构设计

体系结构设计

1968年,在Garmish召开的国际软件工程会议上,人们迫切地感到了软件危机给计算机软件产业的发展带来的巨大阻力。软件危机的两个比较大的问题是:软件的规模越来越大,软件复杂度越来越高。伴随着这两个问题的日益突出,整个软件系统结构的设计与规格说明便显得比算法选择和计算问题的数据结构更为重要。因此,代码级别的软件复用已经远远不能满足大型软件开发的需求,由此便引入了“软件体系结构”这一概念。

软件体系结构可定义为:软件体系结构为软件系统提供了结构、行为和属性的高级抽象,由构成系统的元素描述、这些元素的相互作用、指导元素集成的模式以及这些模式的约束组成。软件体系结构不仅指定了系统的组织结构和拓扑结构,并且显示了系统需求和构成系统的元素之间的对应关系,提供了一些设计决策的基本原理,是构建于软件系统之上的系统级复用。

软件体系结构贯穿于软件研发的整个生命周期内,具有重要的影响。这主要从以下三个方面来进行考察。

(1)利益相关人员之间的交流。软件体系结构是一种常见的系统抽象,代码级别的系统抽象仅仅可以成为程序员的交流工具,而包括程序员在内的绝大多数系统的利益相关人员都借助软件体系结构来作为相互沟通的基础。

(2)系统设计的前期决策。软件体系结构是我们所开发的软件系统最早期设计决策的体现,而这些早期决策对软件系统的后续开发、部署和维护具有相当重要的影响。这也是能够对系统进行分析的最早时间点。

(3)可传递的系统级抽象。软件体系结构是关于系统构造以及系统各个元素工作机制的相对较小、却又能够突出反映问题的模型。由于软件系统具有的一些共通特性,这种模型可以在多个系统之间传递,特别是可以应用到具有相似质量属性和功能需求的系统中,并能够促进大规模软件的系统级复用。

分层设计是一种最常见的架构设计方法,能有效地使设计简化,使设计的系统机构清晰,便于提高复用能力和产品维护能力。

表现层框架设计

使用MVC模式设计表现层

MVC是一种目前广泛流行的软件设计模式。近年来,随着J2EE(Java 2Enterprise Edition)的成熟,MVC成为了J2EE平台上推荐的一种设计模式。MVC强制性地把一个应用的输入、处理、输出流程按照视图、控制、模型的方式进行分离,形成了控制器、模型、视图三个核心模块。

(1)控制器(Controller):接受用户的输入并调用模型和视图去完成用户的需求。该部分是用户界面与Model的接口。一方面它解释来自于视图的输入,将其解释成为系统能够理解的对象,同时它也识别用户动作,并将其解释为对模型特定方法的调用;另一方面,它处理来自于模型的事件和模型逻辑执行的结果,调用适当的视图为用户提供反馈。

(2)模型(Model):应用程序的主体部分。模型表示业务数据和业务逻辑。一个模型能为多个视图提供数据。由于同一个模型可以被多个视图重用,所以提高了应用的可重用性。

(3)视图(View):用户看到并与之交互的界面。视图向用户显示相关的数据,并能接收用户输入的数据,但是它并不进行任何实际的业务处理。视图可以向模型查询业务状态,但不能改变模型。视图还能接受模型发出的数据更新事件,从而对用户界面进行同步更新。

三者的协作关系如图16-1所示。

图16-1 MVC设计模式找不到图片(Image not found)

从图16-1中可以看到,首先,控制器接收用户的请求,并决定应该调用哪个模型来处理;然后,模型根据用户请求进行相应的业务逻辑处理,并返回数据;最后,控制器调用相应的视图来格式化模型返回的数据,并通过视图呈现给用户。

使用MVC模式来设计表现层,可以有以下的优点。

1)允许多种用户界面的扩展。在MVC模式中,视图与模型没有必然的联系,都是通过控制器发生关系,这样如果要增加新类型的用户界面,只需要改动相应的视图和控制器即可,而模型则无需发生改动。

(2)易于维护。控制器和视图可以随着模型的扩展而进行相应的扩展,只要保持一种公共的接口,控制器和视图的旧版本也可以继续使用。

(3)功能强大的用户界面。用户界面与模型方法调用组合起来,使程序的使用更清晰,可将友好的界面发布给用户。

MVC是构建应用框架的一个较好的设计模式,可以将业务处理与显示分离,将应用分为控制器、模型和视图,增加了应用的可拓展性、强壮性及灵活性。基于MVC的优点,目前比较先进的Web应用框架都是基于MVC设计模式的。

使用XML设计表现层,统一Web Form与Windows Form的外观

XML(可扩展标记语言)与HTML类似,是一种标记语言。与主要用于控制数据的显示和外观的HTML标记不同,XML标记用于定义数据本身的结构和数据类型。XML已被公认为是优秀的数据描述语言,并且成为了业内广泛采用的数据描述标准。

由于XML的设计目标是描述数据并集中于数据的内容,所以虽然XML和HTML类似,但是业内很少采用XML作为表现层技术,表现层技术仍然是HTML唱主角。但是,由于Web应用程序对特定浏览器的局限以及性能问题,基于窗体表现形式的胖客户端应用程序又开始有了卷土重来的趋势。这两种应用程序各有优势,在未来很长一段时间这两种技术架构都会并存。因此,许多开发厂商在开发新产品时提出了既要支持胖客户端的表现形式,又要支持Web的表现形式。于是,有人提出将GUI用一个标准的形式描述,对于不同的表现形式,提供特定形式的转换器,根据GUI的描述转换成相应的表现形式。这就要求描述语言有非常好的通用性和扩展性,XML恰恰是这种描述语言理想的载体。

对于大多数应用系统,GUI主要是由GUI控件组成。控件可以看成是一个数据对象,其包含位置信息、类型和绑定的事件等。这些信息在XML中都可以作为数据结点保存下来,每一个控件都可以被描述成一个XML结点,而控件的那些相关属性都可以描述成这个XML结点的Attribute。由于XML本身就是一种树型结构描述语言,所以可以很好地支持控件之间的层次结构。同时,XML标记由架构或文档的作者定义,并且是无限制的,所以架构开发人员可以随意约定控件的属性,例如可以约定type=”button”是一个按钮,type=”panel”是一个控件容器,type=”Constraint”是位置等。这样,整个GUI就可以完整而且简单地通过XML来描述。例如:

图片详情找不到图片(Image not found)

这么一段XML很清晰地表示一个控件容器位置是(16,22,78,200),包含了一个不可视按钮。用上述的XML形式将GUI按照数据描述的形式保存下来代替原先特有的表现形式所需要的GUI描述载体。然后,对于特定的表现技术,实现不同的解析器解析XML配置文件。根据XML中的标签,按照特有的表现技术实例化的GUI控件实例对象。例如,解析器遇到button,JFC解析器会给予JLabel对象,XSLT解析器会给予<button id=… >这样一个HTML字符串,再调用特定表现技术的API将实例化出来的组件对象添加到GUI上显示。

从设计模式的角度来说,整个XML表现层解析的机制是一种策略模式。在调用显示GUI时,不是直接的调用特定的表现技术的API,而是装载GUI对应的XML配置文件,然后根据特定的表现技术的解析器解析XML,得到GUI视图实例对象。这样,对于GUI开发人员来说,GUI视图只需要维护一套XML文件即可。

表现层中UIP设计思想

应用程序通常要用代码来管理用户界面,例如一个窗体可以决定下一个要呈现给用户的窗体。开发人员可以把这些代码写在UI代码中间,但是会使得代码复杂,不易复用、维护和扩展。另一方面,应用程序要运行在其他的平台也变得相当困难,因为它进行控制的逻辑和状态都不能被复用。

在大多数情况下,应用程序需要维护一个状态,如状态存储在窗体中,代码需要访问这个窗体以重新恢复状态。这样做会比较困难并且代码也会变得不雅,同时也会对用户接口的重用性和可扩展性产生影响。

用户应用系统的时候,他可能会先启动一个任务,离开一段时间后再回来继续。如果在中间用户关闭了应用程序,它将失去当前的状态,要想继续任务的话必须一切从头开始。因此设计程序的时候,必须分开来考虑工作流、导航、与商业服务的交互等各个组成部分,以获取数据并呈现给用户。

UIP (User Interface Process Application Block) 是微软社区开发的众多Application Block中的其中之一,它是开源的。UIP提供了一个扩展的框架,用于简化用户界面与商业逻辑代码的分离的方法,可以用它来写复杂的用户界面导航和工作流处理,并且它能够复用在不同的场景、并可以随着应用的增加而进行扩展。

使用UIP框架的应用程序把表现层分为了以下几层。

● User Interface Components:这个组件就是原来的表现层,用户看到的和进行交互都是这个组件,它负责获取用户的数据并且返回结果。

● User Interface Process Components:这个组件用于协调用户界面的各部分,使其配合后台的活动,例如导航和工作流控制,以及状态和视图的管理。用户看不到这一组件,但是这些组件为User Interface Components提供了重要的支持功能。

图16-2展示了这两层在基于.Net的分布式应用程序中的位置。

图16-2 UI Components和UIP Components找不到图片(Image not found)

UIP的组件主要负责的功能是:管理经过User Interface Components的信息流;管理UIP中各个事件之间的事务;修改用户过程的流程以响应异常;将概念上的用户交互流程从实现或者涉及的设备上分离出来;保持内部的事务关联状态,通常是持有一个或者多个的与用户交互的事务实体。因此,这些组件也能进行从UI组件收集数据以执行服务器的成组的升级或是跟踪UIP中的任务过程的管理。

表现层动态生成设计思想

基于XML的界面管理技术可实现灵活的界面配置、界面动态生成和界面定制。其思路是用XML生成配置文件及界面所需的元数据,按不同需求生成界面元素及软件界面。

基于XML界面管理技术,包括界面配置、界面动态生成和界面定制三部分,如图16-3所示。

图16-3 基于XML的界面管理技术框图找不到图片(Image not found)

界面配置是对用户界面的静态定义,通过读取配置文件的初始值对界面配置。由界面配置对软件功能进行裁剪、重组和扩充,以实现特殊需求。

界面定制是对用户界面的动态修改过程,在软件运行过程中,用户可按需求和使用习惯,对界面元素(如菜单、工具栏、键盘命令)的属性(如文字、图标、大小和位置等)进行修改。软件运行结束,界面定制的结果被保存。

系统通过DOM API读取XML配置文件的表示层信息(初始界面大小、位置等),通过数据存取类读取数据库中的数据层信息,运行时由界面元素动态生成界面。界面配置和定制模块在软件运行前后修改配置文件、更改界面内容。

基于XML的界面管理技术实现的管理信息系统实现了用户界面描述信息与功能实现代码的分离,可针对不同用户需求进行界面配置和定制,能适应一定程度内的数据库结构改动。只需对XML文件稍加修改,即可实现系统的移植。

中间层架构设计

业务逻辑层组件设计

业务逻辑组件分为接口和实现类两个部分。

接口用于定义业务逻辑组件,定义业务逻辑组件必须实现的方法是整个系统运行的核心。通常按模块来设计业务逻辑组件,每个模块设计一个业务逻辑组件,并且每个业务逻辑组件以多个DAO组件作为基础,从而实现对外提供系统的业务逻辑服务。增加业务逻辑组件的接口,是为了提供更好的解耦,控制器无须与具体的业务逻辑组件耦合,而是面向接口编程。

业务逻辑组件的实现类

业务逻辑组件以DAO组件为基础,必须接收Spring容器注入的DAO组件,因此必须为业务逻辑组件的实现类提供对应的setter方法。业务逻辑组件的实现类将DAO组件接口实例作为属性(面向接口编程),而对于复杂的业务逻辑,可能需要访问多个对象的数据,那么只需在这个方法里调用多个DAO接口,将具体实现委派给DAO完成。

业务逻辑组件的配置

由于业务逻辑组件的DAO组件从未被初始化过,那么业务方法如何完成?DAO组件初始化是由Spring的反向控制(Inverse of Control, IoC)或者称为依赖注入(Dependency Injection, DI)机制完成的。为此,还需要在applicationContext.xml里面配置FacadeManager组件。

定义FacadeManager组件时必须为其配置所需要的DAO组件,配置信息表示BaseManager继承刚才配置的事务代理模板。并且由容器给BaseManager注入dao的组件,即BaseDAOHibernate。而target则是TransactionProxy FactoryBean需要指定的属性,TransactionProxyFactoryBean负责为某个bean实例生成代理,代理必须有个目标,target属性则用于指定目标。

当然,也可以不使用事务代理模板及嵌套bean,而是为组件指定单独的事务代理属性,让事务代理的目标引用容器中已经存在的bean。

applicationContext.xml文件的源代码配置了应用的数据源和SessionFactory等bean,而业务逻辑组件也被部署在该文件中。

在配置文件中,采用继承业务逻辑组件的事务代理,将原有的业务逻辑组件作为嵌套bean配置,避免了直接调用没有事务特性的业务逻辑组件。

系统实现了所有的后台业务逻辑,并且向外提供了统一的Facade接口,前台Web层仅仅依赖这个Facade接口。这样,Web层与后台业务层的耦合已经非常松散,系统可以在不同的Web框架中方便切换,即使将整个Web层替换掉也非常容易。

业务逻辑层工作流设计

工作流管理联盟(Workflow Management Coalition)将工作流定义为:业务流程的全部或部分自动化,在此过程中,文档、信息或任务按照一定的过程规则流转,实现组织成员间的协调工作以达到业务的整体目标。

工作流管理一直是企业界和学术界关注的热点领域。1993年,国际上专门成立了工作流管理联盟(Workflow Management Coalition, WFMC),以便对工作流实现标准化管理。它是一种反映业务流程的计算机化的模型,是为了在先进计算机环境支持下实现经营过程集成与经营过程自动化而建立的可由工作流管理系统执行的业务模型。它解决的主要问题是:使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者是促使此目标的实现。

图16-4 工作流参考模型找不到图片(Image not found)

(1)interface 1:过程定义导入/导出接口。这个接口的特点是:转换格式和API调用,从而支持过程定义信息间的互相转换。这个接口也支持已完成的过程定义或过程定义的一部分之间的互相转换。早期标准是WPDL,后来发展为XPDL。

(2)interface 2:客户端应用程序接口。通过这个接口工作流机可以与任务表处理器交互,代表用户资源来组织任务。然后由任务表处理器负责,从任务表中选择、推进任务项。由任务表处理器或者终端用户来控制应用工具的活动。

(3)interface 3:应用程序调用接口。允许工作流机直接激活一个应用工具,来执行一个活动。典型的是调用以后台服务为主的应用程序,没有用户接口。当执行活动要用到的工具,需要与终端用户交互,通常是使用客户端应用程序接口来调用那个工具,这样可以为用户安排任务时间表提供更多的灵活性。

(4)interface 4:工作流机协作接口。其目标是定义相关标准,以使不同开发商的工作流系统产品相互间能够进行无缝的任务项传递。WFMC定义了4个协同工作模型,包含多种协同工作能力级别。

(5)interface 5:管理和监视接口。提供的功能包括用户管理、角色管理、审查管理、资源控制、过程管理和过程状态处理器等。

用工作流的思想组织业务逻辑,优点是:将应用逻辑与过程逻辑分离,在不修改具体功能的情况下,通过修改过程模型改变系统功能,完成对生产经营部分过程或全过程的集成管理,可有效地把人、信息和应用工具合理地组织在一起,发挥系统的最大效能。

业务逻辑层实体设计

业务逻辑层实体具有以下特点:业务逻辑层实体提供对业务数据及相关功能(在某些设计中)的状态编程访问。业务逻辑层实体可以使用具有复杂架构的数据来构建,这种数据通常来自数据库中的多个相关表。业务逻辑层实体数据可以作为业务过程的部分I/O参数传递。业务逻辑层实体可以是可序列化的,以保持它们的当前状态。例如,应用程序可能需要在本地磁盘、桌面数据库(如果应用程序脱机工作)或消息队列消息中存储实体数据。业务逻辑层实体不直接访问数据库,全部数据库访问都是由相关联的数据访问逻辑组件提供的。业务逻辑层实体不启动任何类型的事务处理,事务处理由使用业务逻辑层实体的应用程序或业务过程来启动。

在应用程序中表示业务逻辑层实体的方法有很多(从以数据为中心的模型到更加面向对象的表示法),如XML、通用DataSet、有类型的DataSet等。

以下示例显示了如何将一个简单的业务逻辑层实体表示为XML。该业务逻辑层实体包含一个产品。

图片详情找不到图片(Image not found)

将业务逻辑层实体表示为XML的优点如下。

(1)标准支持。XML是World Wide Web Consortium (W3C)的标准数据表示格式。

(2)灵活性。XML能够表示信息的层次结构和集合。

(3)互操作性。在所有平台上,XML都是与外部各方及贸易伙伴交换信息的理想选择。

如果XML数据将由ASP.NET应用程序或Windows窗体应用程序使用,则还可以把这些XML数据装载到一个DataSet中,以利用DataSet提供的数据绑定支持。

将业务逻辑层实体表示为通用DataSet。通用DataSet是DataSet类的实例,它是在ADO.NET的System.Data命名空间中定义的。DataSet对象包含一个或多个DataTable对象,用于表示数据访问逻辑组件从数据库检索到的信息。

图16-5所示为用于Product业务逻辑层实体的通用DataSet对象。该DataSet对象具有一个DataTable,用于保存产品信息。该DataTable具有一个UniqueConstraint对象,用于将ProductID列标记为主键。DataTable和UniqueConstraint对象是在数据访问逻辑组件中创建该DataSet时创建的。

图16-5 用于Product业务逻辑层实体的通用DataSet找不到图片(Image not found)

图16-6所示为用于Order业务逻辑层实体的通用DataSet对象。此DataSet对象具有两个DataTable对象,分别保存订单信息和订单详细信息。每个DataTable具有一个对应的UniqueConstraint对象,用于标识表中的主键。此外,该DataSet还有一个Relation对象,用于将订单详细信息与订单相关联。

图16-6 用于Order业务逻辑层实体的通用DataSet找不到图片(Image not found)

将业务逻辑层实体表示为通用DataSet的优点如下。

(1)灵活性。DataSet可以包含数据的集合,能够表示复杂的数据关系。

(2)序列化。在层间传递时,DataSet本身支持序列化。

(3)数据绑定。可以把DataSet绑定到ASP.NET应用程序和Windows窗体应用程序的任意用户界面控件。

(4)排序与过滤。可以使用DataView对象排序和过滤DataSet。应用程序可以为同一个DataSet创建多个DataView对象,以便用不同方式查看数据。

(5)与XML的互换性。可以用XML格式读写DataSet。

(6)开放式并发。在更新数据时,可以配合使用数据适配器与DataSet方便地执行开放式并发检查。

(7)可扩展性。如果修改了数据库架构,则适当情况下数据访问逻辑组件中的方法可以创建包含修改后的DataTable和DataRelation对象的DataSet。

将业务逻辑层实体表示为有类型的DataSet。有类型的DataSet是包含具有严格类型的方法、属性和类型定义以公开DataSet中的数据和元数据的类。

将业务逻辑层实体表示为有类型的DataSet的优点如下。

(1)代码易读。要访问有类型的DataSet中的表和列,可以使用有类型的方法和属性。

(2)有类型的方法和属性的提供使得使用有类型的DataSet比使用通用DataSet更方便。使用有类型的DataSet时,IntelliSense将可用。

(3)编译时类型检查,无效的表名称和列名称将在编译时而不是在运行时检测。

业务逻辑层框架

业务框架位于系统架构的中间层,是实现系统功能的核心组件。采用容器的形式,便于系统功能的开发、代码重用和管理。图16-7便是在吸收了SOA思想之后的一个三层体系结构的简图。

图16-7 业务框架在整个系统架构中的位置找不到图片(Image not found)

从图16-7中可以看到,业务层采用业务容器(Business Container)的方式存在于整个系统当中,采用此方式可以大大降低业务层和相邻各层的耦合,表示层代码只需要将业务参数传递给业务容器,便不需要业务层多余的干预。如此一来,可以有效地防止业务层代码渗透到表示层。

在业务容器中,业务逻辑是按照Domain Model—Service—Control思想来实现的。

(1)Domain Model是领域层业务对象,它仅仅包含业务相关的属性。

(2)Service是业务过程实现的组成部分,是应用程序的不同功能单元,通过在这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。这种具有中立的接口定义(没有强制绑定到特定的实现上)的特征称为服务之间的松耦合。松耦合系统的好处有两点,一是它的灵活性,二是当组成整个应用程序的每个服务的内部结构和实现逐渐地发生改变时,它能够继续存在。

(3)Control服务控制器,是服务之间的纽带,不同服务之间的切换就是通过它来实现的。通过服务控制器控制服务切换可以将服务的实现和服务的转向控制分离,提高了服务实现的灵活性和重用性。

以下是Domain Model-Service-Control三者的互动关系。

(1)Service的运行会依赖于Domain Model的状态,反之,Service也会根据业务规则改变Domain Model的状态。

(2)Control作为服务控制器,根据Domain Model的状态和相关参数决定Service之间的执行顺序及相互关系。

Domain Model—Service—Control的互动关系,是吸取了Model—View—Control的优点,在“控制和显示的分离”的基础之上演变而来的,通过将服务和服务控制隔离,使程序具备高度的可重用性和灵活性。

数据访问层设计(持久层架构设计)

5种数据访问模式

在线访问

在线访问是最基本的数据访问模式,也是在实际开发过程中最常采用的。

如图16-8所示,这种数据访问模式会占用一个数据库连接,读取数据,每个数据库操作都会通过这个连接不断地与后台的数据源进行交互。

图16-8 在线访问模式找不到图片(Image not found)
Data Access Object

如图16-9所示,DAO模式是标准J2EE设计模式之一,开发人员常常用这种模式将底层数据访问操作与高层业务逻辑分离开。

图16-9 DAO模式找不到图片(Image not found)

一个典型的DAO实现通常有以下组件。

(1)一个DAO工厂类。

(2)一个DAO接口。

(3)一个实现了DAO接口的具体类。

(4)数据传输对象。

这当中具体的DAO类包含访问特定数据源的数据的逻辑。

Data Transfer Object

如图16-10所示,Data Transfer Object是经典EJB设计模式之一。DTO本身是这样一组对象或是数据的容器,它需要跨不同的进程或是网络的边界来传输数据。这类对象本身应该不包含具体的业务逻辑,并且通常这些对象内部只能进行一些诸如内部一致性检查和基本验证之类的方法,而且这些方法最好不要再调用其他的对象行为。

图16-10 DTO模式找不到图片(Image not found)

在具体设计这类对象(DTO)时,通常可以有如下两种选择。

(1)使用编程语言内置的集合对象,它通常只需要一个类,就可以在整个应用程序中满足任何数据传输目的;而且几乎所有的编程语言都内置了集合类型,不需要再另外编写实现代码。同时,使用内置的集合对象来实现DTO对象的时候,客户端必须按位置序号(在简单数组的情况下)或元素名称(在键控集合的情况下)访问集合内的字段。不过,集合存储的是同一类型(通常是最基本的Object类型)的对象,这有时会导致在编译时碰到一些无法检测到的编码错误。

(2)通过创建自定义类来实现DTO对象,通过定义显示的get或是set方法来访问数据。这种方式能够提供与任何其他对象完全一样的、客户端应用程序可访问的强类型对象。这种对象可以提供编译时的类型检查,但是增加了编码的工作量,若应用程序发出许多远程调用的话,需要编写大量的调用代码。

具体实现中有许多方法试图将上述这两种方法的优点结合在一起。第一种方法是代码生成技术,该技术可以生成脱离现有元数据(如可扩展标记语言XML架构)的自定义DTO类的源代码;第二种方法是提供更强大的集合,尽管它也是平台内置的一般的集合,但它将关系和数据类型信息与原始数据存储在一起,例如IBM提出的SDO技术或是微软ADO.NET中的DataSet就支持这类方法。

离线数据模式

离线数据模式是以数据为中心,数据从数据源获取之后,将按照某种预定义的结构(这种结构可以是SDO中的Data图表结构,也同样可以是ADO.NET中的关系结构)存放在系统中,成为应用的中心。离线,对数据的各种操作独立于各种与后台数据源之间的连接或是事务;与XML集成,数据可以方便地与XML格式的文档之间互相转换;独立于数据源,离线数据模式的不同实现定义了数据的各异的存放结构和规则,这些都是独立于具体的某种数据源的。

对象/关系映射(Object/Relation Mapping, O/R Mapping)

在最近几年,采用OR映射的指导思想来进行数据持久层的设计似乎已经成了一种潮流。对象/关系映射的基本思想来源于这样一种现实:大多数应用中的数据都是依据关系模型存储在关系型数据库中;而很多应用程序中的数据在开发或是运行时则是以对象的形式组织起来的。那么,对象/关系映射就提供了这样一种工具或是平台,能够帮助将应用程序中的数据转换成关系型数据库中的记录;或是将关系数据库中的记录转换成应用程序中代码便于操作的对象。

工厂模式在数据访问层应用

在应用程序的设计中,数据库的访问是非常重要的,数据库的访问需要良好的封装性和可维护性。在.Net中,数据库的访问,对于微软自家的SqlServer和其他数据库(支持OleDb),采用不同的访问方法,这些类分别分布于System.Data.SqlClient和System.Data.OleDb名称空间中。微软后来又推出了专门用于访问Oracle数据库的类库。我们希望在编写应用系统的时候,不因这么多类的不同而受到影响,尽量做到数据库无关。

这就需要在实际开发过程中将这些数据库访问类再作一次封装。经过这样的封装,不仅可以达到上述的目标,还可以减少操作数据库的步骤,减少代码编写量。工厂设计模式是使用的主要方法。

工厂模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。这里可能会处理对多种数据库的操作,因此,需要首先定义一个操纵数据库的接口,然后根据数据库的不同,由类工厂决定实例化哪个类。

下面首先来定义这个访问接口。为了方便说明问题,在这里只列出了比较少的方法,其他的方法是很容易参照添加的。

图片详情找不到图片(Image not found)

因为DataAccess的具体实现类有一些共同的方法,所以先从DataAccess实现一个抽象的AbstractDataAccess类,包含一些公用方法。然后,分别为Sql Server、Oracle和OleDb数据库编写三个数据访问的具体实现类。

图片详情找不到图片(Image not found)

现在已经完成了所要的功能,下面需要创建一个Factory类,来实现自动数据库切换的管理。这个类很简单,主要的功能就是根据数据库类型,返回适当的数据库操纵类。

图片详情找不到图片(Image not found)

现在一切都完成了,客户端在代码调用的时候,可能就是采用如下形式。

图片详情找不到图片(Image not found)

或者,如果事先设定了DataAccessFactory的DefaultPersistenceProperty属性,可以直接使用DataAccess db= DataAccessFactory.CreateDataAccess()方法创建DataAccess实例。

当数据库发生变化时,只需要修改PersistenceProperty的值,客户端不会感觉到变化,也不用去关心。这样,实现了良好的封装性。当然,前提是你在编写程序时,没有用到特定数据库的特性,例如,Sql Server的专用函数。

ORM、Hibernate与CMP2.0设计思想

ORM(Object-Relation Mapping)在关系型数据库和对象之间作一个映射,这样,在具体操作数据库时,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作即可。

当你开发一个应用程序的时候(不使用OR Mapping),可能会涉及许多数据访问层的代码,用来从数据库保存、删除和读取对象信息等,然而这些代码写起来总是重复的。

一个更好的办法就是引入OR Mapping。实质上,一个OR Mapping会为你生成DAL。与其自己写DAL代码,不如用OR Mapping,你只需要关心对象就好。

使用ORM可以大大降低学习和开发成本。而在实际的开发中,真正对客户有价值的是其独特的业务功能,而不应该把大量时间花费在编写数据访问、CRUD方法、后期的Bug查找和维护上。在使用ORM之后,ORM框架已经把数据库转变成了我们熟悉的对象,我们只需要了解面向对象开发就可以实现数据库应用程序的开发,不需要浪费时间在SQL上。同时也可减少代码量,减少数据层出错机会。

通过Cache的实现,能够对性能进行调优,实现了ORM区隔了实际数据存储和业务层之间的关系,能够对每一层进行单独跟踪,增加了性能优化的可能。

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了轻量级的对象封装,使Java程序员可以随心所欲地使用对象编程思维来操纵数据库。它不仅提供了从Java类到数据表之间的映射,还提供了数据查询和恢复机制。相对于使用JDBC和SQL来手工操作数据库,Hibernate可以大大减少操作数据库的工作量。另外,Hibernate可以利用代理模式来简化载入类的过程,这将大大减少利用Hibernate QL从数据库提取数据的代码的编写量。Hibernate可以和多种Web服务器或者应用服务器良好集成,如今已经支持几乎所有流行的数据库服务器。

Hibernate技术本质上是一个提供数据库服务的中间件,它的架构如图16-11所示。

图16-11 Hibernate架构图找不到图片(Image not found)

图16-11显示了Hibernate件(如hibernate.properties)的工作原理,它是利用数据库以及其他一些配置XML Mapping等来为应用程序提供数据持久化服务的。

Hibernate具有很大的灵活性,但同时它的体系结构比较复杂,提供了好几种不同的运行方式。在轻型体系中,应用程序提供JDBC连接,并且自行管理事务,这种方式使用了Hibernate的一个最小子集。在全面解决体系中,对于应用程序来说,所有底层的JDBC/JTA API都被抽象了,Hibernate会替你照管所有的细节。

Hibernate是一个功能强大,可以有效地进行数据库数据到业务对象的0/R映射方案。Hibernate推动了基于普通Java对象模型,用于映射底层数据结构的持久对象的开发。通过将持久层的生成自动扩展到一个更大的范围,Hibernate使开发人员专心实现业务逻辑而不用分心于繁琐的数据库方面的逻辑,同时提供了更加合理的模块划分的方法。

灵活运用Xml Schema

XML Schema用来描述XML文档合法结构、内容和限制。XML Schema由XML 1.0自描述,并且使用了命名空间,有丰富的内嵌数据类型及其强大的数据结构定义功能,充分地改造了并且极大地扩展了DTDs(传统描述XML文档结构和内容限制的机制)的能力,将逐步替代DTDs,成为XML体系中正式的类型语言,同XML规范、Namespace规范一起成为XML体系的坚实基础。

XML Schema由诸如类型定义和元素声明的组件组成,可以用来评估一个格式良好元素和属性信息的有效性。XML Schema是Schema组件的集合,这些组件分为三组:基本组件、组件和帮助组件。其中基本组件包括简单类型定义、复杂类型定义、属性声明和元素声明;组件包括属性组、完整性约束定义、模型组和符号声明;帮助组件包括注释、模型组、小品词、通配符和属性使用。Schema组件详细说明了抽象数据模型的每个组件的严格语义,每个组件在XML中的表示,一个XML Schema文档类型的DTD和XML Schema引用。

XML Schema提供了创建XML文档必要的框架,详细说明了一个XML文档的不同元素和属性的有效结构、限制和数据类型。XML Schema规范由如下三部分组成。

(1)XML Schema PartO: Primer。一个非标准化的文档,提供了XML Schema的一个简单可读的描述,目的是快速地理解如何利用XML Schema语言创建一个Schema(框架)。

(2)XML Schema Part1: Structures。这一部分详细说明了XML Schema定义语言,这个语言为描述XML 1.0文档的结构和内容限制提供了便利,包括开发了XMLNamespace(命名空间)的使用。

(3)XML Schema Part2: Datatypes。这一部分定义了可用于XML Schema和其他XML规范中的定义数据类型的方法。这个数据类型语言,本身由XML 1.0自描述,提供了说明元素和属性数据类型的XML 1.0文档类型定义(DTDs)的一个超集。这部分提出了标准的数据类型内容集合,其中讲述了目的、需求、范围和术语。XML Schema与DTD相比,有其独特的特点,提供了丰富的数据类型,实现了继承和复用,与命名空间紧密联系,易于使用。

与DTD不同,XML Schema规范提供了丰富的数据类型。其中不仅包括一些内嵌的数据类型,如string、integer、Boolean、time和date等,还提供了定义新类型的能力,如complexType和simpleType。开发者可以利用内嵌的数据类型和用户定义的数据类型,有效地定义和限制XML文档的属性和元素值。

XML Schema支持继承是它的另一特点。可以利用从已经存在的schema中获得某些类型而构造新的schema,也可以在不需要时使获得的类型无效。同时,XML Schema能将一个schema分成单独的组件,这样,在写Schema时,就可以正确地引用已经定义的组件。继承性使得软件复用更加有效,帮助开发者避免了每一次创建都要从零开始,极大地提高了软件开发和维护的效率。

XML Schema与XML Namespace紧密联系,使得在一个命名空间中创建元素和属性非常容易。这种联系简化了使用多个命名空间定义多个schema的XML文档的创建和验证文档有效性。

事务处理设计

事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚(回到最初的系统状态)。事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写。事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。持久性表示已提交的数据在事务执行失败时,数据的状态都应该正确。

一般情况下,J2EE应用服务器支持JDBC事务、JTA (Java Transaction API)事务和容器管理事务。一般情况下,最好不要在程序中同时使用上述三种事务类型,例如在JTA事务中嵌套JDBC事务。另外,事务要在尽可能短的时间内完成,不要在不同方法中实现事务的使用。下面举列说明两种事务处理方式。

JavaBean中使用JDBC方式进行事务处理

在JDBC中怎样将多个SQL语句组合成一个事务呢?在JDBC中,打开一个连接对象Connection时,默认是auto-commit模式,每个SQL语句都被当作一个事务,即每次执行一个语句,都会自动地得到事务确认。为了能将多个SQL语句组合成一个事务,要将auto-commit模式屏蔽掉。在auto-commit模式屏蔽掉之后,如果不调用commit()方法,SQL语句不会得到事务确认。在最近一次commit()方法调用之后的所有SQL会在方法commit()调用时得到确认。

图片详情找不到图片(Image not found)
SessionBean中的JTA事务

JTA是事务服务的J2EE解决方案。本质上,它是描述事务接口(例如UserTransaction接口,开发人员直接使用该接口或者通过J2EE容器使用该接口来确保业务逻辑能够可靠地运行)的J2EE模型的一部分。JTA具有的三个主要的接口,分别是UserTransaction接口、TransactionManager接口和Transaction接口。这些接口共享公共的事务操作,例如commit()和rollback();但是也包含特殊的事务操作,例如suspend()、resume()和enlist(),它们只出现在特定的接口上,以便在实现中允许一定程度的访问控制。例如,UserTransaction能够执行事务划分和基本的事务操作,而TransactionManager能够执行上下文管理。

应用程序可以调用UserTransaction.begin()方法开始一个事务,该事务与应用程序正在其中运行的当前线程相关联。底层的事务管理器实际处理线程与事务之间的关联。UserTransaction.commit()方法终止与当前线程关联的事务。UserTransaction.rollback()方法将放弃与当前线程关联的当前事务。

图片详情找不到图片(Image not found)

连接对象管理设计

在基于JDBC的数据库应用开发中,数据库连接的管理是一个难点,因为它是决定该应用性能的一个重要因素。

对于共享资源,有一个很著名的设计模式——资源池。该模式正是为了解决资源频繁分配、释放所造成的问题。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略。

建立连接池的第一步,就是要建立一个静态的连接池。所谓静态,是指池中的连接是在系统初始化时就分配好的,并且不能够随意关闭。Java中给我们提供了很多容器类,可以方便地用来构建连接池,如Vector、Stack等。在系统初始化时,根据配置创建连接并放置在连接池中,以后所使用的连接都是从该连接池中获取的,这样就可以避免连接随意建立、关闭造成的开销(当然,我们没有办法避免Java的Garbage Collection带来的开销)。

有了这个连接池,下面就可以提供一套自定义的分配、释放策略。当客户请求数据库连接时,首先看连接池中是否有未分配出去的连接。如果存在空闲连接则把连接分配给客户,并作相应处理。具体处理策略,在关键议题中会详述,主要的处理策略就是标记该连接为已分配。若连接池中没有空闲连接,就在已经分配出去的连接中,寻找一个合适的连接给客户,此时该连接在多个客户间复用。

当客户释放数据库连接时,可以根据该连接是否被复用,进行不同的处理。如果连接没有使用者,就放入到连接池中,而不是被关闭。可以看出,正是这套策略保证了数据库连接的有效复用。

数据架构规划与设计

数据库设计与类的设计融合

对类和类之间关系的正确识别是数据模型的关键所在。本节将讨论如何发现、识别以及描述类。要想将建模过程缩减为一个简单的、逐步进行的过程是不太可能的。从本质上讲,建模是一项艺术。对一个给定的复杂情况而言,不存在唯一正确的数据模型,然而却存在好的数据模型。一个企业或机构的某个数据模型可能会优于另一个数据模型,但就如何为一个特定的系统建立数据模型,却没有唯一的解决方案。

好模型的目标是将工程项目整个生存期内的花费减至最小,同时也会考虑到随时间的推移系统将可能发生的变化,因而设计时也要很容易地能适应这些变化。因此,将目光集中在最大限度地降低开发费用上是一个错误。

数据库设计与XML设计融合

WWW的迅速发展,使其成为全球信息传递和共享日益重要和最具潜力的资源,电子商务、电子图书和远程教育等全新领域的需求和发展,使Web数据变得更加复杂和多样化,利用传统数据库技术很难存储和管理所有不同的Web数据。

目前,XML正在成为Internet上数据描述和交换的标准,并且将来会代替HTML而成为Web上保存数据的主要格式。

XML文档分为两类:一类是以数据为中心的文档,这种文档在结构上是规则的,在内容上是同构的,具有较少的混合内容和嵌套层次,人们只关心文档中的数据而并不关心数据元素的存放顺序,这种文档简称为数据文档,它常用来存储和传输Web数据。另一类是以文档为中心的文档,这种文档的结构不规则,内容比较零散,具有较多的混合内容,并且元素之间的顺序是有关的,这种文档常用来在网页上发布描述性信息、产品性能介绍和E-mail信息等。

Web上存有大量的XML文档,并需要持久保存,这一需求引发了人们对XML文档的存储技术研究。已经提出的XML文档的存储方式有两种:基于文件的存储方式和数据库存储方式。

(1)基于文件的存储方式。基于文件的存储方式是指将XML文档按其原始文本形式存储,主要存储技术包括操作系统文件库、通用文档管理系统和传统数据库的列(作为二进制大对象BLOB或字符大对象CLOB)。这种存储方式需维护某种类型的附加索引,以建立文件之间的层次结构。基于文件的存储方式的特点:无法获取XML文档中的结构化数据;通过附加索引可以定位具有某些关键字的XML文档,一旦关键字不确定,将很难定位;查询时,只能以原始文档的形式返回,即不能获取文档内部信息;文件管理存在容量大、管理难的缺点。

(2)数据库存储方式。数据库在数据管理方面具有管理方便、存储占用空间小、检索速度快、修改效率高和安全性好等优点。一种比较自然的想法是采用数据库对XML文档进行存取和操作,这样可以利用相对成熟的数据库技术处理XML文档内部的数据。数据库存储方式的特点:能够管理结构化和半结构化数据;具有管理和控制整个文档集合本身的能力;可以对文档内部的数据进行操作;具有数据库技术的特性,如多用户、并发控制和一致性约束等;管理方便,易于操作。

在某种程度上,XML及其一系列相关技术就是一个数据库系统。它提供了传统数据库所具有的特点,如存储(以XML文档形式)、数据库的模式(DTD或XMLSchema)、查询语言(XQuery、XPath、XQL和XML-QL等)和编程接口(如SAX、DOM)等。但与传统数据库相比,它在存储、索引、安全、多用户访问和事务管理等方面还存在不足之处。在一定的环境下,例如当数据量和操作用户较少并且性能要求不高的情况下,XML文档能够作为数据库在应用程序中使用。如果应用程序有许多操作用户,并且要求严格的数据完整性和性能要求,则不宜采用XML文档。

XML数据库是一组XML文档的集合,并且是持久的和可操作的;有专门的DBMS管理(不是XML文件系统);文档都是有效的(即符合某一模式);文档的集合可能基于多个模式文件(即文件扩展名为.xsd),多个模式文件之间可能有语法和语义上的相互联系。

实战案例——电子商务网站(网上商店PetShop)

PetShop是一个范例,微软用它来展示.Net企业系统开发的能力。PetShop随着版本的不断更新,至现在基于.Net 2.0的PetShop4.0为止,整个设计逐渐变得成熟而优雅,有很多可以借鉴之处。PetShop是一个小型的项目,系统架构与代码都比较简单,却也凸现了许多颇有价值的设计与开发理念。

PetShop的系统架构设计

PetShop的表示层是用ASP.Net设计的,也就是说,它应是一个BS系统。在.Net中,标准的BS分层式结构如图16-12所示。

图16-12 Net中标准的BS分层式结构找不到图片(Image not found)

随着PetShop版本的更新,其分层式结构也在不断的完善,例如PetShop 2.0,就没有采用标准的三层式结构,如图16-13所示。

图16-13 PetShop 2.0的体系架构找不到图片(Image not found)

从图16-13中可以看到,并没有明显的数据访问层设计。这样的设计虽然提高了数据访问的性能,但也同时导致了业务逻辑层与数据访问的职责混乱。一旦要求支持的数据库发生变化,或者需要修改数据访问的逻辑,由于没有清晰的分层,会导致项目做大的修改。而随着硬件系统性能的提高,以及充分利用缓存、异步处理等机制,分层式结构所带来的性能影响几乎可以忽略不计。

PetShop 3.0纠正了此前层次不明的问题,将数据访问逻辑作为单独的一层独立出来。PetShop 3.0的体系架构如图16-14所示。

图16-14 PetShop 3.0的体系架构找不到图片(Image not found)

PetShop 4.0基本上延续了3.0的结构,但在性能上作了一定的改进,引入了缓存和异步处理机制,同时又充分利用了ASP.Net 2.0的新功能MemberShip。因此,PetShop 4.0的系统架构如图16-15所示。

图16-15 PetShop 4.0的体系架构找不到图片(Image not found)

比较3.0和4.0的系统架构图,其核心的内容并没有发生变化。在数据访问层(DAL)中,仍然采用DAL Interface抽象出数据访问逻辑,并以DAL Factory作为数据访问层对象的工厂模块。对于DAL Interface而言,分别有支持MS-SQL的SQL Server DAL和支持Oracle的Oracle DAL具体实现,而Model模块则包含了数据实体对象,其详细的模块结构如图16-16所示。

可以看到,在数据访问层中,完全采用了“面向接口编程”思想。抽象出来的IDAL模块,脱离了与具体数据库的依赖,从而使得整个数据访问层有利于数据库迁移。DALFactory模块专门管理DAL对象的创建,便于业务逻辑层访问。SQLServerDAL和OracleDAL模块均实现IDAL模块的接口,其中包含的逻辑就是对数据库的Select、Insert、Update和Delete操作。因为数据库类型的不同,对数据库的操作也有所不同,代码也会因此有所区别。

此外,抽象出来的IDAL模块,除了解除了向下的依赖之外,对于其上的业务逻辑层同样仅存在弱依赖关系,如图16-17所示。

图16-17 业务逻辑层的模块结构图找不到图片(Image not found)

图16-17中,BLL是业务逻辑层的核心模块,它包含了整个系统的核心业务。在业务逻辑层中,不能直接访问数据库,而必须通过数据访问层。注意,图16-17中对数据访问业务的调用,是通过接口模块IDAL来完成的。既然与具体的数据访问逻辑无关,则层与层之间的关系就是松散耦合的。如果此时需要修改数据访问层的具体实现,只要不涉及到IDAL的接口定义,那么业务逻辑层就不会受到任何影响。毕竟,具体实现的SQLServerDAL和OracalDAL根本就与业务逻辑层没有半点关系。

因为在PetShop 4.0中引入了异步处理机制,插入订单的策略可以分为同步和异步,两者的插入策略明显不同。但对于调用者而言,插入订单的接口是完全一样的,所以PetShop 4.0中设计了IBLLStrategy模块。虽然在IBLLStrategy模块中,仅仅是简单的IOrderStategy,但同时也给出了一个范例和信息,那就是在业务逻辑的处理中,如果存在业务操作的多样化或者是今后可能的变化,均应利用抽象的原理、或者使用接口、或者使用抽象类,从而脱离对具体业务的依赖。不过在PetShop中,由于业务逻辑相对简单,这种思想体现得不够明显。也正因为此,PetShop将核心的业务逻辑都放到了一个模块BLL中,并没有将具体的实现和抽象严格地按照模块分开。所以表示层和业务逻辑层之间的调用关系,其耦合度相对较高。

图16-18表示层的模块结构图中,各个层次中还引入了辅助的模块,如数据访问层的Messaging模块,是为异步插入订单的功能提供,采用了MSMQ(Microsoft Messaging Queue)技术,而表示层的CacheDependency则提供缓存功能。

图16-18 表示层的模块结构找不到图片(Image not found)