亂用三層的項目
現在三層太流行了,我想至少50%稍微有些規模的項目都是采用三層.本人也開發了幾 個三層方面的項目,總算見識了不理解三層就開發三層帶來的惡果.
什麼是稍具規模的項目,以我開發的項目為例吧,(借此把開發過的項目宣傳一 下,sorry)
如www.ungou.com/ http://www.yinggou.com/ http://www.doocn.com/
說真的,我無意宣傳,也沒有這個臉,因為這三個項目都算是三層方面的失敗者(因為我 不是項目中的老大),帶來的後果我不說大家也知道:混亂,維護難.
三層新觀點
數據訪問層
1.數據訪問層不應該有事務,應該只是很純的增,刪,改,查詢,是否存在等等比較通用的 數據訪問方法.
2.業務需求的變化不應該造成該層方法的修改(除非是增加字段等等和表有關聯的需求 ) ,判斷是否合格的數據訪問層可以 使用這條規則.
3.數據訪問層中的方法對於業務層來說是一個個很純的小零件(或者說積木吧),舉例說 吧,一個添加用戶的方法中,就只是添加用戶,不會再向日志表插入操作日志.這樣做的好處 是方法職責清晰,穩定(能適應需求變化,一經代碼生成後,很少再做修改,就叫做穩定)
4.數據訪問層的方法中並不是說就完全沒有事務參與,經常是要先判斷當前線程上下文 是否有事務存在,有事務存在就按事務執行,沒有事務就自己創建連接對象.
業務層
1.最主要的任務是 按業務需求 組織調用數據訪問層中的方法,就好像搭積木一樣,搭 積木可以有很多花樣,正如業務需求也是一樣花樣很多.具體舉例說:在業務層中添加用戶 方法中可能是這樣:
開始事務
插入用戶
插入操作日志
提交事務
2.大部分業務需求的變化應只要修改業務層中的代碼即可,比如上面添加用戶的需求變 化了,要求添加用戶時能分配角色,上述方法就變成是這樣:
開始事務
插入用戶
插入用戶角色表(用戶id)
插入操作日志
提交事務
表現層
暫不談
基於這種設計的數據層和業務層優點
1.由於數據訪問層的方法就像積木一樣,粒度很低,很通用,可以代碼生成,業務變化基 本上也不會修改改層代碼,很穩定很好.
2.業務層代碼很靈活,都是一些核心的業務代碼.代碼量比數據訪問層多很多.經常聽到 或看到很多人都說"三層代碼中的業務層就好像一個轉發器一樣,沒什麼代碼,把數據訪問 層的方法轉一下就完成任務了,好像是多余的一個層",這種說法在j2ee項目中一樣經常存在,雖然j2ee項目一向被世人認為大部分是比較規范化的,(net項目經常被 人說代碼很亂,很有地方特色,每個人有每個人的寫法,哈哈).
為什麼很多人覺得似乎業務層好像沒有做什麼重要的事,那是因為開發的人把業務搞到 數據訪問層了,把數據訪問方法搞大了,把事務控制搞到數據訪問層了.
那有什麼缺點呢:
1.事務放在業務層可能造成業務層不能適應多數據庫支持特性,因為有些數據庫是不支 持事務或事務保存點,但是這種機會是很小的,因為主流的適合稍具規模項目的數據庫一般 都支持
2.前面說了,數據訪問層中的方法一般還要判斷當前線程上下文中或方法參數中(比較 土的方式)是否有事務存在,然後再進行數據訪問操作,關鍵是:事務在業務層控制,事務還 要在數據訪問方法中被判斷,有點難.當然,目前有比較好的方式,可以用一些類似 spring.net框架來管理事務,以便層和層耦合性低點.
批批現有流行框架
pershop4: 完全沒有體現事務在業務層的價值.
discuzNT: 也完全不知道事務在業務層的好處,代碼生成工具基本上派不上用場,除了 生成實體類,業務變化經常業務和數據兩層都要改動.其實數據層是應該比較穩定的.
看看裡面的數據訪問方法也是先搞大再說,其實這些可以放到業務層
代碼摘抄:
public void MovingForumsPos(string currentfid, string targetfid, bool isaschildnode, string extname)
{
SqlConnection conn = new SqlConnection (DbHelper.ConnectionString);
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction ())
{
try
{
//取得當前論壇版塊的信息
DataRow dr = DbHelper.ExecuteDataset (trans, CommandType.Text, "SELECT TOP 1 * FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + currentfid).Tables [0].Rows[0];
//取得目標論壇版塊的信息
DataRow targetdr = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 * FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetfid).Tables [0].Rows[0];
//當前論壇版塊帶子版塊時
if (DbHelper.ExecuteDataset (CommandType.Text, "SELECT TOP 1 FID FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [parentid]=" + currentfid).Tables[0].Rows.Count > 0)
{
#region
string sqlstring = "";
if (isaschildnode) //作為論壇 子版塊插入
{
//讓位於當前論壇版塊( 分類)顯示順序之後的論壇版塊全部加1(為新加入的論壇版塊讓位結果)
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0}",
Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//更新當前論壇版塊的 相關信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[displayorder]='{2}' WHERE [fid]={0}", currentfid, targetdr ["fid"].ToString(), Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString().Trim()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
else //作為同級論壇版塊,在目 標論壇版塊之前插入
{
//讓位於包括當前論壇 版塊顯示順序之後的論壇版塊全部加1(為新加入的論壇版塊讓位結果)
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0} OR [fid]= {1}",
Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString())),
targetdr["fid"].ToString());
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//更新當前論壇版塊的 相關信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[displayorder]='{2}' WHERE [fid]={0}", currentfid, targetdr["parentid"].ToString(), Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString().Trim())));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
//更新由於上述操作所影響的版 塊數和帖子數
if ((dr["topics"].ToString() != "0") && (Convert.ToInt32(dr["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr ["posts"].ToString()) > 0))
{
if (dr ["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]-" + dr ["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString().Trim() + ")");
}
if (targetdr ["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]+" + dr ["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString().Trim() + ")");
}
}
#endregion
}
else //當前論壇版塊不帶子版
{
#region
//設置舊的父一級的子論壇數
DbHelper.ExecuteDataset (trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]-1 WHERE [fid]=" + dr["parentid"].ToString ());
//讓位於當前節點顯示順序之後 的節點全部減1 [起到刪除節點的效果]
if (isaschildnode) //作為子論 壇版塊插入
{
//更新相應的被影響的 版塊數和帖子數
if ((dr ["topics"].ToString() != "0") && (Convert.ToInt32(dr ["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr["posts"].ToString()) > 0))
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]-" + dr ["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString() + ")");
if (targetdr ["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]+" + dr ["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString() + "," + targetfid + ")");
}
}
//讓位於當前論壇版塊 顯示順序之後的論壇版塊全部加1(為新加入的論壇版塊讓位結果)
string sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0}",
Convert.ToString(Convert.ToInt32 (targetdr["displayorder"].ToString()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//設置新的父一級的子 論壇數
DbHelper.ExecuteDataset(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]+1 WHERE [fid]=" + targetfid);
string parentidlist = null;
if (targetdr ["parentidlist"].ToString().Trim() == "0")
{
parentidlist = targetfid;
}
else
{
parentidlist = targetdr["parentidlist"].ToString().Trim() + "," + targetfid;
}
//更新當前論壇版塊的 相關信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[layer]='{2}',[pathlist]='{3}', [parentidlist]='{4}', [displayorder]='{5}' WHERE [fid]={0}",
currentfid,
targetdr["fid"].ToString(),
Convert.ToString(Convert.ToInt32(targetdr ["layer"].ToString()) + 1),
targetdr["pathlist"].ToString().Trim() + "<a href="showforum-" + currentfid + extname + "">" + dr["name"].ToString ().Trim().Replace("'","''") + "</a>",
parentidlist,
Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString().Trim()) + 1)
);
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
else //作為同級論壇版塊,在目 標論壇版塊之前插入
{
//更新相應的被影響的 版塊數和帖子數
if ((dr ["topics"].ToString() != "0") && (Convert.ToInt32(dr ["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr["posts"].ToString()) > 0))
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE " + BaseConfigs.GetTablePrefix + "forums SET [topics]=[topics]-" + dr ["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString() + ")");
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE " + BaseConfigs.GetTablePrefix + "forums SET [topics]=[topics]+" + dr ["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString() + ")");
}
//讓位於包括當前論壇 版塊顯示順序之後的論壇版塊全部加1(為新加入的論壇版塊讓位結果)
string sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0} OR [fid]= {1}",
Convert.ToString(Convert.ToInt32 (targetdr["displayorder"].ToString()) + 1),
targetdr["fid"].ToString());
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//設置新的父一級的子 論壇數
DbHelper.ExecuteDataset(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]+1 WHERE [fid]=" + targetdr["parentid"].ToString());
string parentpathlist = "";
DataTable dt = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 [pathlist] FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetdr ["parentid"].ToString()).Tables[0];
if (dt.Rows.Count > 0)
{
parentpathlist = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 [pathlist] FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetdr["parentid"].ToString()).Tables[0].Rows[0][0].ToString().Trim();
}
//更新當前論壇版塊的 相關信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[layer]='{2}',[pathlist]='{3}', [parentidlist]='{4}', [displayorder]='{5}' WHERE [fid]={0}",
currentfid,
targetdr["parentid"].ToString(),
Convert.ToInt32(targetdr["layer"].ToString ()),
parentpathlist + "<a href="showforum-" + currentfid + extname + "">" + dr["name"].ToString().Trim() + "</a>",
targetdr["parentidlist"].ToString().Trim(),
Convert.ToString(Convert.ToInt32(targetdr ["displayorder"].ToString().Trim()))
);
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
#endregion
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw ex;
}
conn.Close();
}
}
3.動軟代碼生成工具生成的三層工廠代碼
也是沒有考慮把事務放在業務層,生成的數據訪問方法經常是要再重寫,業務層方法倒 是變得相對穩定,作用變小了,其實相對穩定的是數據訪問層,不穩定的應該是業務層.
其實造成目前現有流行框架也會發生這種錯誤是沒有良好的業務層事務控制機制,或者 說比較優美的實現,當然使用一些類似spring.net框架的除外
本人知識還是比較薄弱的,歡迎釋放你的見解,哈哈