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