代码改变世界

petShop详解

2006-08-04 13:50  Jacky_Xu  阅读(1256)  评论(0编辑  收藏  举报
 

.NET Pet Shop解决问题的描述

.NET Pet Shop是一个电子商务的实例,是在Microsoft.NET平台上的一个具体实现。这个系统包含了B2CB2B的实现。在这里我主要将的是B2C实现的部分,B2B.NET Pet Shop里面不是重点要解决的问题。

.NET Pet Shop是一个在线的宠物购物系统,用户可以通过各种能够连接到internet的终端(包括移动终端)进行在线购物(具体界面见图一)。在这里,我将列出它将要实现的功能,也就是需求了:

(1)    用户帐号的管理功能:包括帐号创建,帐号登录,帐号维护;

(2)    产品浏览功能:类别浏览,具体产品浏览,详细信息,库存信息等等;

(3)    用户购物功能:添加购物,计算总价,下订单等等.NET Pet Shop的逻辑结构

.NET Pet Shop按照三层的分层模型来部署整个系统。.NET Pet Shop分为数据展示层,中间层,数据层。这三层被清晰的分割为分布式应用的三个方面。其中数据展示层主要是完成界面和与最终用户交互的功能,在应用程序里面是一些aspx的页面和代码;中间层用于封装商业逻辑和规则,在应用程序里面被封装为.NET组件;数据访问通过中间层中的数据访问组件与SQL Server Provider交互,所有的数据获取依靠存储过程来进行,而不是通过SQL语句。图二是参考petshop的白皮书说明.NET Pet Shop的物理部署结构。

交互的三层综述

在前面的文章中,我们丛整个系统的角度了解了petshop的三层部署结构物理上的一个概貌,没有从应用程序的角度来说明是如何划分的,也不清楚数据展示层,中间层,数据层是如何交互的。为了更好的说明这个三层的结构以及每一层的具体细节问题,也让大家对整个应用有一个walkthrough,在这里我将说明从系统的角度来看(见参考图1),顾客在购物的过程中,系统的三层是如何的工作以及如何的交互。这个东西有点象UML里面的实例情景故事,对需求和分析的进一步是大有帮助的。 从这个情景中,我们可以很清晰的了解到Internet Customer在购物时系统的运作情形。首先通过数据展示层的用户交互界面Cart.aspx页面输入顾客购买的商品以及数量,然后这些购物清单由后台的应用程序(cart.aspx.cs)做相应的预处理(如安全验证,校验,数据的格式化等等),接着调用中间件(在程序结构里面就是一些.NET Assembly,封装了购物的商业逻辑),最后通过数据访问接口来更新数据库里面的数据(数据层)。

数据层(Database

.NET Petshop的数据库并不是十分的庞大,总共有12个用户表和23个存储过程。Petshop数据库里面存储的是用户数据,帐号数据,产品数据,用户配置数据,订单数据,库存数据以及供应商的数据。应用程序访问数据库的数据并不是直接的与数据库表打交道,而是通过存储过程的运行来获取所需要的数据。这样的设计有一个好处就是,避免了频繁的表操作,而通过运行在服务器端的存储过程可以极大的提高运作效率和提升访问数据的速度,同时也很好的屏蔽了数据库表的逻辑,使得数据库访问变成了数据库提供的服务访问。当然,也有人指责说这些存储过程迁移性是值得怀疑的。

下面我将通过表格把这些数据库的基本表列出来(见表1),并一一做说明,希望对大家深入的理解有帮助。

表名称

备注信息

Account

基本用户信息。

BannerData

存储的是系统界面的banner图片的设置信息。

Category

宠物的类别目录表(比如鱼类,狗类等等)。

Inventory

宠物产品的存货信息。

Item

单个产品的详细信息。

LineItem

订单的每一项的详细信息,包括产品名称和数量,价格等。

Orders

用户购物的订单,一个订单可以包括多项LineItem

OrderStatus

订单的状态

Product

宠物的产品列表,一条Product可能包括多个Item

Profile

用户配置表,用于记录他们的favorites

Signon

用户帐号登陆表,因为常用,故从Account独立出来。

Supplier

供应商信息。

通过上表我们知道了petshop数据库的数据库表的一些信息,但是这些表之间到底是一种什么样的关系呢?关心数据库建模和设计的人可能对这个问题比较感兴趣。在这里,我将给出图表(图2)说明这些数据库表之间的关系。图2中列出了这些表之间的关系和主键信息。

技巧:其实对于从事过数据库建模和设计的人都知道,得到上面的数据库模型图形并不是一件很困难的事情。正如我们前面提到的一样,我们可以用Visio做工程反转就可以得到上面这么美观的设计模型图了。同时,我们也可以在模型图中做修改设计,可以马上应用到你的物理数据库,使其保持同步。

说完了基本的数据库表,接下来我们看看存储过程。用微软的话说,只有设计称存储过程,才算是”cleaner separation of code from the middle-tier”,我个人觉得这样做是很好的。同样的,我把它列在一个表格里(表2)。

 存储过程名称

备注信息

upAccountAdd

增加一个帐号。

upAccountGetAddress

获取用户的地址,主要用于下订单时注册地址与送货地址不一。

upAccountGetDetails

获取帐号的详细信息。

upAccountLogin

用户登陆验证。

upAccountUpdate

更新用户帐号。

upCategoryGetList

获取某个类别的产品列表。

upInventoryAdd

增加指定的项到存活信息。

upInventoryGetList

获取存货列表。

upItemAdd

增加一项产品。

upItemGetDetails

获取指定产品项的详细信息。

upItemGetList

获取某一特定类别的产品的具体项目列表。

upItemGetList_ListByPage

功能与上同,但是分页获取数据。

upOrdersAdd

增加一项订单。

upOrdersGet

获取某一订单的信息。

upOrdersGetDetails

获取某一订单的详细信息。

upOrderStatusGet

获取订单的状态。

upProductAdd

增加一类别产品。

upProductGetList

返回某类产品的列表。

upProductGetList_ListByPage

与上同,但是分页获取结果数据。。

upProductSearch

产品搜索。

upProductSearch_ListByPage

与上同,但是翻页获取结果数据。

upProfileGetBannerOption

Banner的配置信息。

upProfileGetListOption

获取用户配置信息。

2petshop存储过程列表

在这些存储过程里面使用了SQL Server2000OpenXML的特性来代替传统的行集结果,使用在非常频繁的数据访问操作,可以减轻系统的负担。

好了,petshopDatatier就说到这里了,在后面的文章我会就一个数据访问的实例再次解说存储过程的执行。接下来我将就中间层做介绍了通过前面的文章,我们对.NET Petshop的整个结构有了一个大致的了解,也清楚的知道了数据库的设计模式和实现的细节,尤其值得一提的是通过存储过程访问数据库。在接下来的这篇文章里,我将和大家一起来探究一下.NET Petshop的中间层。

根据三层结构的设计原则,中间层封装的是业务逻辑和规则,在这个网络宠物商店的例子中,购物处理,订单处理,帐号管理,产品查询等等都是具体的业务逻辑,至于与用户交互并不是中间层要处理的问题。它处理是与具体的用户界面和交互无关,而仅仅是核心的商业规则和逻辑。.NET Petshop的中间层业务逻辑被封装为一个.NET 组件,它的命名空间为Pet Shop.Components(编译后在bin的文件夹里面有一个petshop.dll的文件)。图1.NET Petshop解决方案中间层的类视图和文件视图

接下来,我们模拟顾客到百货超市采购日常用品的过程来说明运作的流程以及抽象出重要的概念(实际上User case,我们在领域分析的时候会这么做,并且是很重要的一步,从这里可以初步的发现在我们实施的系统中将要涉及到的逻辑实体,进而可以为数据库建模设计以及类设计提供参考)。

购物用例的业务分析:

1 客户有购买商品的意愿;

2 客户到登陆管理员处登记,且成功登记;

3 在登记处推一个购物车;

4 在超市内查找所购商品类别存放的货架;

5 在具体的货架上查找某一具体品牌的商品;

6 将符合意愿的商品放入自己的购物车;

7 重复4-6

8 购物完毕;

9 到付款处计算总价格并付款;

10 打印购物清单;

11 退还购物车;

12 取走购物,购物完毕;

备注:在这个用例中,我们做了一些前提和假设,为的是方便.NET Petshop的分析,比如说在实际生活中根本就不需要第二步。

通过这个用例的分析,我们至少可以抽象出一下几个重要概念,并且能在应用程序里面找到对应的类:客户对应Customer、商品对应Product、购物车对应ShoppingCart、商品类别对应Category、具体商品对应Item、清单对应Order

正如我前面说过的,这几个概念对于我们的业务建模和系统建模是非常有用的。正是通过这样的分析,在.NET Petshop的业务逻辑里面共有9个核心类和5个轻量级的数据结构类。同样的方式,我在这里列出这些类,并加以说明(见表1)。说明

BasketItem

代表购物车ShoppingCart里的一项购物商品。

Customer

用于帐号管理和登陆验证。

CustomerDetails

用户帐号的详细信息。

CustomerAddress

用户帐号的地址信息。

Error

用于登陆出错的帮助功能。

Item

代表某类产品中的具体一项商品。

ItemResults

搜索Item的结果集。

Order

购物完毕后的购物清单和订单。

Product

大类别里面的某类产品。

ProductResults

搜索产品的结果集。

Profile

用户的配置。

ShoppingCart

购物车,用于购物的整个过程,直到下订单。

Database

通过ADO.NET访问数据库,封装了具体的访问方法。

SearchResults

模糊搜索的结果集。

1.NET Petshop中间层的类

CustomerAddress, CustomerDetails, ItemResults, ProductResults, and SearchResults这几个轻量级的数据结构类为在数据层和展示层之间提供了一种松散的数据绑定调用。这些类都被设计为有公开的属性,ASP.NET web页面可以通过这些属性访问数据。下面这段类的代码说明了这5个类是如何暴露自己的公开属性供展示层使用的。

public class ProductResults

       {

              private string m_productid;

              private string m_name;

              public string productid {

                     get { return m_productid; }

                     set { m_productid = value; } 

              }

              public string name {

                     get { return m_name; }

                     set { m_name = value; } 

              }

       }

.NET Petshop详解(二)中我们就说过数据库的访问是通过存储来进行的,我们看看下面这部分代码就知道了:

       public string Login(string userName, string password) {

                     string customerID;

                     // params to stored proc

                     Database data = new Database();

                     SqlParameter[] prams = {

                            data.MakeInParam("@username",    SqlDbType.VarChar, 25, password),

                            data.MakeInParam("@password",    SqlDbType.VarChar, 25, userName),

                            data.MakeOutParam("@CustomerID", SqlDbType.VarChar, 25)

                     };

                     // create data object and params

                     data.RunProc("upAccountLogin", prams);    // run the stored procedure

                     customerID = (string) prams[2].Value;     // get the output param value

                     // if the customer id is an empty string, then the login failed

                     if (customerID == string.Empty)

                            return null;

                     else

                            return customerID;

              }

 

这段代码是Customer类的Login方法,它是通过将用户输入的用户名userName和密码password做为输入参数传递给存储过程upAccountLogin的,这个存储过程完成在Sigon用户帐号表里面查找该用户是否合法,最后返回一个字符串的用户ID值。在这里没有使用SQL查询语句,很好的分离了逻辑。具体的数据库访问是通过Database来完成的,我们将在后面的文章中继续探讨它的运作。

ShoppingCart是比较有意思的一个类,也是很重要的一个类。它是与状态有关的一个类,在.NET Petshop里面,它的状态是通过ASP.NET Session state来管理的,关于其进一步的细节留待后面讨论。

.NET Petshop的中间层的探讨先到此,我在这里只是抛砖引玉,很多的东西要深入代码才可以搞的更加清楚。欢迎大家继续与我一起关注下一篇.NET Petshop的数据展示层。在前面的文章中,我们已经就.NET Petshop的数据层和中间的业务逻辑层作了说明,接下来的文章中,我们将就数据展示层作探究。与前面的两层有着很大的差别的是,.NET Petshop的展示层用了很多Microsoft最新的web Forms技术即Asp.NET。因此,在解说展示层之前,我想就Asp.NET.NET Petshop的开发中使用到的非常重要的特性做一些说明,以示区别:

(1)       Asp.NET代码不再是解释型代码,可以经由JIT编译器编译后运行,并且引入了很好的页面缓冲机制。

(2)       ASP.Net的配置模型引入了基于XML文件的零安装配置模型。零安装的含义是只需将配置文件Web.config,应用程序拷贝到系统指定的目录下即可,需要更改时直接在文件里更改并保存。

(3)       安全管理 ASP.Net提供了比传统ASP更强大可靠的安全管理。Asp.NET提供了三种验证方式以及两种类型的授权服务。

(4)       支持代码和页面内容的分离。回想以前编写Asp程序的时候的问题:代码逻辑混乱,难于管理。

(5)       提供了更好的状态管理,包括会话状态的管理和视图状态的管理。

(6)       大量的ASP.NET服务器端控件和对用户控件的开发的支持。

.NET Petshop充分的使用了服务器控件技术和会话状态管理。展示层的交互界面均采用aspx页面,后端有分离的逻辑代码。.NET Petshop共有19aspx页面和代码逻辑。在这里一一里列出,并说明设置参数和其作用ASP.NET Web页面

EnableSessionState

EnableViewState

备注说明

Cart.aspx

true

true

购物清单

Category.aspx

True

false

产品分类的列表

CheckOut.aspx

Readonly

false

订单确认

CreateNewAccount.aspx

False

true

创建新帐号

Default.aspx

False

false

系统首页面

EditAccount.aspx

False

true

账号编辑

Error.aspx

False

false

错误处理

Help.aspx

False

false

帮助

OrderAddressConfirm.aspx

readonly

false

订单地址确认

OrderBilling.aspx

True

true

订单信息

OrderProcess.aspx

readonly

false

下订单

OrderShipping.aspx

True

true

订单地址和姓名

Product.aspx

False

false

产品列表

ProductDetails.aspx

false

false

产品详细信息

Search.aspx

false

false

搜索

SignIn.aspx

false

false

帐号登陆

SignOut.aspx

true

false

帐号退出

ValidateAccount.aspx

false

false

帐号创建确认

VerifySignIn.aspx

false

false

账号登陆确认

.NET Petshop使用了很多用户控件,这些控件位于web/Inc目录下面。表格2对用户控件作出说明:

User Controls

EnableViewState

备注说明

ControlAddress

true

详细地址列表

ControlBanner

false

页面底部的banner

ControlCart

true

购物清单

ControlFavList

false

个人Favorites列表

ControlHeader

false

页面顶部的菜单和链接

ControlStaticAddress

false

不能编辑的地址信息

下面列出一段代码,说明这些参数的设置和用户控件的引用:

/*  摘自Cart.aspx   */

<%@ Register TagPrefix="PetShop" TagName="Cart" Src="http://www.xmlasp.net/Inc/ControlCart.ascx" %>

<%@ Register TagPrefix="PetShop" TagName="Header" Src="http://www.xmlasp.net/Inc/ControlHeader.ascx" %>

<%@ Page language="c#" Codebehind="CheckOut.aspx.cs" AutoEventWireup="false" Inherits="PetShop.Web.CheckOut" EnableSessionState="readonly" enableViewState="False"%>
开始的两个Registe指令用于引用ControlCartControlHeader的用户控件,Page指令定义 ASP.NET 页分析器和编译器使用的页特定的属性。

在控件的排放位置使用下面的代码既可以显示该控件了:

<PetShop:Cart id="ctlCart" runat="server" allowedit="false" />

在上面的代码中allowedit一项是向实例化的ctlCart传递参数。我个人认为用户控件是一个很好的东西,可以简化web页面的开发模型又可以达到代码复用的目的,而且其开发非常简单,与aspx的开发基本是一样的。图1说明了设计模式下和运行时的用户控件。

ASP.NET的输出缓存

衡量高性能、可缩放的web应用程序最重要的一个指标就是缓存了。ASP.NET提供了高性能的web应用程序的缓存功能,ASP.NET 有三种可由 Web 应用程序使用的缓存:

·  输出缓存,它缓存请求所生成的动态响应。

·  片断缓存,它缓存请求所生成的响应的各部分。

·  数据缓存,它以编程方式缓存任意对象。为支持这种缓存,ASP.NET 提供了全功能的缓存引擎,使程序员能够轻松地在请求间保留数据。

页的输出缓存是非常有用的。在海量的访问站点中,有些页面的访问频率占了非常大的比重,即使对这些页使用输出缓存很少的时间,也会减轻系统不少的负担,因为后面对这些页面的请求将不在执行创建该页的代码

但是,这样显得不够灵活,页的请求可能的确是很多,然而在页面上我们缓存了所有的东西,无论是构造成本高还是构造成本低的部分。能否有一种可以缓存页的部分的数据呢?幸运的是ASP.NET提供了针对每个请求来创建或自定义该页的各部分。比如说我们可以对页面上构造成本很高的用户控件做片断缓存。ASP.NET 缓存支持文件和缓存键依赖项,使开发人员可以使缓存项依赖于外部文件或其他缓存项。此项技术可用于在项的基础数据源发生更改时使该项无效。 ASP.NET可以将这些项存储在 Web 服务器上或请求流中的其他软件上,例如代理服务器或浏览器。这可以使您避免重新创建满足先前请求的信息,特别是当在服务器上创建时要求大量处理器时间或其他资源的信息。

Petshop的页缓存设置

我们可以可通过使用低级别的 OutputCache API 或高级别的 @ OutputCache 指令来实现页的输出缓存。 启用输出缓存后,当发出对页的第一个 GET 请求时创建一个输出缓存项。随后的 GET HEAD 请求由该输出缓存项服务,直到该缓存请求过期。输出缓存还支持缓存的 GET POST 名称/值对的变体。

输出缓存遵循页的过期和有效性策略。如果某页位于输出缓存中,并且有一个过期策略标记指示该页自缓存起 60 分钟后过期,则在 60 分钟后将该页从输出缓存中移除。如果此后接收到另一个请求,则执行页代码,并且可以再次缓存该页。

下面的指令在响应时激活输出缓存:

<%@ OutputCache Duration="60" VaryByParam="none"%>

DurationVaryByParam是必选参数,前者标识过期时间,后者表示GET POST 名称/值对的字符串。如果不使用该属性,可是设置为none。在这里我们还要说明一个参数VaryByCustom,使用这个参数,我们可以自定义输出缓存要求的任意文本。除了在OutputCache指令里面申明该属性之外,我们还得在应用程序的 global.asax 文件的代码声明块中,重写 GetVaryByCustomString 方法来为自定义字符串指定输出缓存的行为。

举一列来说:

<%@ OutputCache VaryByParam="none" VaryByCustom="CategoryPageKey" Location="server" Duration="43200" %>

这里的VaryByCustom定义的为CategoryPageKey,那么在global.asax里面我们必须定义CategoryPageKey这个字符创输出缓存的行为,见下面代码。

public override string GetVaryByCustomString(HttpContext context, String arg) {

              string cacheKey = "";

              switch(arg) {

                   case "CategoryPageKey":

                       if (Request.IsAuthenticated == true) {

                            cacheKey = "QQQ" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];

                       }

                       else {

                            cacheKey = "AAA" + context.Request.QueryString["category_id"] + context.Request.QueryString["requestedPage"];

                       }

                       break;

                   case "SearchPageKey" :

                       if (Request.IsAuthenticated == true) {

                            cacheKey = "QQQ" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];

                       }

                       else {

                            cacheKey = "AAA" + context.Request.QueryString["search_text"] + context.Request.QueryString["requestedPage"];

                       }

                       break;

                   case "ProductPageKey" :

                       if (Request.IsAuthenticated == true) {

                            cacheKey = "QQQ" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];

                       }

                       else {

                                 cacheKey = "AAA" + context.Request.QueryString["name"] + context.Request.QueryString["product_id"] + context.Request.QueryString["requestedPage"];

                       }

                       break;

                   case "ProductDetailsPageKey" :

                       if (Request.IsAuthenticated == true) {

                            cacheKey = "QQQ" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];

                       }

                       else {

                            cacheKey = "AAA" + context.Request.QueryString["item_id"] + context.Request.QueryString["requestedPage"];

                       }

                       break;

                   case "UserID" :

                       if (Request.IsAuthenticated == true) {

                            cacheKey = "UserID_In";

                       }

                       else {

                            cacheKey = "UserID_Out";

                       }

                       break;

              }

              return cacheKey;

         }从上面对CategoryPageKey字符创所作的行为来看,当我们的请求页面中含有对特定的category_id的某一分页显示的数据页的请求时,将调用缓存(自然是已经缓存了该页)。下表列出了petshopweb应用程序的输出缓存设置。

ASP.NET WebForms

Cache setting

Duration

ControlHeader

<%@ OutputCache

         Duration="43200"         

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Default

<%@ OutputCache

         Duration="43200"

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Help

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="UserID" %>

12 hours

Category

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="CategoryPageKey " %>

12 hours

Product

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="ProductPageKey " %>

12 hours

ProductDetails

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="ProductDetailsPageKey " %>

12 hours

Search

<%@ OutputCache

         Duration="43200" 

         VaryByParam="none"

         VaryByCustom="SearchPageKey " %>

12 hours 显然petshopweb页面上部的ControlHeader是随着用户登陆的状态有关的,故其设置了VaryByCustom属性以来标识用户不同登陆状态的缓存版本。而Category页面由于可能被大量的访问,并且数据量很大,是十分有必要缓存的,但是由于数据的随机性很大,存在不同的版本,比如说是不同类别的Category,甚至不同的分页显示的数据页,在这里采用了VaryByCustom属性以缓存不同版本的页 Petshop片断缓存 :在前面我们提到ASP.NET可以提供页的局部数据的缓存,通常是一些构造代价较大的部分,诸如用户控件。在petshop里面,大量使用了用户控件(尤其是.NET示例程序Duwamish7.0使用了更多的用户控件,那些页面简直就是控件的拼装),用户控件的缓存设置方法和aspx页的缓存设置方法基本相同,在这里我们不再列出。只有ControlHeader控件使用了缓存设置,见上表。