Microsoft.NET框架1.0和1.1MicrosoftWindows2000和WindowsServer2003MicrosoftInternetInformationServicesMicrosoftSQLServer2000Oracle9iDatabase
最初研究.NETPetShop的目的是用Microsoft.NET实现Sun主要的J2EE蓝图应用程序SunJavaPetStore同样的应用程序功能。根据用.NET实现的SunJ2EE最佳实践示例应用程序,各方面的客户可以直接地对Microsoft的.NET技术与基于J2EE的应用程序服务器进行比较,同时了解构建基于Web的应用程序中用到的各种建议的设计模式之间的异同。.NETPetShop应用程序现在已经是第三版了,旨在显示构建企业级n层应用程序(可能需要支持多种数据库平台和部署模型)的.NET最佳实践。根据社区对.NetPetShop2.0的反馈,.NETPetShop3.0遵照MSDN上发布的Microsoft《说明性体系结构指导》进行了重新设计。第三版还完全符合了Middleware公司的应用程序服务器基准测试规范,将作为Microsoft参加今年春天即将进行的MiddlewareApplicationServerBenchmark的产品:这是Middleware公司举办的第二轮测试活动,旨在比较.NET和J2EE平台在构建和承载企业级Web应用程序方面的可伸缩性。
JavaPetStore是按Sun公司维护的J2EE蓝图开发的分布式应用程序的一个参考实现。示例应用程序最初的开发目的是帮助开发人员和架构师理解如何使用和利用J2EE技术,以及各个J2EE平台组件是如何配合的。JavaPetStore演示软件包括构建应用程序所需的EnterpriseJavaBeans(EJB)体系结构、JavaServerPages(JSP)技术、标记库和servlet的完整的源代码及文档。此外,JavaPetStore蓝图应用程序还通过具体示例说明了一些模型和设计模式。
完整的JavaPetStore包括三个示例应用程序:
JavaPetStore:J2EE蓝图主应用程序。
JavaPetStore管理器:JavaPetStore的管理器模块
BlueprintsMailer:在小一些的包中给出一些J2EE蓝图设计指南的一个小应用程序。
使用JavaPetStore的典型会话方案如下:
主页—这是用户第一次启动应用程序时加载的主页。
产品—如果现在选择一个产品,应用程序将显示产品的所有类型。通常产品类型是雄或者雌。
产品详情—每种产品类型(分别用不同项目表示)有详细的视图显示产品说明、产品图像、价格和库存数量。
购物车—用户可以通过它操作购物车(添加、删除和更新行项目)。
结帐—结帐页面以只读视图显示购物车。
定单确认—显示记帐地址和送货地址。
定单提交—这是定单处理流程的最后一步。定单现在将提交到数据库。
图1.JavaPetStore
.NETPetShop的目标是把注意力仅仅放在JavaPetStore上(管理和Mailer组件没有在.NET中实现)。除了重现JavaPetStore应用程序的功能之外,还增加了两项目标:
比较.NET和J2EE通过最佳实践实现的真实应用程序中代码和代码大小上的异同。
提供用.NET和J2EE实现的典型的设计良好的应用程序能够支持多少用户的数据。
图2..NETPetShop
图3..NETPetShop高层逻辑体系结构
图4说明了Microsoft.NETPetShop物理上是怎样部署的。这里使用网络负载平衡(NLB)或者可能是硬件实现的负载平衡技术将入站的网络通信量分到了两台应用程序服务器上。在网络请求达到群集中的一台机器时,针对该请求的所有工作都会在这台特定机器上进行。业务逻辑和数据访问组件将以程序集的形式安装在两台服务器上,它们本质上是完全相同的。如果负载平衡软件配置为使用“StickyIP”,则每台服务器都有自己的会话-状态存储,因为要保证第二个请求返回到实现第一个请求的那台服务器。如果解决方案所需的容错要求更高,两台应用程序服务器可以共享一个公共会话-状态存储比如SQLServer或者一台专用的会话服务器(图中没有显示)。会话-状态存储的类型和位置由每个站点‘web.config’文件里‘system.web’元素‘sessionState’子节点中的值决定。
图4..NETPetShop的物理部署图
作为PetShop3体系结构文档的一部分,我们给出了.NETPetShop的业务需求,这样开发人员和客户就可以理解我们在做应用程序的设计决策时进行的一些选择。
PetShop应用程序的功能性需求是什么?
应用程序应该使客户能够按类和通过关键字搜索浏览公司目录。
应用程序应该为客户提供一种通过一个购物车模型就能购买多个商品项的机制。
应用程序旨在支持高容量的企业级电子商务解决方案;因此应用程序应该展示以下方面:
通过增加更多处理器来扩展的能力
通过增加更多机器组成群集的分布式扩展能力
在大型企业级系统中,应用程序可能需要访问多个数据库,因此应用程序应该支持分布式事务。
应用程序应该考虑灵活的部署策略。默认时应用程序的设计方案是要部署到两台机器上,一台是应用程序服务器,一台是数据库服务器,但是应该能够扩展在其他部署模型下工作。应用程序应该支持多个数据库供应商。这里我们选择了MicrosoftSQLServer和Oracle。
应用程序应该容易维护,这是通过应用程序中的代码行数来衡量的。
.NETPetShop中使用的数据库架构是直接从JavaPetStore移植而来的。JavaPetStore支持几种数据库供应商格式,因此我们选取了Sybase应用程序的架构,并在一个MicrosoftSQLServer2000实例中创建。这不需要改变Sybase版本的架构。而创建Oracle版本的.NETPetShop时,我们直接采用了JavaPetStore数据库原来的Oracle实现。
数据库有如下整体表结构,参见表1:
表1.PetShop中的数据库表
Account
代表基本客户信息
BannerData
Category
目录类别(Fish,Dogs,Cats等)
Inventory
产品库存状态
Item
各个产品的细节
LineItem
定单细节
Orders
客户下的定单。定单包括一个或多个行项目
OrderStatus
定单状态
Product
目录产品,每个产品可有一或多类型(项目)。通常类型可能是雄或雌。
Profile
客户的用户配置情况
Signon
Supplier
有关供应商信息
在.NETPetShop版本2中,应用程序改为要创建一个方案,其中完成定单处理必须使用分布式事务。为了适应分布式事务方案,Orders、OrderStatus和LineItem表都分到不同的可能安装在不同机器上的数据库实例。我们在.NETPetShop的第三版中保持了这个分布式设计模式。
图5..NETPetShop订购数据库架构
图6..NETPetShop帐号和产品数据库架构
PetShop表的设计可以做什么更改?
应用程序中使用的架构可以做一些更改;然而,这些更改并不是为了与JavaPetStore参考实现提供的架构一致。这些更改已列于表2中:
表2.PetShop架构中可能的改进
表中不存储HTML
因为可能要使用不同客户端应用程序类型,数据库仅存储图像文件名而不是图像标记,部署客户端类型可以更灵活。
对客户密码使用单向加密算法
帮助使应用程序更安全,因为即使系统任何部分的安全受到威胁,读取密码仍然很困难。这是要求设施必须重置密码为新值。
加密信用卡信息
在系统安全受到威胁时防治对信用卡信息的访问。做出的其他更改还有将表设为只能允许通过一个存储过程进行写入访问;因此黑客访问数据库将不得不需要另一套凭据来读取数据。
图7..NETPetShop2应用程序的体系结构
.NETPetShop2.0被设计成部署在物理上两层的部署环境中,并且在应用程序的一些部分的实现中利用了这一事实。应用程序由以下部分构成:一个用ASP.NETWeb窗体(用“代码隐藏”将应用程序HTML和用户接口代码分离)创建的Web层。一个包含控制应用程序逻辑的业务组件(通过自定义版本的Microsoft数据库访问应用程序块(DAAB)与SQLServer数据库通信)的中间层。为了支持分布式事务,一些中间层业务组件是用企业服务实现的。对于Microsoft.NET,这是一种支持分布式事务的建议方式。然而,并非所有类都要扩展ServicedComponent类,因为将所有类都实现为企业服务的组件是有性能开销的。发布第一次Middleware应用程序服务器基准测试中使用的.NETPetShop2.0时,我们收到了许多反馈,认为体系结构应该优化,以更适应于大规模的企业。反馈比较集中的方面包括:
创建完全抽象的数据层,无需在中间层导入数据特定的类。
为Oracle实现数据访问层,可以透明地使用与SQLServer版本一样的业务层和UI层。
将Web会话状态从业务逻辑层和数据层中完全提取出来,这样应用程序的后端组件可以在物理上分布到Web服务器之外的其他计算机上,或者从其他类型的客户端比如基于Windows的客户端和基于Web–的客户端重用。
将应用程序模块整个分为多个命名空间和物理程序集。
其他各方面的反馈。
图8..NETPetShop3.0应用程序体系结构
应用领域
表3.PetShop解决方案中的应用领域
用户接口组件
捕获来自用户的数据输入,显示后端系统返回的数据。它们还处理简单的定位。参见用户接口组件。
ASP.NETWeb窗体,用户控件和服务器控件。这些构造能够清晰地分离设计者的HTML和UI代码比如按钮的事件处理程序。
用户接口处理
用后端业务对象控制用户定位和处理流程。还要处理用户会话数据的管理。参考用户处理组件。
这些是用C#类实现的。会话状态管理由ASP.NET处理。
业务组件
实现应用程序业务逻辑的组件
这些是用C#类实现的
业务实体
在应用程序各层之间传递数据的瘦数据类。参见业务实体组件。
这些是用C#类实现的,每个字段都以属性的形式公开。每个类都标记为“serializable”,启用进程间传输。
数据访问层组件
处理与后端数据存储区的交互,包括数据库、消息处理系统等。
这些组件处理与后端数据存储区的交互,包括数据库、消息处理系统等,是用四个C#项目实现的:
一组接口类,要公开的每个数据访问方法都有。
SQLServer接口的实现。
Oracle9i接口的实现。
一组加载正确实现的工厂类,SQLServer或Oracle。
MicrosoftVisualStudio.NET解决方案
图9显示了MicrosoftVisualStudio.NET解决方案对.NETPetShop应用程序的布局。应用程序的每个元素或层都有自己的项目,这样解决方案可以管理和清晰地定义应用程序中使用的新类应该放在哪里,旧类又可以在哪里找到。
图9.VisualStudio.NET应用程序解决方案
表4中列出了每个项目的目的:
表4.PetShop解决方案中的VisualStudio项目
BLL
业务逻辑组件存放之处
ConfigTool
用来加密连接字符串和创建事件日志源的管理应用程序
DALFactory
用来确定加载哪一个数据库访问程序集的类
IDAL
每个DAL实现都要实现的一组接口
Model
瘦数据类或业务实体
OracleDAL
Oracle特定的PetShopDAL实现,使用了IDAL接口
Post-Build
运行编译后操作的项目,比如将程序集添加到GAC或COM+
Pre-Build
将程序集从GAC删除或从COM+注销程序集的项目
SQLServerDAL
MicrosoftSQLServer特定的PetShopDAL实现,使用了IDAL接口
Utility
一组帮助器类,包括DPAPI的包装
Web
Web页和控件
SolutionItems
用来构建应用程序的杂项,比如用来签署应用程序程序集的PetShop.snk密钥文件
数据库可移植性
1.创建DB2的数据库访问类,它应该实现IDAL接口。
2.将DB2访问类编译成一个程序集。
3.测试和部署新的数据程序集到一台运行中的服务器上。
4.更改配置文件,指向新的数据库访问类。
无需更改或重新编译业务逻辑组件。
图10..NETPetShop中DAL工厂类的实现
存储过程
通常我们都建议客户使用存储过程来访问数据库中的表。原因如下:
存储过程提供了封装查询的一种简洁机制。
修改查询可以在不改变数据访问代码的情况下进行。
DBA可以很容易地看到正在执行什么SQL语句。
存储过程一般更安全,对数据库访问的控制也更容易。
使用存储过程,可通过在存储过程中发送多个请求,避免与客户端的多次往返行程。
存储过程与中间层生成的SQL相比,通常能提供最佳性能。
存储过程提供了极好的封装XML查询和XML输入参数的方式。
存储过程的缺点在于,它们往往是专有的,不能跨平台移植。
然而,要想最大程度地利用在数据库软件和硬件上已经花费的投资,开发人员往往对应用程序中使用的SQL针对具体数据库引擎进行优化,无论SQL是在存储过程中还是在中间层生成的。这一点有一个很好的例子,就是唯一编号或者标识编号的生成,因为所有数据库执行此操作时都支持自己的特殊机制,所以用来生成唯一编号的SQL就往往是特定于所用数据库的。一般总是有替代方案的,但是它们的执行速度都比不上专有解决方案。
对于.NETPetShop,我们有意识地没有在应用程序使用存储过程,因为这在Middleware基准测试中会被看作是.NET解决方案一种不太公平的优势。实际上,这方面的性能差异很小,因为应用程序相对比较简单,大多数SQL语句的执行计划都缓存在数据库中了。但是,Middleware基准测试规范不允许使用存储过程,哪怕只是包装简单的SQL语句,因此.NETPetShop3.0没有使用存储过程。
缓存
最有效的提高数据库驱动的应用程序性能的方式,是避免对每次请求都访问数据库。ASP.NET提供了各种缓存机制以提高大多数应用程序中的性能。ASP.NET中使用缓存的两种主要方式是输出缓存和数据缓存。
.NET缓存选项
页面级输出缓存接收来自ASP.NETWeb页的响应,并将整个页面存入缓存中。页面缓存被设计成工作在Web层和中间层之间,缓存中间层方法的结果/数据,或在两层应用程序中缓存数据库调用结果。第一版的.NETPetShop同时提供了一个页面级输出缓存版本和一个非缓存版本。第三版只支持数据缓存,但是可以很容易地改为支持输出缓存。对于WindowsServer2003和IIS6.0,有些输出缓存的页面(那些VaryByParm="none"而且有Cache'Anywhere'指令的)还能在内核级进行缓存,Internet客户端访问就更快了。无论如何,任何输出缓存的页面(内核或者非内核缓存)Windows应用程序服务器都可以在资源(CPU)消耗少得多的情况下,极快速地进行服务,因为实际上重新创建页面无需进行处理。
ASP.NET输出缓存
ASP.NET数据和对象缓存
对象缓存(缓存API)允许使用.NET框架在内部缓存引擎中存储方法调用或者数据库查询的结果。由于已经深入了应用程序工作管道,数据缓存可能无法像输出缓存那样提供同样的性能提升,因为对每个请求仍然必须动态构造HTML页面。但是,通过在中间层存储非易失数据,已经在完全动态的页面和减少数据库负载之间取得了很好的折衷。例如,要在两个Web页中以不同方式显示同样的数据,或者在同一个应用程序不同页面中以不同方式使用已经缓存的对象或数据。
ASP.NET缓存监视
表5..NET缓存选项摘要
输出缓存
提供了最佳性能
整个页面输出都缓存
片段缓存
(缓存用户控件)
实现很简单。整个页面输出都缓存
缓存API
页面中不同用户控件可以有不同的缓存超时
缓存控件可以跨页面共享。需要分别缓存每个用户控件
PetShopMiddleware基准测试缓存规则
基准测试的第二条规则是不应该允许页面级输出缓存;应始终要求Web服务器重新为页面创建要呈现的HTML。这是为了测试应用程序服务器生成动态页面的能力,而不是测试服务器从缓存拖出HTML有多快。
PetShop3.0缓存实现
1
创建数据库访问层(DAL)。
可以将业务逻辑从数据库访问代码中完全分离出来,因此可以无需更改业务逻辑代码,即可更换数据库供应商。
2
为所有瘦数据类(模型)创建公共的项目。
模型项目只包含保存数据的瘦类,可以用在应用程序的每一层,作为传输数据的容器。所有模型类都通过[Serializable]标记支持序列化,以添加对群集和任何未来物理部署中变化的增强支持。
3
在Components项目中删除对System.Web的引用。
这是一种好的设计实践,因为可以使中间层组件用于不同类型的UI。
4
将一些自定义服务器控件改为Web用户控件;然而Pager控件仍然保留为自定义服务器控件。
标头和标题控件含有许多HTML/UI内容,因此如果实现为Web用户控件更易维护。Pager控件处理的是查询字符串的操作,视图状态中数据的使用,因此作为自定义服务器控件更合适。
5
为Oracle创建了DAL实现。
为了支持Oracle数据库,创建了特定的OracleDAL,可以使用Oracle特定的驱动程序
6
DAL层现在实现了工厂模式[GoF]以加载相应的供应商特定的DAL。
为了隐藏后端所使用的数据库,使用工厂方法将接口返回到要用到的DAL层。
7
模型类中的所有公共字段都转换为属性。
这样字段的存储机制就可以隐藏起来,如果要看什么函数修改了某些数据,这提供了一个很好的在代码中添加断点的地方。
8
9
将所有静态方法改为实例方法。
10
将assemblyinfo.cs中的版本号改为与部署匹配的具体版本。
在DALFactory对象中加载程序集时,允许指定证据。
11
在VisualStudio.NET解决方案中添加构建前和构建后的步骤。
这样,在项目编译后,就可以对具体程序集运行gacutil和regsvcs实用工具。
12
将订购业务组件改名,根据其执行的功能而非实现机制。
对其他开发人员而言理解组件的功能更直观。
13
创建配置工具辅助应用程序的设置。
需要以管理员组成员的身份创建应用程序事件日志源。确保完成最简单的方式是提供一个简单的工具,可以在部署后运行。
14
将代码修改为允许帐号和产品数据库以及定单数据库使用不同的DAL。
对用户请求的响应应该能够使用混合数据库部署模型。
15
页面中添加页面输出缓存;但是基准测试时要删除。
有用户请求说明如何重写VaryByCustom函数使标题和标头控件等页面能够在default.aspx这样的页面中缓存。