最近在研究適合團隊開發的web架構解決方案,該架構即要適合分工協作又要有一定擴充性,適合不同的資料庫需要,因此我查閱了一些資料,初步構想出了一套架構,請各位多多指教。
問題由來
最近在研究適合團隊開發的web架構解決方案,該架構即要適合分工協作又要有一定擴充性,適合不同的資料庫需要,因此我查閱了一些資料,初步構想出了一套架構,請各位多多指教。
探索
web開發架構最經典莫過於三層架構,展示層、邏輯層、資料處理層。
資料訪問層:其功能主要是負責資料庫的訪問。
商務邏輯層:是整個系統的核心,它與這個系統的業務(領域)有關。
展示層:是系統的UI部分,負責使用者與整個系統的互動。理想的狀態是展示層不應包括系統的商務邏輯。
這些是經典的解釋,如果要適合不同的資料庫則需要加入原廠模式,裡面用面向介面的方式進行多態調用。是不是這有點像petshop了。所以架構的初步設想是這樣:
下面以擷取使用者資訊為例,簡述這個架構的流程:(以下為類似petshop的經典做法,瞭解的可以略過)
step 1 、首先我們應該建立項目所需的實體模型,在這裡建立使用者資訊的實體模式,UserInfo.cs。該類儲存在Model項目裡。
step 2 、我們再將項目的單元功能寫到相關的介面中,這裡以擷取使用者資訊功能為例。在IDAL項目裡建立IUser介面。
//根據使用者ID擷取使用者資訊
UserInfo GetUserById(int userId);
step 3、完成了介面,我們就要實現它,現在我們用sqlserver、oracle兩種資料庫訪問方式來實現它。以下是SqlserverDAL中User類對介面的現實:
public class User:IUser
{
public UserInfo GetUserById(int userId)
{
//實現操作
}
}
OracleDAL中現實方式類似。。。。
step 4、在此資料庫訪問層應該就基本寫好了,下面應該給邏輯層調用了,但是兩種實現方式怎麼調用呢,或者說怎麼有選擇的調用它呢,petshop是這樣處理的,在DALFactory中的DataAccess類,利用反射載入程式集從而執行個體化所需要的類:
private static readonly string path = ConfigurationManager.AppSettings["WebDAL"];
public static IUser CreateUser()
{
string className = path + ".User";
return (IUser)Assembly.Load(path).CreateInstance(className);
}
至於要選擇哪個資料庫訪問層,在設定檔裡配置一下WebDAL。如:<add key="WebDAL" value="SQLServerDAL"/>。
這樣就基本解決了邏輯層和資料訪問層的耦合。
step 5、下面就該寫邏輯層了,在BLL裡面建立User.cs類。大致如下:
public class User
{
private static readonly IUser dal = DALFactory.DataAccess.CreateUser();
public UserInfo GetUserInfo(int userId) {
return dal.GetUserById(userId);
}
}
是不是覺得BLL毫無意義,因為它只是對資料訪問層方法的簡單調用,但並不是這樣的,這裡只有一個簡單的案例,在實際項目中一個BLL裡面處理的可能是一個非常複雜的邏輯,而這個複雜邏輯的結果才提供給展示層顯示。
step 6、最後是展示層,好像沒什麼可說的,把從BLL取出來的資料繫結到你的頁面就行了。
以上是仿petshop的架構設計,看起來沒什麼質疑的地方,畢竟是微軟的經典案例。你可能抱怨的地方有兩點,一是層是不是有點多,關係過於複雜;二如果我需要改變或增加一個資料庫欄位,那不是會很痛苦,因為要節聯修改。這兩個問題,我都沒辦法解決,一如果說過於層過於多而繁瑣,那麼下面我寫的好像更為複雜,原諒。。。。二、鄙人覺得凡是分層開發,只要以資料庫欄位為依據的建立實體模型,都會存在節聯修改的問題。除非全部用DataTable,那麼在BLL、展示層調用的時候並不知道DataTable到底裝有什麼,這樣無疑更加了調用的不便利。關於減少節聯修改的問題,如有解決方案的請指教。
對以上架構的修改
我重點分析了以上架構的資料訪問工廠的設計部分,即DALFactory中的DataAccess類。在此類中,實現了對不同資料庫訪問層的調用
。但如果現在有一個項目,裡面有sqlserver又有oracle的現實,我們是不是要這樣做:
private static readonly string path = ConfigurationManager.AppSettings["WebDAL"];//對sqlserver資料庫訪問層的調用
private static readonly string path2 = ConfigurationManager.AppSettings["WebDAL2"];//對oracel資料庫訪問層的調用
public static IUser CreateUser()
{
string className = path + ".User";
return (IUser)Assembly.Load(path).CreateInstance(className);
}
public static IOrder CreateOrder()
{
string className = path2 + ".Order";
return (IUser)Assembly.Load(path2).CreateInstance(className);
}
如果要建立其他的訪問類,我們還要寫CreateProduct(),CreateArticle,CreateMenu。。。。。那麼這樣的類會很繁瑣,我們能不能
只做一個方法,其他的工作只需要開發人員通過設定檔來完成呢。我的解決方案有兩個:
一、Spring.net
這個東西就是專門用來解耦合的,我們將它的相關程式集載入到DALFactory中,於是在DataAccess中,我們可以做:
private static readonly string configPath = HttpContext.Current.Request.PhysicalApplicationPath +
ConfigurationManager.AppSettings["objectconfig"];//這是spring.net的對象設定檔在伺服器上的物理位置
public static T CreateObject<T>()
{
IResource rs = new FileSystemResource(configPath);
IObjectFactory factory = new XmlObjectFactory(rs);
string id = typeof(T).FullName;
return (T)factory.GetObject(id);
}
這裡我們傳入一個泛型,讓spring.net在它的對象設定檔裡面找到該類型的程式集並載入,建立出對應的對象。objectconfig檔案
大致如下:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net
http://www.springframework.net/xsd/spring-objects.xsd">
<object id="IDAL.IUser" type="SQLServerDAL.Function"></object>
</objects>
這樣在BLL 就這樣調用
private static readonly IUser dal = EtourAF.Shared.DALFactory.DataAccess.CreateObject<IUser>();
這樣開發人員如果要加入一個對象就在object-config中加一段相關配置就行了。嘿嘿,這就變成了petshop+spring.net了,YY無極限。。。。
二、也是用反射
這裡我們只是用了一個索引值對的方式,照例在設定檔裡配置相應的介面和對象,只是我們把他配置到了web.config當中:
<add key="IDAL.IUser" value="SQLServerDAL.Function" />
在DataAccess中,我們就這樣寫:
public static T CreateObject<T>()
{
string interfaceFullName = typeof(T).FullName;
string className = ConfigurationManager.AppSettings[interfaceFullName];
string nameSpace = className.Substring(0, className.LastIndexOf("."));
return (T)Assembly.Load(nameSpace).CreateInstance(className);
}
可能有人說
string nameSpace = className.Substring(0, className.LastIndexOf("."));
這裡這個截取是不是覺得有點硬,我現在也只想到這個辦法,但絕對不會有問題的。
好了,這些大概就是鄙人這兩天的有些收穫,請指教。