zoukankan      html  css  js  c++  java
  • PetShop的系统架构设计

      
    前言:PetShop是一个范例,微软用它来展示.Net企业系统开发的能力。业界有很多.NetJ2EE之争,很多数据是从微软的PetShopSunPetStore而来。这样的争论不可避免带有浓厚的商业色彩,对于我们开发者而言,没有必要过多关注。然而PetShop随着版本号的不断更新,至如今基于.Net 2.0PetShop4.0为止,整个设计逐渐变得成熟而优雅,却又非常多能够借鉴之处。PetShop是一个小型的项目,系统架构与代码都比較简单,却也凸现了很多颇有价值的设计与开发理念。本系列试图对PetShop作一个全方位的解剖,根据的代码是PetShop4.0,能够从链接http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp中获得。
    一、PetShop的系统架构设计
    在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据訪问层、业务逻辑层(又或成为领域层)、表示层,如图所看到的:

    图一:三层的分层式结构
    数据訪问层:有时候也称为是持久层,其功能主要是负责数据库的訪问。简单的说法就是实现对数据表的Select,Insert,Update,Delete的操作。假设要添加ORM的元素,那么就会包含对象和数据表之间的mapping,以及对象实体的持久化。在PetShop的数据訪问层中,并没有使用ORM,从而导致了代码量的添加,能够看作是整个设计实现中的一大败笔。
    业务逻辑层:是整个系统的核心,它与这个系统的业务(领域)有关。以PetShop为例,业务逻辑层的相关设计,均和网上宠物店特有的逻辑相关,比如查询宠物,下订单,加入宠物到购物车等等。假设涉及到数据库的訪问,则调用数据訪问层。
    表示层:是系统的UI部分,负责使用者与整个系统的交互。在这一层中,理想的状态是不应包括系统的业务逻辑。表示层中的逻辑代码,仅与界面元素有关。在PetShop中,是利用ASP.Net来设计的,因此包括了很多Web控件和相关逻辑。
    分层式结构到底其优势何在?Martin Fowler在《Patterns of Enterprise Application Architecture》一书中给出了答案:
    1、开发者能够仅仅关注整个结构中的当中某一层;
    2、能够非常easy的用新的实现来替换原有层次的实现;
    3、能够减少层与层之间的依赖;
    4、有利于标准化;
    5、利于各层逻辑的复用。
    概括来说,分层式设计能够达至例如以下目的:分散关注、松散耦合、逻辑复用、标准定义。
    一个好的分层式结构,能够使得开发者的分工更加明白。一旦定义好各层次之间的接口,负责不同逻辑设计的开发者就能够分散关注,齐头并进。比如UI人员仅仅需考虑用户界面的体验与操作,领域的设计人员能够仅关注业务逻辑的设计,而数据库设计人员也不必为繁琐的用户交互而头疼了。每一个开发者的任务得到了确认,开发进度就能够迅速的提高。
    松散耦合的优点是显而易见的。假设一个系统没有分层,那么各自的逻辑都紧紧纠缠在一起,彼此间相互依赖,谁都是不可替换的。一旦发生改变,则牵一发而动全身,对项目的影响极为严重。减少层与层间的依赖性,既能够良好地保证未来的可扩展,在复用性上也是优势明显。每一个功能模块一旦定义好统一的接口,就能够被各个模块所调用,而不用为同样的功能进行反复地开发。
    进行好的分层式结构设计,标准也是不可缺少的。仅仅有在一定程度的标准化基础上,这个系统才是可扩展的,可替换的。而层与层之间的通信也必定保证了接口的标准化。
    “金无足赤,人无完人”,分层式结构也不可避免具有一些缺陷:
    1、减少了系统的性能。这是不言而喻的。假设不採用分层式结构,非常多业务能够直接造訪数据库,以此获取对应的数据,现在却必须通过中间层来完毕。
    2、有时会导致级联的改动。这样的改动尤其体现在自上而下的方向。假设在表示层中须要添加一个功能,为保证其设计符合分层式结构,可能须要在对应的业务逻辑层和数据訪问层中都添加对应的代码。
    前面提到,PetShop的表示层是用ASP.Net设计的,也就是说,它应是一个BS系统。在.Net中,标准的BS分层式结构例如以下图所看到的:

    图二:.Net中标准的BS分层式结构
    随着PetShop版本号的更新,其分层式结构也在不断的完好,比如PetShop2.0,就没有採用标准的三层式结构,如图三:

    图三:PetShop 2.0的体系架构
    从图中我们能够看到,并没有明显的数据訪问层设计。这种设计尽管提高了数据訪问的性能,但也同一时候导致了业务逻辑层与数据訪问的职责混乱。一旦要求支持的数据库发生变化,或者须要改动数据訪问的逻辑,因为没有清晰的分层,会导致项目作大的改动。而随着硬件系统性能的提高,以及充分利用缓存、异步处理等机制,分层式结构所带来的性能影响差点儿能够忽略不计。
    PetShop3.0纠正了此前层次不明的问题,将数据訪问逻辑作为单独的一层独立出来:

    图四:PetShop 3.0的体系架构
    PetShop4.0基本上延续了3.0的结构,但在性能上作了一定的改进,引入了缓存和异步处理机制,同一时候又充分利用了ASP.Net 2.0的新功能MemberShip,因此PetShop4.0的系统架构图例如以下所看到的:

    图五:PetShop 4.0的体系架构
    比較3.0和4.0的系统架构图,其核心的内容并没有发生变化。在数据訪问层(DAL)中,仍然採用DAL Interface抽象出数据訪问逻辑,并以DAL Factory作为数据訪问层对象的工厂模块。对于DAL Interface而言,分别有支持MS-SQL的SQL Server DAL和支持Oracle的Oracle DAL具体实现。而Model模块则包括了数据实体对象。其具体的模块结构图例如以下所看到的:

    图六:数据訪问层的模块结构图
    能够看到,在数据訪问层中,全然採用了“面向接口编程”思想。抽象出来的IDAL模块,脱离了与详细数据库的依赖,从而使得整个数据訪问层利于数据库迁移。DALFactory模块专门管理DAL对象的创建,便于业务逻辑层訪问。SQLServerDAL和OracleDAL模块均实现IDAL模块的接口,当中包括的逻辑就是对数据库的Select,Insert,Update和Delete操作。由于数据库类型的不同,对数据库的操作也有所不同,代码也会因此有所差别。
    此外,抽象出来的IDAL模块,除了解除了向下的依赖之外,对于其上的业务逻辑层,相同仅存在弱依赖关系,例如以下图所看到的:

    图七:业务逻辑层的模块结构图
    图七中BLL是业务逻辑层的核心模块,它包括了整个系统的核心业务。在业务逻辑层中,不能直接訪问数据库,而必须通过数据訪问层。注意图中对数据訪问业务的调用,是通过接口模块IDAL来完毕的。既然与详细的数据訪问逻辑无关,则层与层之间的关系就是松散耦合的。假设此时须要改动数据訪问层的详细实现,仅仅要不涉及到IDAL的接口定义,那么业务逻辑层就不会受到不论什么影响。毕竟,详细实现的SQLServerDAL和OracalDAL根本就与业务逻辑层没有半点关系。
    由于在PetShop 4.0中引入了异步处理机制。插入订单的策略能够分为同步和异步,两者的插入策略明显不同,但对于调用者而言,插入订单的接口是全然一样的,所以PetShop 4.0中设计了IBLLStrategy模块。尽管在IBLLStrategy模块中,不过简单的IOrderStategy,但同一时候也给出了一个范例和信息,那就是在业务逻辑的处理中,假设存在业务操作的多样化,或者是今后可能的变化,均应利用抽象的原理。或者使用接口,或者使用抽象类,从而脱离对详细业务的依赖。不过在PetShop中,由于业务逻辑相对简单,这样的思想体现得不够明显。也正由于此,PetShop将核心的业务逻辑都放到了一个模块BLL中,并没有将详细的实现和抽象严格的依照模块分开。所以表示层和业务逻辑层之间的调用关系,其耦合度相对较高:

    图八:表示层的模块结构图
    在图五中,各个层次中还引入了辅助的模块,如数据訪问层的Messaging模块,是为异步插入订单的功能提供,採用了MSMQ(Microsoft Messaging Queue)技术。而表示层的CacheDependency则提供缓存功能。这些特殊的模块,我会在此后的文章中具体介绍。
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    二、PetShop数据訪问层之数据库訪问设计
    在系列一中,我从总体上分析了PetShop的架构设计,并提及了分层的概念。从本部分開始,我将依次对各层进行代码级的分析,以求获得更加仔细而深入的理解。在PetShop 4.0中,因为引入了ASP.Net 2.0的一些新特色,所以数据层的内容也更加的广泛和复杂,包含:数据库訪问、Messaging、MemberShip、Profile四部分。在系列二中,我将介绍有关数据库訪问的设计。
    在PetShop中,系统须要处理的数据库对象分为两类:一是数据实体,相应数据库中相应的数据表。它们没有行为,仅用于表现对象的数据。这些实体类都被放到Model程序集中,比如数据表Order相应的实体类OrderInfo,其类图例如以下: 
    这些对象并不具有持久化的功能,简单地说,它们是作为数据的载体,便于业务逻辑针对相应数据表进行读/写操作。尽管这些类的属性分别映射了数据表的列,而每个对象实例也恰恰相应于数据表的每一行,但这些实体类却并不具备相应的数据库訪问能力。
    因为数据訪问层和业务逻辑层都将对这些数据实体进行操作,因此程序集Model会被这两层的模块所引用。
    第二类数据库对象则是数据的业务逻辑对象。这里所指的业务逻辑,并不是业务逻辑层意义上的领域(domain)业务逻辑(从这个意义上,我更倾向于将业务逻辑层称为“领域逻辑层”),一般意义上说,这些业务逻辑即为主要的数据库操作,包含Select,Insert,Update和Delete。因为这些业务逻辑对象,仅具有行为而与数据无关,因此它们均被抽象为一个单独的接口模块IDAL,比如数据表Order相应的接口IOrder: 
    将数据实体与相关的数据库操作分离出来,符合面向对象的精神。首先,它体现了“职责分离”的原则。将数据实体与其行为分开,使得两者之间依赖减弱,当数据行为发生改变时,并不影响Model模块中的数据实体对象,避免了因一个类职责过多、过大,从而导致该类的引用者发生“灾难性”的影响。其次,它体现了“抽象”的精神,或者说是“面向接口编程”的最佳体现。抽象的接口模块IDAL,与详细的数据库訪问实现全然隔离。这样的与实现无关的设计,保证了系统的可扩展性,同一时候也保证了数据库的可移植性。在PetShop中,能够支持SQL Server和Oracle,那么它们详细的实现就分别放在两个不同的模块SQLServerDAL、OracleDAL中。
    以Order为例,在SQLServerDAL、OracleDAL两个模块中,有不同的实现,但它们同一时候又都实现了IOrder接口,如图: 
    从数据库的实现来看,PetShop体现出了没有ORM框架的臃肿与丑陋。因为要对数据表进行Insert和Select操作,以SQL Server为例,就使用了SqlCommand,SqlParameter,SqlDataReader等对象,以完毕这些操作。尤其复杂的是Parameter的传递,在PetShop中,使用了大量的字符串常量来保存參数的名称。此外,PetShop还专门为SQL Server和Oracle提供了抽象的Helper类,包装了一些经常使用的操作,如ExecuteNonQuery、ExecuteReader等方法。
    在没有ORM的情况下,使用Helper类是一个比較好的策略,利用它来完毕数据库基本操作的封装,能够降低非常多和数据库操作有关的代码,这体现了对象复用的原则。PetShop将这些Helper类统一放到DBUtility模块中,不同数据库的Helper类暴露的方法基本同样,仅仅除了一些特殊的要求,比如Oracle中处理bool类型的方式就和SQL Server不同,从而专门提供了OraBit和OraBool方法。此外,Helper类中的方法均为static方法,以利于调用。OracleHelper的类图例如以下: 
    对于数据訪问层来说,最头疼的是SQL语句的处理。在早期的CS结构中,因为未採用三层式架构设计,数据訪问层和业务逻辑层是紧密糅合在一起的,因此,SQL语句遍布与系统的每个角落。这给程序的维护带来极大的困难。此外,因为Oracle使用的是PL-SQL,而SQL Server和Sybase等使用的是T-SQL,两者尽管都遵循了标准SQL的语法,但在非常多细节上仍有差别,假设将SQL语句大量的使用到程序中,无疑为可能的数据库移植也带来了困难。
    最好的方法是採用存储过程。这样的方法使得程序更加整洁,此外,因为存储过程能够以数据库脚本的形式存在,也便于移植和改动。但这样的方式仍然有缺陷。一是存储过程的測试相对困难。尽管有对应的调试工具,但比起对代码的调试而言,仍然比較复杂且不方便。二是对系统的更新带来障碍。假设数据库訪问是由程序完毕,在.Net平台下,我们仅须要在改动程序后,将又一次编译的程序集xcopy到部署的server上就可以。假设使用了存储过程,出于安全的考虑,必须有专门的DBA又一次执行存储过程的脚本,部署的方式受到了限制。
    我以前在一个项目中,利用一个专门的表来存放SQL语句。如要使用相关的SQL语句,就利用keyword搜索获得相应语句。这样的做法近似于存储过程的调用,但却避免了部署上的问题。然而这样的方式却在性能上无法得到保证。它仅适合于SQL语句较少的场景。只是,利用良好的设计,我们能够为各种业务提供不同的表来存放SQL语句。相同的道理,这些SQL语句也能够存放到XML文件里,更有利于系统的扩展或改动。只是前提是,我们须要为它提供专门的SQL语句管理工具。
    SQL语句的使用无法避免,怎样更好的应用SQL语句也无定论,但有一个原则值得我们遵守,就是“应该尽量让SQL语句尽存在于数据訪问层的详细实现中”。
    当然,假设应用ORM,那么一切就变得不同了。由于ORM框架已经为数据訪问提供了主要的Select,Insert,Update和Delete操作了。比如在NHibernate中,我们能够直接调用ISession对象的Save方法,来Insert(或者说是Create)一个数据实体对象:
    public void Insert(OrderInfo order)
    {
        ISession s = Sessions.GetSession();
        ITransaction trans = null;
        try
        {
        trans = s.BeginTransaction();
          s.Save( order);
          trans.Commit();
        }
        finally
        {
          s.Close();
        }
    }
    没有SQL语句,也没有那些烦人的Parameters,甚至不须要专门去考虑事务。此外,这种设计,也是与数据库无关的,NHibernate能够通过Dialect(方言)的机制支持不同的数据库。唯一要做的是,我们须要为OrderInfo定义hbm文件。
    当然,ORM框架并不是是万能的,面对纷繁复杂的业务逻辑,它并不能全然消灭SQL语句,以及替代复杂的数据库訪问逻辑,但它却非常好的体现了“80/20(或90/10)法则”(也被称为“帕累托法则”),也就是说:花比較少(10%-20%)的力气就能够解决大部分(80%-90%)的问题,而要解决剩下的少部分问题则须要多得多的努力。至少,那些在数据訪问层中占领了绝大部分的CRUD操作,通过利用ORM框架,我们就仅须要付出极少数时间和精力来解决它们了。这无疑缩短了整个项目开发的周期。
    还是回到对PetShop的讨论上来。如今我们已经有了数据实体,数据对象的抽象接口和实现,能够说有关数据库訪问的主体就已经完毕了。留待我们的还有两个问题须要解决:
    1、数据对象创建的管理
    2、利于数据库的移植
    在PetShop中,要创建的数据对象包含Order,Product,Category,Inventory,Item。在前面的设计中,这些对象已经被抽象为相应的接口,而事实上现则依据数据库的不同而有所不同。也就是说,创建的对象有多种类别,而每种类别又有不同的实现,这是典型的抽象工厂模式的应用场景。而上面所述的两个问题,也都能够通过抽象工厂模式来解决。标准的抽象工厂模式类图例如以下: 
    比如,创建SQL Server的Order对象例如以下:
    PetShopFactory factory = new SQLServerFactory();
    IOrder = factory.CreateOrder();
    要考虑到数据库的可移植性,则factory必须作为一个全局变量,并在主程序执行时被实例化。但这种设计尽管已经达到了“封装变化”的目的,但在创建PetShopFactory对象时,仍不可避免的出现了详细的类SQLServerFactory,也即是说,程序在这个层面上产生了与SQLServerFactory的强依赖。一旦整个系统要求支持Oracle,那么还须要改动这行代码为:
    PetShopFactory factory = new OracleFactory();
    改动代码的这样的行为显然是不可接受的。解决的办法是“依赖注入”。“依赖注入”的功能一般是用专门的IoC容器提供的,在Java平台下,这样的容器包含Spring,PicoContainer等。而在.Net平台下,最常见的则是Spring.Net。只是,在PetShop系统中,并不须要专门的容器来实现“依赖注入”,简单的做法还是利用配置文件和反射功能来实现。也就是说,我们能够在web.config文件里,配置好详细的Factory对象的完整的类名。然而,当我们利用配置文件和反射功能时,详细工厂的创建就显得有些“画蛇添足”了,我们全然能够在配置文件里,直接指向详细的数据库对象实现类,比如PetShop.SQLServerDAL.IOrder。那么,抽象工厂模式中的相关工厂就能够简化为一个工厂类了,所以我将这样的模式称之为“具有简单工厂特质的抽象工厂模式”,其类图例如以下: 
    DataAccess类全然代替了前面创建的工厂类体系,它是一个sealed类,当中创建各种数据对象的方法,均为静态方法。之所以能用这个类达到抽象工厂的目的,是由于配置文件和反射的运用,例如以下的代码片断所看到的:
    public sealed class DataAccess
    {
     // Look up the DAL implementation we should be using
        private static readonly string path = ConfigurationManager.AppSettings[”WebDAL”];
        private static readonly string orderPath = ConfigurationManager.AppSettings[”OrdersDAL”];
     public static PetShop.IDAL.IOrder CreateOrder()
     {
             string className = orderPath + “.Order”;
             return (PetShop.IDAL.IOrder)Assembly.Load(orderPath).CreateInstance(className);
        }
    }
    在PetShop中,这样的依赖配置文件和反射创建对象的方式极其常见,包含IBLLStategy、CacheDependencyFactory等等。这些实现逻辑散布于整个PetShop系统中,在我看来,是能够在此基础上进行重构的。也就是说,我们能够为整个系统提供相似于“Service Locator”的实现:
    public static class ServiceLocator
    {
     private static readonly string dalPath = ConfigurationManager.AppSettings[”WebDAL”];
        private static readonly string orderPath = ConfigurationManager.AppSettings[”OrdersDAL”];
     //……
     private static readonly string orderStategyPath = ConfigurationManager.AppSettings[”OrderStrategyAssembly”];
     public static object LocateDALObject(string className)
     {
      string fullPath = dalPath + “.” + className;
      return Assembly.Load(dalPath).CreateInstance(fullPath);
     }
    public static object LocateDALOrderObject(string className)
     {
      string fullPath = orderPath + “.” + className;
      return Assembly.Load(orderPath).CreateInstance(fullPath);
     }
    public static object LocateOrderStrategyObject(string className)
     {
      string fullPath = orderStategyPath + “.” + className;
      return Assembly.Load(orderStategyPath).CreateInstance(fullPath);
     }
     //……
    }
    那么和所谓“依赖注入”相关的代码都能够利用ServiceLocator来完毕。比如类DataAccess就能够简化为:
    public sealed class DataAccess
    {
     public static PetShop.IDAL.IOrder CreateOrder()
     {
             return (PetShop.IDAL.IOrder)ServiceLocator. LocateDALOrderObject(”Order”);
        }
    }
    通过ServiceLocator,将全部与配置文件相关的namespace值统一管理起来,这有利于各种动态创建对象的管理和未来的维护
     
  • 相关阅读:
    hdu 4614 线段树 二分
    cf 1066d 思维 二分
    lca 最大生成树 逆向思维 2018 徐州赛区网络预赛j
    rmq学习
    hdu 5692 dfs序 线段树
    dfs序介绍
    poj 3321 dfs序 树状数组 前向星
    cf 1060d 思维贪心
    【PAT甲级】1126 Eulerian Path (25分)
    【PAT甲级】1125 Chain the Ropes (25分)
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/4279347.html
Copyright © 2011-2022 走看看