寫技術文檔的難度太大了!數次删改,都沒能滿意,是以我還決定,先寫出來,以後再逐漸整理完善——否則可能這個系列都沒辦法寫下去了。這也算是借鑒了靈活的思路,先寫再改,不斷疊代重構吧!
前面的幾篇部落格反響還不錯,但還有一個硬傷,“說了這麼多理論,能不能實踐?”講類似概念的文章不算多,但也不少了,但我一直沒能從中收獲太多的東西,反而更是雲裡霧裡的糊塗了。估計這主要是兩方面的原因造成的:我智商低,卻愛較真!
你說得得天花亂墜,我隻信一點,眼見為實,“是騾子是馬,牽出來溜溜?”
按照你說的架構,把系統搭起來,跑起來,需求改上個幾百上千遍,高并發大流量沖一沖……咦,這樣一番折騰下來,沒被砸跨,系統千錘百煉之後,還百煉成鋼繞指柔。那我才豎起大拇指,真是不錯!
我相信,按照DDD、TTD、靈活開發之類的理念,一定有成功的案例,不然他們不會被站在巅峰的技術大牛們交相稱贊。但很遺憾,我這個野生程式員,沒機會融入那個圈子。
是以我就用了一個最蠻最笨的方法:我自己做一個系統,嚴格按照我自己對于這些概念的了解進行開發,看最後這條路能不能走出來?曆經五年甚至更多時間的摸索和實踐,我覺得我基本上是走出來了。
是以,如果你願意,就靜下心來,聽我細細道來吧。
尴尬
在确定了忘記資料庫的大原則之後,我們理應從業務層入手開始系統的搭建。
業務層裡,通常我們就把需求裡的一些名詞拎出來,做成一個一個的類,以創業家園為例,就應該有一個部落格類(Blog),部落格裡還有方法,比如GetBlog(int Id),或者GetBlogs(int pageIndex, int pageSize),如下所示:
這是我最開始接觸三層架構時業務層類的樣子,寫在書上的。
但我就感覺這種做法特别别扭!一個部落格對象取出10篇部落格,一輛汽車具有提供十輛汽車的能力。這都是些什麼亂七八糟的東西?不通啊……
我曾經想過将所有的Get()方法設定成靜态的,這樣從邏輯上說稍微通暢一點:通過部落格類可以擷取一些部落格執行個體。但還是不爽,類的靜态方法就喪失了對象的繼承多态等特性。比如,取10篇文章,和取10篇部落格就無法重用。
後來我才慢慢明白了,這種做法其實還是來自于“資料庫驅動”的思想。Blog類其實代表的是資料庫中Blog表,一個Blog執行個體就代表着一行資料,然後通過該表取到一些行,這些行又被封裝成Blog類(細究起來還是很亂,是吧?)。估計當初微軟DataSet的流行加劇了這一現象,當然DataSet本身沒有問題,它的邏輯是自洽的;然而有很多開發人員不認可DataSet,說它性能低,要用DataReader,自己“封裝”,結果不知怎麼的,就搞成了上面那種樣式的“四不像”。
Entity
上述傳統的業務層架構,除了邏輯上的混亂以外,還有一個很大的問題:難以測試!和資料庫攪在一起,怎麼測試?我是頭都大了。我得去做一個小型資料庫啊?而且這個資料庫還得insert/update之類 的,測試的基準資料就會變,是以每一次單元測試都得tear down(回到基準測試環境),這個又怎麼搞?
這樣做最大的好處,就是解決了Entity的單元測試的問題。由于(至少是暫時)不再需要考慮這些對象和存儲問題,那麼在測試的時候,我需要一個對象,隻需要直接new一個就行了,而不是從資料庫裡取,這多友善啊!
Query(Repository)
那麼,對象的增删查怎麼辦?從技術層面來講,我們隻能依靠ORM工具了,我用的是NHibernate。簡單的說,通過NHibernate,我們可以在對象和資料庫結構中建立關系(映射)。然後,可以通過NHibernate的session,調用session.Save(), session.Delete(), session.Load()和session.Query()等方法将對象存儲、删除或者加載/檢索到記憶體(C#項目)中使用。
但從系統架構層面講,有另外一種提法:Repository模式。
Repository,從字面意義上了解,就是倉庫。這個概念我覺得很貼切,就像汽車存放在庫房裡,我們通過倉庫管理者,取出一輛或多輛汽車。這就有“代碼映射真實世界”,一種邏輯自洽的感覺;而不是之前,一輛汽車取出十輛汽車的樣子。
具體到代碼層面,就大概是這個樣子:
但Repository的了解和使用都有争議,主流的大概有兩種:
認為Repository是類似于集合,或者一種封裝集合的對象。是以還是把它放到了Entity中使用。
認為Repository是“聚合根”的一種,和取出/存儲對象并列,應該置于Entity之外。
我連Repository都沒有顯式的使用,是以就不進行這種關于概念的抽象讨論了。後面有機會我們穿插着講一講吧。
我們“增”和“删”直接利用了NHibernate的session機制,隻是把“查(select)”給單獨抽象了出來,也單獨的抽象成一個名為Query的project。
Service
好了,現在我們可以回頭歸納一下。對系統資料的操作,我們腦海中應該是這樣一個概念:
前提:所有的對象平時都是直接的存儲在磁盤裡,然後:
我們需要某個/些對象時,就把他們從磁盤裡取出來,加載到記憶體中
進行一些操作修改
最後再存儲到磁盤中
那麼問題來了,上面這些步驟,由“誰”來做呢?注意我們現在所說的這些東西,都是在業務層的範疇。是以,按照三層架構的思路,應該是UI層調用BLL層,而我們的UI層,采用的是MVC,是以,這樣工作,是不是應該在Controller裡面做?
但是,閱讀我們的源代碼,你就會發現,我們在UI層和BLL層之間加了一個Service層。實際上是由Service層來做的這些加載、修改和存儲的工作。我非常同意這麼一個觀點:絕不能為了分層而分層。那麼,Service層存在的意義是什麼?
主要是為了前後端分離。早期的開發過程中,我設想過招聘一個專門的前端開發人員,他/她不管背景的具體業務邏輯、和資料庫的互動,隻管頁面的呈現和互動。那麼這裡就有一個問題,我不想她隻是一個單純的美工,畫出效果圖切片弄成一個html的靜态頁面就完了,我希望她一樣的用VS進行開發,用Razor做成view,還負責頁面的互動和跳轉,是以她還得在Controller裡建Action,在Action裡寫代碼。是以她在Action裡寫代碼,是要得到資料用以呈現的,是需要根據頁面回發的資料調用不同的業務邏輯的。那麼,這些資料這些調用怎麼得來?等着背景開發人員完成了之後再做?這無疑是很不經濟的。
是以我們抽象了一個ServiceInterface,前台和背景開發人員可以先确立一系列的接口,然後各自去完成自己的實作。于是就有了:
UIDevService:前台開發人員的“模拟”實作,看源代碼就可以發現,裡面是一些非常簡單粗暴的邏輯。比如需要一個ViewModel對象,就直接給new一個就可以了。
ProdService:真正的業務邏輯實作,是一直連到資料庫的。
這其實就有一點“面向接口”的意思,前台背景都依賴于ServiceInterface的接口,而不管其具體的實作。
當然,這樣隔離出UIDevService之後,還附帶了其他一些好處,比如更便利的單元測試。這些我們都以後再說。
上張圖吧。先看看,看不懂也就算了,實在是我畫得不咋的。以後還會詳細講的:

ViewModel
我們項目中還有一個ViewModel,我們的開發人員曾不止一次的提出來:為什麼不能直接使用Entity呢?
我非常了解他的疑惑,一次次的把一個Entity裡面的Article的屬性取出來,再一條條的放到一個ArticleViewModel裡面去,這多鬧心啊?吃飽了撐的?
其實,我也是開發人員,這架構是我一個字母一個字母敲出來的,能偷懶的我肯定都會偷懶!就像前面我沒采用Repository一樣,我甚至都還弄過兩層架構,但最後都沒有好下場,才一步步走到今天。簡單的說,ViewModel存在的原因主要有兩個:
第一、前後端分離的要求。如果直接使用Entity,前台開發人員是不是又得等着背景開發人員把Entity先建好?是不是Entity一有變動就會立馬影響前台開發?有興趣的同學可以觀察我們的ui.task.zyfei.net.sln解決方案,BLL層裡的所有project是根本就沒有包括在裡面的,我們徹底的做到了實體隔絕!
最後,我們其實應該跳出來,從架構的角度來思考這個問題。ViewModel究竟是什麼?它說承載的職責應該是什麼?應該由誰來建構它?……
我認為:ViewModel本質上就是一個用于頁面呈現的資料容器(DTO),是以他不應該具有任何内在邏輯,而且應該由前端開發人員來建構它。前端開發人員應該徹底的擺脫業務層中的Entity的束縛,根據頁面的呈現規律,大膽的進行各種抽象組合,使得ViewModel真正的綻放它的光彩!
MVC
說完了上面這些,MVC其實也就沒什麼好說的了。就是Controller調用Service,得到ViewModel供View使用這樣一個流程。當然,裡面有很多值得細講的内容,比如mvc route的測試、使用Autofac切換Service的實作、Session Per Request進行性能優化等。我們在之後的分則裡細講。
這裡還是上一張我制作的PPT吧,醜了點,先将就看吧!
Tool
要填的坑
架構就這麼拉出來了,但其實裡面的坑還有很多,趁着有思路,先挖出來,以後慢慢填:
1. UI
CurrentUser的處理:也是一個相當頭痛的東西,因為會大量使用,那麼就想着要重用,要想重用就傷腦筋
Get-Post-Redirct模式:裡面也是一堆的坑。因為Http是無狀态的,是以Redirect的時候就面臨着一個傳遞資料的問題
MVC Route:曾經傷心欲絕,當頁面複雜之後,url就跳不到指定的action;或者稍一改動,以前的route規則就就崩潰了
Partial View、EditTemplate和Child Action:在裡面不知道暈了多久
單元測試
其他性能優化
2. Service
提高性能:SessionPerRequest。這個必須放在最前面說,因為它深刻的影響了我們下面提到的頁面架構的很多東西
UIDev和Prod的切換:利用Autofac
SessionPerRequest的具體實作,和UI和NHibernate都攪在一起,真不知道該放在哪裡說
為什麼不使用Repository模式而采用Query
ViewMode的Map:使用Automapper
單元測試:Query又要攪到資料庫,唉……
3. BLL
Entity大集合的性能問題。由于對象間的1:n的關系映射,造成一不小心,就扯出一堆集合資料出來,比如一個Author的所有Article,一個Article的所有Comment、Agree和Disagree。要這樣弄的話,再多的記憶體也吃不消。
Entity的多态應用。超級大坑,簡直是要出人命的感覺,我覺得我能爬出來都是個奇迹
Entity的單元測試。由于Entity之間複雜的對象關系,其單元測試簡直就是一場災難
Entity的NHMap單元測試。Entity裡都沒問題了,但你怎麼保證Entity的資料庫映射時正确的?隻能做單元測試,還是繞不開資料庫!
4. Tool
BuildDatabase:超級繁瑣超級難
其他清理統計工具等
呵呵,原來有這麼多坑!
這又讓我不由得想起我煩躁咆哮,扯頭發摔滑鼠的那些日日夜夜,我也不止一次的懷疑過,我是不是走錯道了?這些亂七八糟的MVC、測試驅動、面向對象……根本就沒有讓我更高效順暢的開發,好像隻是不斷的在扯我的後腿。我就用傳統的辦法,拖控件增删改查資料庫又怎麼啦?不是一樣能用?而且說不定早就開發完了!……
但一次又一次解決問題的喜悅,一不小心窺視到另一個世界的驚奇,讓我欲罷不能。這可能就是技術路,人生路,大抵也如此吧?