PetShop4.0 系列之二

來源:互聯網
上載者:User

二、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到部署的伺服器上即可。如果使用了預存程序,出於安全的考慮,必須有專門的DBA重新運行預存程序的指令碼,部署的方式受到了限制。

我曾經在一個項目中,利用一個專門的表來存放SQL語句。如要使用相關的SQL語句,就利用關鍵字搜尋獲得對應語句。這種做法近似於預存程序的調用,但卻避免了部署上的問題。然而這種方式卻在效能上無法得到保證。它僅適合於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值統一管理起來,這有利於各種動態建立對象的管理和未來的維護。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.