系統分層演變


  這是一個從組件和分層角度的系統架構迭代過程的例子,選擇這個角度因為這個角度最直觀,也最具表現力,這個例子並不完整,不過沒有包含進來的部分並不影響這個例子就是了,原本是有說明各個組件之間的調用關系,通訊方式什么的,不過感覺那個圖看着亂,而且我覺得只要能說明問題就可以了;另外一點就是公司今年初就開始重組,這個系統也一直再跟着調整,沒有大動作的情況下,我偶爾可能會改下最后的圖。

  例子開始之前,先簡單提三點:一是隨機應變;二是沒有銀彈;三是KISS。這三點其實是一回事,很多書將理論講設計講的其實就是一個隨什么樣的機應該怎么變,現在很多開發人員都有一種想法,就是一次性什么都做好,以后可能會怎么變都想好,這種想法就是所謂的過度設計,沒有銀彈就是說沒有做好這種事的,沒有能適應一切情況的設計,只有最合適,沒有最好,復雜的結構就代表着要付出理解、維護及性能上的代價,只要將真實的需求理解好了,未來的需求就一定能適應,對於確定的未來一定會出現的需求,可以思考,但不到真正出現的那一天,最好不要去做(除非實在是閑着沒事,而且做了客戶也發現不了,一般是很少見有項目時間是充足的)。

  先看一眼現在變化成什么樣了:

                  

  正式開始:背景是這樣的(這部分是我聽說的,因為那時候我還沒入職,真的假的反正關系不大),原本公司用的別人的OA系統,但是由於公司是又至少5,6家公司合並的,類似聯邦似的管理,所以業務不是很規范(國內公司業務非常規范的應該不多吧),總之,這套OA用起來有些不通暢,同時由於是成品的產品,修改維護非常困難,所以領導決定自己做一套,然后就開始下手了,而且是沒有規划直接就下手了,聽說用了一年的時間做了兩個審批流程出來,工作流驅動開始用的一個網上完全沒有資料的產品,然后大部分功能沒用上,后來我自己封裝了一個,關於審批流程部分我這里不明確說了,有時間會單獨細寫的(現在有兩篇隨便寫的),由於工作流驅動被我用依賴注入注入到應用邏輯層了,在分層中就忽略不計了,總之原本的情況大概出了個如下圖的架子出來:

                  

                               <圖1>

  一、原本狀態,這個 <圖1> 可能不完全,因為我只是大概瀏覽了下原本的代碼,這個結構很簡單,問題也很簡單:

  1.大部分數據層使用的是EF,EF現在是個挺普遍的東西,這里只有一點問題,EF用的是3.5版本的,這個版本熟悉EF的人都知道什么情況,各種別扭,這一點倒是怨不得原來選型的人,沒什么性能壓力的情況下用ORM框架輔助很正常,選擇熟悉的技術更沒問題,至於這個版本。。。是由於技術以外的不可抗力造成的,要求沒有足夠經驗的人在一個不太明確的限制條件下(你懂得,不可說)對版本的可行性做評估確實很難;

  2.還有小部分數據訪問在 <圖1> 中的Service中,直接使用的ADO.NET,由於這部分的數據訪問和業務邏輯以及服務本身混雜在一起了,也就不細化了,提供的服務非常少,用的asp.net web service 沒有分布式部署;

  3.DTO的話,本身作用應該是為了將上下層解耦,可以使上層不用關心下層的數據類型和結構,我猜原本做的時候應該是有好好這么做的,但是由於后來需求不斷的變化,最終導致的結果是,DTO和EF生成的實體混合着BLL的業務邏輯被應用層直接調用,這里用圖不太好表達,所以<圖1>中就不費這神了;

  4.WebForm充當應用層和UI,圖上也不細分了,受原OA系統,業務人員及技術選型的限制,界面上充斥了大量的隱式包含業務邏輯的js腳本,基本沒有使用局部刷新,說基本是因為系統中有兩處位置使用了ajax加載數據,但是詭異的是,調用的是和web部署在一起,且和普通調用BLL沒有區別的asp.net web service,自然返回的數據是xml的格式,ajax本質上是讀取異步請求的頁面輸出的字符串,大量的xml標記會造成網絡傳輸的無謂的損失

  這幾點是主要的問題,也是調整開始的切入點,細節就不細說了,結果是領導不滿意開發效率,開發人員自己也對開發出來的系統感覺不舒服,代碼的可讀性和擴展性都不怎么好。

  二、慢慢調整整體上的問題,首先說“慢慢”,由於我剛剛入職對公司業務不了解,了解的渠道也很薄弱,這不是托詞,因為公司的財務和行政部門甚至都不了解有的部門為什么會有某個職位的情況下,我能了解到的業務基本也都是來至於推測;再說“整體”,細節雖然也調整了,但是由於各種各樣的原因,沒有太多精力也不願意深入細節,原因在於上部分說的進度慢,開發效率低,不是說做的東西少,而是做出了大量的妥協於技術和需求的額外邏輯,另外,畢竟是公司內部系統,沒有壓力,所以程序員們想要提高的動力也不足(這里說句體外話,總有朋友抱怨公司技術實力弱啊,學不到東西什么的,雖然有牛人帶進步確實快,但不代表沒有人帶就不能學習,內事有baidu,外事有google,大量的開源代碼,有msdn和各種開源社區,自己摸索着進步才是真實的能力提高),為了盡量不影響已經開發的系統,就先進行些局部調整:

      

                                  <圖2>

  1.ORM被我換成了NHbernate,它比較成熟,而且用的比較熟,然后在nh上提供了兩個接口以供上層調用,一個是DAO,一個是存儲過程的調用;將這兩個接口的實現封裝起來,拒絕繼承,使程序員無法直接使用nhibernate,以備未來可以升級到4.0以上時,還換回EF,至於為什么要換回來,原因主要有兩點,一是跟着微軟混當然是用微軟出的東西最牢靠,二是NH的社區越來越沒有活力了,沒有微軟的專職團隊這么有生機;此外,封裝中實現了NH提供的日志接口EmptyInterceptor,可以記錄所有使用NH對數據庫的操作,當然一般都配置關閉了的,開啟的時候可以借助消息隊列來保存日志;還有一點好處是NH自帶緩存,當對映射表單表訪問時命中率很高,對效率有很大好處

  2.調整后的圖里的Service不是指Web Service,因為當時沒有使用的必要,這里是指當領域中的某個重要過程或轉換不屬於實體或值對象的自然職責(這句話出自DDD),這里包含了業務邏輯的調用,或者業務邏輯接口的簡單實現,原本的web service包含的業務邏輯絕大多數移入BLL,只保留了來至於外部的服務;

  3.DTO移除掉了,使用DTO的絕大多數都是DAO的簡單操作,DTO的存在價值無法體現出來,直接使用NH映射的貧血實體,簡單明了維護方便,系統並不復雜,構建DTO去解耦沒有意義,軟件系統的分層主要是為了將變動限制在更小的范圍內,也使開發不同部分的開發人員並行開發對相互之間的影響更少,但是系統一人開發足矣的情況下雖然為了代碼整潔而分層,但是擴展的代碼(比如層之間傳遞數據的DTO)是應當在不擴展會對系統有不良影響時才寫的,所以這種規模的系統使用DTO會有過度設計的嫌疑。

  4.將界面的腳本進行了整理,統一腳本的寫法,將一些腳本進行了封裝,對webform后台代碼寫法進行了規范,簡化界面開發。

  之前說的工作流這里不得不再提一下了,之前雖然使用了某種工作流引擎,但不得不說,用了和沒用差不多,仍然有大量的應該由引擎處理的邏輯被放到了webform中,所以匆忙中,用了些時間簡單的封裝了個工作流引擎,然后為了不影響某些已有的東西,用了依賴注入,可以將新引擎和原有引擎通過配置分別使用。

  三、經過一段時間調整,開發效率提高很大,相應增加的需求也越來越多,我也參與了一點應用開發的工作(比如寫個DEMO什么的),對程序員灌輸了些面向接口,面向抽象的概念(我很奇怪他們獲取聽說過但完全不想去用),以及作為一個程序員,寫重復的代碼是一種恥辱(他們很喜歡復制粘貼。。。),大致整理了一下業務接口,發現業務雖然很多,但大多類似,這段時間將業務的基本接口大致梳理了些,交給程序員參造整理並對部分的業務代碼進行重構(另一部分沒重構是因為些上不得台面兒的原因,就不細說了),將映射的實體分成了三類,分別繼承了源自同一基類的三個略有不同子類,這主要是應對各事業部拍腦袋改需求所做(比如,某天突然想再所有單據上加上個什么屬性,但是過十天半月又突然不要了);

  在這段時間內,對界面層做了一些變動,由於在參與開發的過程中,發現很多頁面結構及邏輯相似,完全沒必要總是做個新頁面出來,總是寫些微妙的雖然不同但是很像的代碼,基於此就將WebForm的前后台分離,引入了angular,使用雙向綁定在頁面上只保留頁面元素,將腳本邏輯移入angular的controller,后台邏輯移入service和web api中,封裝了些angular的provider使用ajax的方式進行前后台交互,原本webform直接使用的實體由angular中的$scope來維護了。

  Service部分正在着手做封裝,這一Service的含義又有些不同,一方面內部的實現與上部分所說的含義相同,另一方面表現形式是分布式的服務接口,這里准備使用WCF和Web API,還會包含一些直接調用的其他系統的Web Service,WCF應該算是個微軟的集大成之作了吧,所有的微軟的分布式消息通訊方面的技術大融合,又支持業界標准,至於Web API處理一些簡單的請求更方便,不需要通過依靠很多約定等。

  這里可能會有人奇怪為什么不用MVC,其實我考慮過asp.net MVC,暫時(注意:只是暫時)沒用的原因也很狗血,我們所有的東西都是部署在sharepoint 2010上的,但是沒有專職的相關人員(應該說基本就沒人懂),也沒深入了解過這東西,搞不清楚究竟怎么和MVC整合,舉個例子,一般情況下,sharepoint會有個名稱以sp開頭的組件對應普通的asp.net,比如SPContext對應httpcontext,但是sphttpapplicant居然就沒實現ihttpapplacation接口,sharepoint的global又是輕易動不得的東西,使用了httpmodule到是攔截到了,但是結果不盡人意,github上倒是有個老外做的解決問題的例子,可惜使用的是sharepoint 2007的,而且語焉不詳,測試了下,感覺不踏實,去微軟社區問也沒有結果,現在也還在研究當中,好在領導終於松口打算不用sharepoint了,人精力有限我也是實在不願意花費太多精力在它上面,所以,MVC暫時還沒用上,不過早晚能用上的吧,只要機會來的時候我還沒離職。

                                  <圖3>

  四、在我將注意力放在對簡陋封裝的工作流引擎不滿而進行一系列修改,還有諸如郵件審批服務,信息部門一些日常工作等一些瑣碎的事上面時,由於開發效率的提高,造成了需求大量增多而引起業務對象數量上的快速膨脹,而由於業務對象被分為貧血的映射實體和BLL的業務邏輯操作類兩部分(當初選擇這么做主要是因為開發人員這么處理習慣,而且看起來清晰,在系統規模不大時便於閱讀和維護),造成了一個業務對象至少會增加兩個類,有礙觀瞻,十分影響心情,於是要解決它:

  將貧血實體改為充血實體,整個Service借用DDD中領域服務的概念了,由它實現非當前業務對象本身的職責(包含大多數原本的業務邏輯接口),比如單據的提交應該是由提交人而非單據本身,至於實現方法,在另一篇隨筆中寫了個演示例子,這里就不細說了(由繼承演變為角色扮演)。順便說一下貧血和充血,網上有好多分析這個的,在我看來不用理解那么復雜(也有可能是我沒理解透徹,等這倆概念對我真正有幫助的時候再細致了解吧),就是一個是將對象的動作分離了出去,方便一般程序員理解和小型項目開發,另外一個就是即包含對象屬性又包含對象動作,這種情況下處理需要注意一下,對象是不能直接提供給上層調用的,否則封裝就會被破壞,業務會在一定程度上被泄露出去。

  經過上面一步,BLL中的業務邏輯大體上出去了,剩下的就是具體的業務執行了,也就是應用層,在圖中稱為應用邏輯。

  至於這中間的代碼工作寫個例子就交由開發人員去做了,這時候要開始考慮DTO的問題了,因為充血的實體是不能直接提供給上層用的,得封起來,那交給應用層(這時候angular和原來的webform方式同時存在,所以就叫應用層了)的只能是DTO,DTO的另外一個好處是,拼裝好的DTO可以將一部分原本暴露給應用層的業務邏輯或者說拼裝邏輯封裝起來。當然,這事咱也是能偷懶就偷點懶,交給開發人員做,索性來例子都不寫了,本來他們就用過DTO的,區別僅僅是那時候他們的DTO構建是沒有依據的,用到什么算什么,這次統一按照界面來,拼裝中公用的東西再緩個存(已經申請到手了linux還沒時間用,准備以后緩存都交給redis了,不過畢竟還沒上所以緩存這部分先不畫圖了)。,

   這里將服務統一成WCF,主要是因為之后公司各種系統,像ERP什么的都會通過ESB銜接起來,對其他系統的服務的調用都會通過ESB,分布式且統一標准的接口是必須做的,用以提供給ESB供其他系統調用,至於本系統內部當然適當的Web API還是會方便些。

  然后,還有一樣體力活交給了開發人員去做,就是讀寫分離,最開始做數據層對外接口的時候,就將存儲過程調用與普通的SQL執行都預備好了,所以只需要將復雜查詢剝離出來就好了,區別是以前返回實體或DTO,現在統一返回ILIST,這一部分主要是為了將要進行的報表查詢統計,一些BI的工作,另外,NH本身只支持單數據庫操作,但是可以通過些 手段多數據庫也不是問題,這個最早就做到了,因為這里雖然只說了一個系統,但是系統其實有幾個,所以數據庫也是多個。

                                    <圖4>

 

  上圖中還有一處值得一提的是Provider,這個Provider在之前一直是BLL中的一個類,作用是將所有數據層的接口全部封裝起來統一提供給業務使用,這樣做看起來是有點大雜燴的感覺,不過當時有兩個原因需要它存在(其實我就是那個閑的無聊且干了什么別人也發現不了的人):
  一是數據層提供的接口從概念上完全是對數據如何使用的描述,將接口重新包裝后可以讓它在業務層面上有一定的表現力(比如:插入歷史表在業務中描述為歸檔),容易融入到業務當中,便於程序員在開發中專注於業務,也有利於之后應用DDD的設計理念后轉變為Repository的重要組成部分。
  二是充當從<圖3>到<圖4>轉變的緩沖,讀寫分離是很早就做了准備的,將數據層接口的封裝到這里,讀和寫可以在上層不知不覺中完成分離,當分離成熟后,可以很輕松的將其中查詢部分移出,只需要借助工具就可以輕松將查詢的引用指向新的查詢類,成為<圖4>中的狀態。

  目前,這個分層架構尚尚未調整完成

  五、我預想中,下一步在這個分層基礎上的進一步調整,就不畫圖了:

    1.接入ESB,目前在怎么接上,我的想法和領導不太一樣,不過我相信終究會按我想的來的,大概。。。;

    2.由於很多Entity在業務上是要保證數據一致性的,可以作為一個整體在系統中執行,這里可以使用DDD的思想,可以參考CQRS的架構,當然現在系統還沒有那種規模的復雜度;

    3.使用MVC深入研究下,我覺得在sharepoint2010上應該是可以的,而且,據說sharepoint2013上是已經支持了MVC,另外可以考慮一定程度上結合angular;

    4.目前尚不清楚是否有必要使用緩存,不過既然redis環境我都准備好了,不用下實在對不起自己,大不了暫時讓他們發現不了就是了;

    5.異常處理和日志准備重新統一規划下,不過目前似乎領導不大願意做這件事,我也先偷偷做着。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM