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