走向.NET架構設計—分層設計,初涉架構(前篇)
前言:本篇不打算接著上一篇來,這沒有關係,以為內他們之間的聯絡不大,以後我再補上。因為之前一直在談論設計,也談了一些TDD的東西,大家反應覺得講述的還是有點”空”,所以打算換一種方式:先講述一些例子,把一些思想穿插著講述,理論的東西最後最為總結。希望大家支援!
本篇主要講述ASP.NET應用中如何進行邏輯分層。本篇的前篇會從Smart UI 反模式和它的一些缺點開始講述,然後一步步的講述如何邏輯分層,而且在後篇中也會給出一個ASP.NET設計中常用的僅供參考的分層架構的Demo。
一個穩定和易維護的系統必須建立在一個好的基礎之上。計劃和設計一個好的架構對一個項目的成敗起著至關重要的作用。可能在我們一般做項目的時候,經驗告訴我們:3層,N層的設計,基本就能把問題解決了,很多的情況確實是這樣的。在提出一個設計的時候,常常要考慮為什麼要這樣劃分結構,而且常常要承擔風險和責任,特別是萬一這個項目因為最初的設計而導致崩潰,那就鬱悶了。所以設計的提出一定和考慮業務。
下面就先來看看Smart UI的設計方式。
Smart UI
想想我們最初是如何開發ASP.NET的應用的:在頁面設計介面中把介面布局好,然後雙擊控制項就開始編寫功能代碼。很多的時候把邏輯判斷和資料訪問都寫在頁面的.cs的檔案中。後來我們學習到了分層,逐漸的明白了這種方式的缺點:導致商務邏輯代碼到處分散而且重複,不利於以後的更改和維護等。
儘管有上述說的一些缺點,Smart UI還是有它的用途的,如為項目快速的建立一個原型或者開發一個功能比較的小的項目。還有一個問題,如何最初用Smart UI的方式開發的小項目很成功,慢慢的變大,變複雜了,那麼很多的問題就出來了。就像Flower在架構模式一書中提到的:盡量用領域模型來組織一個項目的商務邏輯,儘管在開始的時候邏輯不複雜或者看不出這種方式的好處,一旦項目變化,好處就顯而易見了。在對項目原型開發中,盡量不用Smart UI。
其實Smart UI最大的問題就是:職責不清—把所有的東西全部寫在一起。
為了和以後講述的內容的比較,我還是寫一個例子出來,很多朋友都已經對這種Smart UI的開發方式很熟悉了,可以跳過下面的例子。
這裡以產品管理系統中的一個簡單情境為例:顯示所有的產品資訊。例子採用ASP.NET來實現,步驟如下:
1.建立AgileSharp.Chapter3.Antipattern的ASP.NET項目, 所示:
2. 選擇“App_Data”檔案夾,添加資料庫“OrderManagement.mdf”, 所示 :
3. 在新加的資料庫檔案上右擊,並且開啟。然後添加一個新表:如下:
其中ProductId設定為自動標示。
然後儲存為Products表。
4. 添加一些測試的資料.
5. 然後選擇Products表,並且把表拖放到Default.aspx頁面上。這樣之後,在頁面上就自動添加一個GridView和SqlDataSource.
介面就如:
6. 我我們添加額外的兩列來顯示折扣資訊和庫存資訊。
7. 然後,我們在Default.aspx.cs後編碼:
protected void gvProduct_RowDataBound(object sender,GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
intproductId = int.Parse(((System.Data.DataRowView)e.Row.DataItem)
[“ProductId”].ToString());
decimal price = decimal.Parse(((System.Data.DataRowView)e.Row.DataItem)
[“Price”].ToString());
Label lblDiscount = (Label)e.Row.FindControl(“lblDiscount”);
lblDiscount.Text = DisplayDiscount
(ApplyDiscountsStrategy(productId, price));
}
}
在上面的 GridView1_RowDataBound方法在GridView的每個row被建立的時候調用。這個方法擷取每個產品的推薦的零售價格RRP(Recommend Retail Price),然後調用 DisplayDiscount和DisplaySavings方法來擷取折扣和庫存,然後再更新UI的顯示。
在上面的代碼中,就將計算折扣和計算庫存的邏輯寫在了UI中,而且資料的存取碼也寫在UI中了。這就意味著:如果我們想要在不同的頁面顯示產品的資訊,那麼這些邏輯就得一遍遍的重寫。如果我們在加一些新的功能,那麼頁面後面的代碼就開始修改,開始縫縫補補。
解決Smart UI的方法就是劃分職責,我想大家都知道“單一職責的原則”,這個原則不僅僅適用於類,方法,而且對項目的層次劃分也有作用。分層,最主要的目的就是:把不通的功能放在各自對應的地方,這樣清晰的職責劃分,也是對變化點進行分離。
下面的圖就是一個典型的企業級ASP.NET項目的分層結構:
下面我們就來看看,按照我們的一般的分層的經驗來如何設計這功能:
下面,我們再次來練下手,重新設計前面提到的訂單管理系統,使其功能適應新的需求(很多時候,重新設計基本不可能的,所以在開始設計時就需要考慮清楚,這裡重新設計只是從舉例的角度來考慮的):
(1) 支援使用者通過網頁或PC傳統型程式來訪問系統。
(2) 訂單管理系統需要為其他的系統(如財務系統等)提供服務。
(3) 訂單的一些處理流程和相關的業務處理在其他的系統要用到。
(4) 還有更多的需求,等待確認。
註:朋友們一眼就應該可以看出,這些類庫的命名是反映了一些
DDD的一些概念,但是,不是說在一個項目的開發中用了這些概念名詞就表明就開發的方式是DDD了。
這裡我先提一下上面類庫的一起名字:儘管有關DDD和一些架構模式的概念我在以後的文章中會講,但我這裡還是先給大家提一下,目的僅僅是讓大家對這個例子有一些更好的瞭解。
在DDD中,一直主張業務模型,也就是我們常常所說的業務類,例如之前例子中的Product,只關注自身的商務邏輯,而不管如何去擷取和儲存資料,這些對資料的操作完全交給另外的對象去執行,也就是Repository,這樣就達到了DDD中所說的PI(Persistence Ignore)。所以在上面的例子中,ASPPatterns.Chap3.Layered.Model就代表了一個業務模型,它之所以被Repository引用,是因為Repository負責將Model的資料持久化到存放裝置中,而Model不管這些事情了。
在講之前,首先給大家統一 一下Service的概念。
有時在類的設計過程中,有些行為不適合放在任何的一個類中,如果把這些行為放在一個不真正擁有它的類中,只能把類的職責搞混了。為了給這些行為一個安置的地方,我們常常把這些行為放在一個稱為服務的類中。
作為服務的類一般沒有狀態的,可以簡單的作為一個提供操作介面實現。
在DDD中,Service也是用來提供一種服務的。很多人看到了DDD的類階層是這樣的:Repository---Model---Service--- Presentation(包括本例),所以都以為Service只能出現在Model的上一層,如果看到Repository-- Service ---Model---Service--- Presentation這樣的階層,又作何感想。如果被這些所謂的結構搞迷惑了,那就說明對DDD的理解只是在於“形”上。Service就是向外部提供的功能介面,和我們常見的Web Service的概念很相似,例如的Web Service就是向外部系統提供一些功能的。
我們來看下面的一個圖:
有時候之所以要在Model層之上加上一個Service層,主要的原因就是實現粗顆粒度的API,往往和系統的User Case有一定的聯絡。例如,如果在系統用例中要實現一個使用者訂單的處理,那麼可能就涉及到Customer, Product,Order等類,當然,如果我們調用這些類來共同完成這個任務是沒問題的,但是這樣就向調用者暴露這些類之間的複雜的關係,而且如果處理的過程變化了,那麼調用者的代碼就要改變,如果把這個處理的方法放在上面的任意一個類中,又顯得不倫不類,這裡的Service功能就類似於設計模式中的Façade面板模式。這樣就向外界提供簡單的API,向外界提供訂單處理的服務!
所以在一般在DDD中業務層被劃分為兩個邏輯層:Model (提供細粒度的商務邏輯處理,也便於重用), Service(提供業務處理的流程,提供粗顆粒度的供外部調用的方法)。
但是,我們常見到的Model層之上的Service層僅僅只是對CRUD的再次封裝,一個可能的原因就是業務不是很複雜,這時其實這個Service層可以拿掉的,但是考慮到以後可能邏輯會更多更複雜,所以還是保留Service這層。
其實在Repository上的那個Service也是同樣的概念。例如發送郵件通知使用者的功能。例如中的最上層的Service可以調用業務層和基礎設施層的Service來共同完成一個事情。
今天的上篇的就講述到這裡吧,下篇會用一個例子,代碼量還是有點的!敬請關注!
大家感興趣,我們下篇講述!