隨著微軟.NET開發平台的推出,無疑給無數的VB開發人員帶來了福音,無繼承,醜陋的錯誤捕捉,無對象池,不能進行底層開發,這些都成為過去,隨之而來的是完全的面向OO特性,豐富的類庫,支援XML,當有人更鐘情於C#,C++開發人員更是對此不屑一顧,但軟體開發中更重要的是一種思想和原理,語言只是一種工具。
我有幸參與了一個基於三層架構的ASP.NET的項目,也有了些心得,下面就開發過程中遇到的一些問題談談自己的一點看法。以下是架構圖:
一、資料實體層(Entity)的實現
我們首先需要解決的是資料的表示方式的問題,在VB開發中,相信大家都遇到過如何有效構造資料實體的問題,單個類,集合類,集合類中如何操作單個類,如何用填充資料實體,這些問題解決起來都不是很容易,也有很多方法來實現,對於集合類的實現,可以用數組,可以用集合對象,也可以用字典對象,當然執行個體化資料實體也有很多方法,象最常見的原廠模式,這裡就不討論了,我在項目中是用TYPED-DATASET對象來作為資料實體,個人覺得有以下好處:
1. 資料繫結。可以直接和控制項綁定,尤其是網格控制項,在VB中是很困難的,除非增加個屬性來存放Recordset,這樣一來又要多增加方法來初始化Recordset,並且增加了執行個體化和封送處理的成本
2. 代碼自動產生。少寫很多代碼,尤其是當表中欄位很多的時候,並且自動提供了序列化功能
3. 集合類。Typed-Dataset本身就是個集合類,提供添加,刪除,修改,尋找單個類的方法
當然也有缺點,任何事物都無法十全十美,比如很難從中派生出子類,執行個體化成本高等。
在自動產生Typed-Dataset時,需要做些改動使Entity更好的符合OO的特性
A、自動產生的表對象和行對象是以DataTable和Row結尾的,這可以通過加如以下代碼來更改:在XML檔案中的<xml:schema 節點加入:
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:codegen="urn:schemas-microsoft-com:xml-msprop" 命名空間
B、重寫 <xs:choice maxOccurs="unbounded"> 節點後的<xs:element name="Tables”> 節點 <xs:element name="Tables" codegen:typedName="Table" codegen:typedPlural="Tables">
C、由於自動產生的程式碼遇到某個欄位為NULL時預設是報錯,如想不報錯,需在每個Element節點定義中加入: nillable="true" codegen:nullValue="_null" 或nillable="true" codegen:nullValue="false" 或nillable="true" codegen:nullValue="1900-01-01T00:00:00"
D、Entity的粒度。在考慮實體物件的設計時,“對象的粒度”是一個需要仔細考慮的問題,個人喜歡粗粒度的Entity,這樣可以減少與資料庫的往返次數,當然也不是包含所有的表,這樣效率不高,比如Order,只需要Order 和OrderDetail 表,Product,SalesPerson就不需要了,當然也可以用從dataset繼承的自訂類來作為Entity,但個人覺得一般情況下沒必要,畢竟MS的typed-dataset功能更多,而且實現起來也比較複雜。
二、 資料服務層的實現(Data Service)
使用MS的Data Access Application Block,效果還可以,大家可以到MS網站下載。
三、資料訪問層(Data Access)
所有的資料操作採用傳統的預存程序(SP)實現的,但由於Entity是個集合,包含多行,因此要迴圈調用SP以實現大量操作,個人建議提供兩個方法,一個是操作一行(常用),一個是操作多行(不常用)。
四、業務層(Business Logics)
需要完成的功能是各種商務規則和邏輯的實現,從SevicedComponent繼承,這是介面層唯一需要清楚瞭解的層,也是以後系統擴充和經常需要變更的地方,因此業務層的每個模組都有個基類,Module1BaseClass,例如單據模組,結構如下:
Abstract Class OrderBase Public Function AddNew() Public Function Delete Public Function GetOrderByID Public Function Update End class Public Class SaleOrder Inherits OrderBase …………… End Class Public Class PurchaseOrder Inherits OrderBase ……… End Class Public Class MorePurchaseOrder Inherits PurchaseOrder ……… End Class |
這樣一來業務層的擴充性就強了,這就是OO的好處啊,如果在VB中,就很難實現了,當然通過Interface也可以間接的實現,但不方便,畢竟VB6不是面向OO的語言,是面向Interface的。
五、Facade層
提供更高層次的商務邏輯處理,一般是當表現層的某個操作需要關連到多個模組時由這層提供一個統一的介面,但並不要求表現層必須調用這層來處理商務邏輯。
六、事務的處理(Transaction)
採用手工處理事務的方式,由於所有的業務層都是Serviced component,因此我們可以直接利用COM+中的事務功能,雖然Serviced component有點效能損耗,但在分布式的結構中還是會帶來很多方便的,我將所有的業務層的事務屬性設為Required,具體代碼如下:
<Description ("Contains methods for inserting and updating product records in the berp system."), Guid ("4FE02C4F-C8FA-497c-9A9E-EDA0877B772C"), Transaction (TransactionOption. Supported), Synchronization (SynchronizationOption.Required), JustInTimeActivation(True)> Public Class Product Public Function Add() As Boolean '………… If ContextUtil.IsInTransaction Then ContextUtil.SetComplete() Else ContextUtil.SetAbort() End If '………… End Function End Class |
系統中的其它層不需要作為Serviced component
七、異常的處理
徹底改變了VB6中的錯誤處理方式,這點我最喜歡,哈! 項目中的異常分為兩種,業務異常和系統異常:
1、業務Exception:
從ApplicationException中繼承,有一個總的Exception,然後各個模組的Exception都是從總的Exception中繼承,從而形成了一個階層:
ApplicationException ProjectException Module1Exception Module1Concret1Exception Module2Concret2Exception …………… Module2Exception …………… |
2、系統異常,報出的錯誤,包括資料庫報出的:
異常捕捉的原則是只捕捉需要的錯誤,因材在資料訪問層和業務層不需要Catch所有的錯誤,如下:
Public Function Methoda() IF …… THEN If ContextUtil。IsInTransaction Then ContextUtil.etAbort() End If Throw Module1Concret1Exception End If …… Catch 需要的具體的錯誤Module2Concret1Exception '這裡不需要 Catch ProjectException End Function |
為了便於調試,需要把系統異常記錄在記錄檔中,這裡用的MS提供的Exception Management Application Block 來實現的,具體的實現方式見MSDN,是:
http:www.icrosoft.om/downloads/details.spx?FamilyId=8CA8EB6E-6F4A-43DF-ADEB-8F22CA173E02&displaylang=en
在介面顯示錯誤資訊的時候為兩種顯示方式,一種是用來顯示業務異常的,一種介面是用來顯示系統異常的,代碼如下:
Public Sub Button_OnClick() Dim objblModule As 業務層 Try ObjblModul.osomething() Catch ProjectExcption '顯示一個定製的頁面() Catch Exception 'call Exception Management. Publish method to log the exception '顯示另一個定製的頁面,定製的頁面有將具體的錯誤資訊發送到administrator的功能,就象windows的錯誤頁面 End Try End Sub |
八、許可權控制
這是一個值得深入討論的問題,我採用的方式是用FORM認證的方法,具體的使用者資訊,許可權是放在資料庫中,並沒有整合WINDOWS的域認證,實現的時候用專門的層來執行許可權判斷,利用GeneralPrincipal 和GeneralIdentity對象 ,程式碼片段如下:
Public Function CheckRole(ByVal strRole As String) As Boolean Return privateUserPrincipal.IsInRole(strRole) End Function Private Sub InitPrincipal() Try privateUserIdentity = New GenericIdentity(privateUserName) privateUserPrincipal = New GenericPrincipal(privateUserIdentity, privateUserRoles) Catch e As Exception Throw New Exception("an error occurred setting credentials") End Try End Sub Private Sub SavePrincipal() Try If Not IsNothing(_context) Then context.Session("UserName") = privateUserIdentity.Name context.Session("Roles") = privateUserRoles context.User = privateUserPrincipal End If Catch e As Exception Throw e End Try End Sub |
這樣一來當介面變成WINDOWS的FORM是就不需要改動很多代碼了,同時為瞭解決將許可權放到SESSION中引起的延時問題,我將使用者的許可權資訊放在服務端的XML檔案中,然後直接中XML檔案中獲得資料,任何對使用者資訊的修改都將改變相應的XML檔案,這樣效率高於從資料庫獲得。
當然在實際開發中還會碰到很多其他問題,如報表,列印,並發性等,如果大家有興趣可以和我聯絡,限於個人水平有不足之處請多多包含。