利用General框架進行三層架構(gòu)開發(fā)
三層架構(gòu)是企業(yè)信息管理系統(tǒng)中一種比較流行的架構(gòu)方式,如大家所知,三層架構(gòu)將信息系統(tǒng)分為數(shù)據(jù)訪問層(DAL)、業(yè)務(wù)邏輯層(BLL)、界面表示層(UI)三部分,三層架構(gòu)的好處是根據(jù)系統(tǒng)中代碼所處的層次將系統(tǒng)拆開,而通過業(yè)務(wù)模型(Model)再進行連接,降低系統(tǒng)各層次之間的耦合度,提升程序開發(fā)和后期維護的容易度。
由 于三層架構(gòu)是根據(jù)由上至下的層次進行分層,而不是根據(jù)功能、應(yīng)用領(lǐng)域進行分層,所以三層架構(gòu)在每一層的關(guān)注點并不相同,數(shù)據(jù)訪問層關(guān)注的是跟數(shù)據(jù)庫打交道 的部分,業(yè)務(wù)邏輯層關(guān)注的是業(yè)務(wù)邏輯處理部分,而界面表示層關(guān)注的是人機交互部分,所以三層架構(gòu)在一定程度上也體現(xiàn)出了系統(tǒng)開發(fā)的先后順序和分工。
本文將從我對三層架構(gòu)的理解上,利用General框架從頭開始打造一個信息管理系統(tǒng)的初步結(jié)構(gòu),以此來展示General框架在信息管理系統(tǒng)開發(fā)上的優(yōu)勢。由于不同的人對架構(gòu)的理解也不一樣,所以本文不強調(diào)架構(gòu)的正確性,只是出于簡化開發(fā)、方便編程的原則下提供一個三層架構(gòu)的樣本。本文中的示例工程為一個小型的商品庫存管理軟件,源代碼請查看General框架中的Sample.Market工程。
第一步、設(shè)計業(yè)務(wù)模型
由 于是示例工程,所以本文繞過需求分析過程,直接從業(yè)務(wù)模型設(shè)計開始。關(guān)于為何從業(yè)務(wù)模型開始,其實目前主要有兩種開發(fā)順序,即業(yè)務(wù)驅(qū)動設(shè)計和界面驅(qū)動設(shè) 計,業(yè)務(wù)驅(qū)動設(shè)計更強調(diào)業(yè)務(wù)作為系統(tǒng)的核心,所有編程工作都圍繞業(yè)務(wù)設(shè)計展開,而界面驅(qū)動設(shè)計認為界面是客戶、項目經(jīng)理、設(shè)計師、程序員最好的溝通工具, 所以一切以界面為首要確定目標。而我認為這兩種方式至于哪種更好,需要看實際情況,假如項目不是很大,而界面又容易確定或是客戶比較注重界面設(shè)計,那界面 驅(qū)動設(shè)計當然為優(yōu)選方案,可以先從界面入手,做出DEMO,等客戶滿意后再做業(yè)務(wù)流程的開發(fā)。反之,如果業(yè)務(wù)邏輯更為重要,當然是以業(yè)務(wù)驅(qū)動設(shè)計為優(yōu)選方案,而信息管理系統(tǒng)的開發(fā),大部分還是以業(yè)務(wù)為中心,所以除去需求分析之后的第一步也就以業(yè)務(wù)模型設(shè)計為先。
業(yè)務(wù)模型設(shè)計可以借助Excel、PowerDesinger等工具,先用Excel整理好業(yè)務(wù)模型比較核心的數(shù)據(jù)信息,然后通過PowerDesinger做出業(yè)務(wù)模型圖(即ER圖),再生成物理數(shù)據(jù)庫模型,再生成數(shù)據(jù)庫。本文的業(yè)務(wù)模型圖設(shè)計如下,并生成了對應(yīng)各種數(shù)據(jù)庫的物理數(shù)據(jù)庫模型,再生成了數(shù)據(jù)庫的建庫腳本,Acess通過腳本創(chuàng)建數(shù)據(jù)庫需要一定的小技巧,這個方法可以在百度中查到。
我通過PowerDesinger生成的建庫腳本,分別創(chuàng)建了Access、Sqlite、SqlServer2000、SqlServer2005、Oracle、MySql的數(shù)據(jù)庫,數(shù)據(jù)庫名稱都為Market。
#p#
第二步、生成實體模型
數(shù)據(jù)庫創(chuàng)建完成后,先用VisualStudio創(chuàng)建名為Sample.Market的解決方案,并創(chuàng)建Sample.Market.Logic(業(yè)務(wù)邏輯層)、Sample.Market.Model(實體模型庫)、Sample.Market.WinForm(WinForm界面表示層),大家發(fā)現(xiàn)為什么沒有創(chuàng)建數(shù)據(jù)訪問層呢,因為我是利用General框架進行開發(fā),而General框架支持多數(shù)據(jù)庫并且有ORM功能,所以數(shù)據(jù)訪問層就顯得不是必須的了,也可以將General.Data理解為通用的數(shù)據(jù)訪問層。但是從系統(tǒng)解耦和更針對性的多數(shù)據(jù)庫支持出發(fā),再增加一個系統(tǒng)內(nèi)的數(shù)據(jù)訪問層也有好處,但會帶來更多的編碼和更多的后期維護成本,其中利弊需要自己權(quán)衡。
工程創(chuàng)建完畢后,利用General代 碼生成器這個利器,我們就可以很快的一次性把實體模型生成出來,注意這個實體模型是從數(shù)據(jù)庫生成而來的,而實際上實體模型應(yīng)從業(yè)務(wù)模型而來,因為數(shù)據(jù)庫是 業(yè)務(wù)模型生成而來,而實體模型又是從數(shù)據(jù)庫生成而來,所以這三者就成了完全一致的,這樣在開發(fā)角度其實更方便實用,因為只要了解其中一者就可以對三者完全 了解。但問題是如果業(yè)務(wù)模型發(fā)生變化怎么辦,這個問題也困擾我很久,因為雖然有先進的工具做支持,而從業(yè)務(wù)模型生成物理數(shù)據(jù)模型,再修改數(shù)據(jù)庫結(jié)構(gòu),再從 數(shù)據(jù)庫生成實體類,依然是非常累人的一件事,直到目前我也沒有特別好的辦法解決這個問題,甚至曾想過制作一個從設(shè)計業(yè)務(wù)模型到生成數(shù)據(jù)庫再到生成實體類的 完整解決方案工具,但奈何工作量太大是我難以完成的,在此只能提出以下幾點供大家參考:
1)盡量減少業(yè)務(wù)模型的修改,前期設(shè)計要盡量完善,留足冗余字段,并告知負責業(yè)務(wù)調(diào)研同事修改的成本,修改盡量要求客戶簽字確認;
2)將數(shù)據(jù)庫和測試數(shù)據(jù)分開,比如通過SQL腳本錄入測試數(shù)據(jù),免得修改數(shù)據(jù)庫結(jié)構(gòu)造成測試數(shù)據(jù)丟失;
3)工具不是人,沒有人的智能,如果對數(shù)據(jù)庫結(jié)構(gòu)或?qū)嶓w類的某個地方修改了,而重新建庫或是重新生成實體類后又忘記復(fù)原,容易導(dǎo)致莫名其妙的BUG,所以盡量避免重新建庫或一次重新生成所有的實體類,而是小范圍修改;
4)如果實在改動太多,就拋棄業(yè)務(wù)模型,直接去數(shù)據(jù)庫修改吧,這樣減少了一大部分工作量,可以避免積勞成疾。
General代碼生成器的使用可以參考壓縮包內(nèi)的說明教程,生成實體的模板已經(jīng)包含在模板庫中,動手能力強的可以自己修改或制作符合自己習慣的模板。
#p#
第三步、創(chuàng)建界面表示層
制 作軟件界面其實是一項非常累人的工作,大概占了整個系統(tǒng)開發(fā)的一半強的時間,但是在界面開發(fā)上也有很多竅門,比如通過配置動態(tài)生成菜單、通過配置動態(tài)生成 表格列、表單自動生成自動收集填充、下拉框自動生成、搜索項自動生成并自動生成查詢語句、呈現(xiàn)器自動綁定字典等等,可以說是只有想不到?jīng)]有做不到,通過這 些技巧可以大大提高開發(fā)的效率,減少很多重復(fù)工作,在以后我會慢慢介紹這些技巧,本文中將介紹一個表單自動收集填充的技巧。
在General.WinForm.FormHelper和General.Web.WebHelper中都有一個名為CollectAndFill的方法,分別對應(yīng)窗體和網(wǎng)頁的收集和填充,可以對 Object、DataRow、控件集這三者中的任意兩者之間進行收集和填充工作,比如對表單控件收集值并賦值到實體,對實體收集值并賦值給表單,但前提是需要將表單的控件命名為與實體的屬性相同的名稱以進行對應(yīng)。使用方法如:
創(chuàng)建實體并收集表單值:
1 Goods good = new Goods();
2 FormHelper.CollectAndFill(this, good);
查詢實體并賦值給表單
1 Goods good = goodsLogic.GetGoodByCode(編號.Text);
2 FormHelper.CollectAndFill(good, this);
其實控件的收集填充工作是通過各種控件對應(yīng)的工具類來完成的,所以如果有收集或填充不到的控件類型,可以通過增加并注冊新的工具類來實現(xiàn),具體請參考源代碼。
第四步、創(chuàng)建業(yè)務(wù)邏輯層
最重要的業(yè)務(wù)邏輯卻放在最后,因為當業(yè)務(wù)模型和界面都確定之后,業(yè)務(wù)邏輯其實也跑不出圈了,只要根據(jù)需要對應(yīng)完成就行了,可能有人說是不是應(yīng)當先創(chuàng)建業(yè)務(wù)邏輯再制作界面,其實這樣業(yè)務(wù)邏輯的方法反而難以確定,還是以界面制作為優(yōu)先。
General代碼生成器也可以生成業(yè)務(wù)邏輯層的代碼,甚至說界面層的代碼也完全可以生成,如果項目代碼優(yōu)化的比較好,可以通過先生成再修改的辦法來節(jié)約很多時間,但大部分情況這個工作重復(fù)度并不高,所以不需要自動生成來完成。
創(chuàng)建業(yè)務(wù)邏輯類后,通過DataManager來進行數(shù)據(jù)庫訪問操作,比如下面這個通過ID獲取商品信息的方法:
- /// <summary>
- /// 通過ID獲取商品信息
- /// </summary>
- /// <param name="id">商品ID</param>
- /// <returns></returns>
- public Goods GetGoodByID(int id)
- {
- return DataManager.Default.Find<Goods>(id);
- }
通過一行代碼就可以完成;
再如商品入庫的方法:
- /// <summary>
- /// 商品入庫
- /// </summary>
- /// <param name="good">商品信息</param>
- /// <param name="stockDate">入庫日期</param>
- /// <param name="amount">入庫數(shù)量</param>
- /// <returns></returns>
- public ReturnCode StockInGoods(Goods good, DateTime stockDate, int amount)
- {
- Transaction tran = DataManager.Default.BeginTransaction();
- try
- {
- // 查詢是否有編號已存在的商品
- Goods tmpGood = DataManager.Default.FindFirst<Goods>("編號 = @bh", good.編號);
- if (tmpGood != null)
- {
- good.ID = tmpGood.ID;
- good.Attach();
- good.庫存數(shù)量 = tmpGood.庫存數(shù)量;
- good.庫存金額 = tmpGood.庫存金額;
- good.進貨數(shù)量 = tmpGood.進貨數(shù)量;
- good.進貨金額 = tmpGood.進貨金額;
- }
- else
- {
- good.Detach();
- }
- if (good.庫存數(shù)量 == null) good.庫存數(shù)量 = 0;
- if (good.庫存金額 == null) good.庫存金額 = 0;
- if (good.進價 == null) good.進價 = 0;
- if (good.進貨數(shù)量 == null) good.進貨數(shù)量 = 0;
- if (good.進貨金額 == null) good.進貨金額 = 0;
- // 調(diào)整庫存
- good.庫存數(shù)量 += amount;
- good.庫存金額 += amount * good.進價;
- good.進貨數(shù)量 += amount;
- good.進貨金額 += amount * good.進價;
- good.平均進價 = good.進貨金額 / good.進貨數(shù)量;
- DataManager.Default.Save(good);
- // 保存入庫記錄
- GoodsIn goodsIn = new GoodsIn();
- goodsIn.Goo_ID = good.ID;
- goodsIn.日期 = stockDate;
- goodsIn.編號 = good.編號;
- goodsIn.進價 = good.進價;
- goodsIn.零售價 = good.零售價;
- goodsIn.數(shù)量 = amount;
- goodsIn.金額 = amount * goodsIn.進價;
- goodsIn.生產(chǎn)日期 = good.生產(chǎn)日期;
- goodsIn.保質(zhì)期 = good.保質(zhì)期;
- goodsIn.到期日期 = good.到期日期;
- goodsIn.生產(chǎn)批號 = good.生產(chǎn)批號;
- goodsIn.供應(yīng)商 = good.供應(yīng)商;
- DataManager.Default.Save(goodsIn);
- tran.Commit();
- return ReturnCode.Successed;
- }
- catch
- {
- tran.Rollback();
- throw;
- }
- }
即便是對兩個表進行操作并且使用事務(wù),代碼依舊非常簡短。
由于各家數(shù)據(jù)庫的SQL都稍有差別,所以要做到兼容多種數(shù)據(jù)庫其實是非常困難的,General框架的數(shù)據(jù)庫操作是通過拼接SQL語句來完成的,沒有像EntityFramework的Linq或NHibernate的HQL這樣的自身查詢語言,所以要兼容多種數(shù)據(jù)庫是難以實現(xiàn)的,而這個示例程序能夠兼容Access、Sqlite、SqlServer、Oracle、MySql多種數(shù)據(jù)庫,主要是因為以下幾點:
1)General框架對各種數(shù)據(jù)庫有對應(yīng)的查詢生成器,同一個方法對不同數(shù)據(jù)庫有不同的SQL生成實現(xiàn);
2)General框架具有不同參數(shù)前綴替換能力,比如在Oracle中參數(shù)前綴是“:”而不是SqlServer的“@”,General框架會自動將作為通用前綴的“@”替換為Oracle的“:”以便兼容;
3)本示例中沒有使用一些特殊的SQL語句,各家的數(shù)據(jù)庫在常用的select、insert、update、delete語句上兼容的比較好。
最后,大家可以打開General框架中的示例程序查看具體的使用方法,里面還包含一個性能測試程序可以測試General框架的性能,相關(guān)的源碼下載請參看上一篇文章,歡迎大家評論。