標籤:前端
本文讀者基本要求:從事信息管理系統開發,略懂GOF設計模式及SOLID設計原則,對三層面向過程機械編碼厭倦,而且不知道出路在何方,若是還掌握代碼壞味和重構手法,那是極好的。程序員
理論介紹-->實際經驗-->總結反思數據庫
1.1 簡單介紹三層架構編程
嚴格分層架構模式的特色是上層只能訪問相鄰的下層,其餘層次間的調用都不容許。三層架構就是一種嚴格分層模式,它把職責劃分爲界面展現、業務邏輯、數據訪問三層,還有一個業務實體,前面三層都要依賴它,因此它並不構成一個層。結構如圖1。設計模式
三層架構的特色是一種面向過程的編程思想,特色以下:緩存
a. 業務實體類中基本上只有屬性沒有方法。安全
b. 業務邏輯層的類基本上只有方法沒有屬性。性能優化
c. 將數據表結構映射爲業務實體類是一個慣用作法,以致於有人將其稱之爲「傳統」。這樣的好處是隻須要理解一套模型,可以經過自動化工具從數據表直接生成業務實體,還可以天然而然的經過自動化機制存儲與檢索業務實體。但對於複雜點的業務,這樣作就是絕大部分問題的根源。數據結構
d. 當業務膨脹起來,須要劃分模塊的時候,咱們有個經常使用的變形:提取一個服務層出來,用來組合模塊間的交互,還爲業務邏輯層提供了一個防腐層,能夠把記錄日誌、驗證權限、處理異常等職責分配給服務層。結構如圖2。架構
上面介紹的就是咱們常說的三層架構,因爲採用了嚴格分層模式,用戶界面層是絕對不能跨過業務邏輯層調用數據訪問層的,同理服務層也是不能調用數據訪問層。可是圖2都有四層了,那它仍是三層嗎?這就須要見仁見智了,通常來講仍是的。其實三層架構還有個更準確的名字----分層貧血領域模型架構,這樣就無論多少層都能歸納了,可是這名字沒有三層那麼朗朗上口。前面名字中的領域模型指的是業務實體,貧血意思業務實體中沒有或不多方法。
1.2 實際應用中的三層架構
1.2.1 PetShop。
三層架構比較著名的例子就是PetShop,其技術內容比較豐富,MemberShip,ProfileProvider,各類接口,各類Factory, 可替換的數據訪問層,緩存機制,消息隊列等技術,看起來很牛的樣子。但它的領域模型卻比較簡單,連模塊都沒有劃分,只能算個玩具。不少年前,見識還比較少 的時候,微軟就是我心中的神,只有微軟提供的技術纔是最正宗的,微軟說的都是對的,沒有用上微軟提供的技術,內心就各類不對勁。後來學習設計模式,知道有 個模式癡迷症(若是在編碼中沒有用上模式,內心就各類不對勁),才明白原來本身以前患上了微軟癡迷症。在個人實際工做經歷中,微軟癡迷症患者不在少數。就PetShop而言,微軟只是展現一下他的技術而已,初學者們不明覺厲,盲目地奉它經典,以致於固步自封,不明白軟件開發的路還很長很長,每種技術都有它的適用場景,選擇權永遠都在本身的手上(或在團隊領導的手上)。
1.2.2 本人實際工做經歷。
參加工做的前幾年,都在小公司,經歷的都是一我的開發的小項目,有時有個Web前端或美工配合一下,信息系統嘛,主要功能固然就是存儲、檢索、展現數據,業務操做就是CRUD了,業務邏輯最多就是驗證一下業務實體填寫的正確性,數據表的數量也很是有限。那時的我,在三層架構的統治下,作着一個快(枯)樂(燥)的數據搬運工(從數據庫搬到界面或從界面搬到數據庫)。
長期重複枯燥的工做會讓人感受前途無亮,窮則思變,結果就是人員的流動。對於短時間小項目來講,人員流動根本不是個事,可是對於長期項目來講,可能就 沒那麼簡單了,某些關鍵人物的離職,可能會引發很長期的震盪,接手的人加班、發脾氣絕對是正常的(這視乎接手人員的技術水平),甚至可能使項目停滯不前。
終於,我順從心裏的呼喚,離開那個簡單的環境,加入了一個比較複雜的長期項目。和之前同樣,採用三層架構,信息系統項目嘛,CRUD就是基石,項目中絕大部分的工做就是搬運數據。不一樣的是,開發人員有10個以上(人生中第一個團隊開發);數據表超過200個 (這是之前不敢想象的)。更加不一樣的是,有些功能,它可能牽涉到了全部的數據表,作這樣一個功能,首先要把全部關聯到的數據表都找出來,理解清楚表與表之 間的關係,比較痛苦的是別人寫的代碼你基本用不上,都要本身從頭搞。對於那些平時只是埋頭搞本身一塊功能的人來講,簡直是要命。還有更要命的,不知道什麼 時候需求又變動了,而處理這個功能的人離職了。開始,接手的新人兩眼淚汪汪求多給點時間,而領導不理解,之前誰誰不是很快就搞定了嗎?那些站着說話不腰疼 的同事說,這功能簡單,這個、這個、還有這個表,那個、那個、還有那個表,這樣再那樣就解決了。我把這種協做方式成爲嘴炮協做,真正的協做應該是代碼層面 的。新人無法辯駁通常最後會選擇默默地加班。人員繼續流動,這樣的情形發生屢次以後,領導開始意識到,狀況沒那麼容易了。特別是若是幾個關鍵開發者都離職 了,那境況更是難上加難。就算沒有人離職,狀況同樣是愈來愈難,這我在後面的一個項目中有比較深入的體會,可是並非全部人都會把這種難歸結於架構不對或 團隊開發水平還有待提高,他們會認爲這是需求的問題或者其餘同事的問題,還有人會認爲這是正常的必然的無解的事情。若是一段時間複雜的功能變動多起來,加 班就會成爲常態,若是加班可以搞定,狀況還不算最糟糕。「水很深,水太深,水不知道有多深」開始成爲了團隊的口頭禪。比較幸運的是當時那個項目這樣的功能 不算太多。最後有人總結出一句名言:「誰遇到誰倒黴 ,忍忍就過去了」。說到這裏,不少人可能會說,讓全部開發人員的熟讀全部數據表不就好了?實際狀況是,除了關鍵的幾我的,其餘開發人員每每沒有那個願意把 有限的生命浪費在閱讀這些下個項目就毫無用處了的數據表上,並且表的數量可能會增加到沒有任何一我的可以總體掌控的程度。並且一個牽涉多個模塊的複雜功 能,不能調動相關模塊來協助,反而都得直接與數據庫打交道,其餘模塊的邏輯也須要本身處理,這是單打獨鬥的套路,體現不了團隊的協做與效率;再有就是不能 複用那些久經考驗的代碼,致使重複代碼慢慢積累,系統缺陷也比較多。並且由多人接手後,一旦有人理解出了誤差,需求變動致使致命缺陷的概率會很高。
雖然項目組的生活氛圍不錯,時不時有經費給同事們出去吃完玩樂一下,可是不能預料何時需求就會提出一些「險惡」的功能,對人的精神也是一種考驗。爲了應付隨時可能出現的各類險惡狀況,我學習掌握了一些重構手法,學會用了UML來分析問題,還在項目中成功地應用了兩個設計模式來解決一個棘手問題。可是在三層架構的大環境下,可以發揮的餘地也是很是有限,有種有技術沒地方用的感受,常有新建一個類不知道該往哪一個層放的狀況。
後來,我還在另一個採用三層架構的長期項目裏呆過,狀況與上一個項目基本同樣,除了人員比較上一個項目穩定不少。就三層架構實際遇到的問題,我諮詢了一些項目經理朋友,他們的意見以下:
經理A:代碼是不可能寫成書上介紹那樣規範好維護的。
經理B:需求是不可能穩定下來的,代碼也是不可能穩定下來的。
經理C:項目到了後期代碼亂?難道不都是這樣嗎?
1.3 三層架構的反思
Linus Torvalds :Bad programmers worry about the code. Good programmers worry about data structures and their relationships. (差的程序員糾結代碼怎麼寫,好的程序員糾結數據結構和結構間的關係。)
三層架構的最大問題在於:實際應用中人們喜歡把內存模型和數據庫模型保持一致。三層架構的大部分問題都是從這裏衍生出來的。
數據庫模型的粒度若是很小,那麼大量的錶鏈接很快就會讓數據庫跑不動了。
若是數據庫模型的粒度若是很大(這是大部分項目的選擇),代碼的質量(重用性、穩定性、擴展性)就不好。因爲沒有從業務的角度去仔細定義每個對象,每一個人會根據本身的須要創建各類QueryModel或ViewModel,慢慢地類會多到想哭。或若是不創建各類Model,強行重用DataModel的話,那麼接口提供的內容每每絕大部分都不是你想要的。
內存模型與數據庫模型保持一致並不是天生的,這是有不少緣由形成的:
它建模的簡單性讓初學者沒法拒絕,因爲經驗主義,以致於多年之後已經沒有勇氣去擺脫了;
沒有專門論述三層與建模的書籍;
ORM工具誤導,與數據表結構一致內存結構方便創建映射關係;
示範代碼的誤導,錯誤把示範代碼當成產品代碼;
等等......
種種誤導致使不少人工做不少年後依然未能找到正確的路,忽略了一個重要的核心(業務建模)環節(業務模型要與代碼的數據結構保持一致),可是他卻坐 上了項目領導的職位,這就是更大的誤導了。以致於讓人產生不管你去到那個公司都是同樣的錯覺(在不少人的經歷中,這的確是事實,包括我本身),但事實不該 該是這樣的。
用戶界面,領域模型,數據庫它們應該具備同等的重要位置,領域模型在不少公司都是被忽略的角色。
咱們應該相信:優秀的軟件必定是由優秀的開發者製做的,團隊的協做方式應該在代碼層面,代碼複用能夠下降缺陷和提高效率。而這些都指向你應該離開「傳統」三層架構。
還有一些三層開發人員最終患上了數據庫癡迷症,他堅信程序就應該作個搬運工,其餘的事情都應該交給數據庫來完成,業務邏輯也應該寫進存儲過程裏面去。
優化層次關係-->重構到面向對象設計-->使用DDD相關模式深刻重構
上面講到,三層是「分層貧血領域模型架構」,那麼DDD則可稱爲「分層充血領域模型架構」。從名字上看,它們就有親戚關係,有親戚好辦事,嘿!
三層到DDD的 過程大致是這樣的:首先推翻嚴格分層的理念,採用鬆散分層來從新定義服務層(鬆散分層的意思是上層能夠訪問下層,而不僅是相鄰的下層),把調用數據訪問層 的職責交給服務層,接着把業務邏輯層移動到與業務實體在一塊兒,再接着融合業務邏輯與業務實體,使之成爲面向對象的設計,而後利用DDD的模式進行更深刻的重構。在DDD技術掌握的還不是那麼紮實的時候,三層技術基本仍然可以繼續使用。
2.1 優化三層結構&重構到面向對象的設計
因爲目前的服務層職責是很是輕的,甚至有不少空殼的調用,因此平衡一下職責,把調用數據訪問層的職責從業務邏輯層提高到服務層,須要的數據經過參數傳遞給業務邏輯層。這樣,對於那些簡單到無業務邏輯的CRUD就不須要去訪問業務層了,直接調用數據訪問層。
結構如圖3,咱們看到業務邏輯與數據訪問層已經沒有依賴關係了。
而後咱們就能夠把業務邏輯與業務實體移到一塊。
而後把屬於業務實體的邏輯遷移到實體類中。
上 面簡單單幾句話和兩張圖就把問題搞定了,可是實際遷移過程當中,風險是很是大的,若是沒有充分掌握重構知識,建議不要在正式產品代碼上嘗試。有些邏輯沒法歸 類到任何一個業務實體上的,就讓它留在原地,成爲一個領域服務。有些邏輯連領域服務都沒法歸類的,就讓它留在服務層,在堅持大方向的前提下,細節靈活處 理,由於每次重構都可以讓你對將來的路看到更清楚,極可能下一次重構後你就會爲這些流浪邏輯找到合適的家了。全部的建議都只是建議,最終決定權在你的手 上,受苦的人始終都是你本身。
一個事實是:掌握的編程思想越多,那麼搬遷起來就越容易,假若編程功底太差,可能根本沒法搬動,不建議初學者進行這些危險的試驗,由於你必定會把事情搞砸的,若是你把事情搞砸了,請自我檢討一下是否是編程思想不太夠用。
上面的過程並不是要你一步到位,你能夠把代碼重構到任何一個時刻,等待後面知識跟上了,再進行下一步的重構。
到目前爲止,代碼仍是那些代碼,只是位置變了而已,因此若是嚴格按照重構手法進行,編譯、運行應該都是沒有問題的。就這樣,在現有生活沒有任何影響的狀況下,你已經爲你的職業生涯打開了另一扇門,不再怕編程技術拼不過那些年輕人了。
實際上,DDD只是讓你從新迴歸到面向對象編程,沒有什麼更加神奇的魔法,固然DDD超越面向對象的地方在於對類的設計提出了更多的指導方法。
2.2 使用DDD相關模式進行深刻重構
正式介紹一下DDD的分層架構,固然DDD的架構並不限於分層,如今還有六邊形架構、CQRS、EventSourcing等技術能夠選用,可是不要好高騖遠,先把分層架構掌握好。原版分層結構如圖5,改進版結構如圖4,圖4基本上就是圖3的各個層換了名字,而且UI能夠訪問基礎設施層。而圖4與圖5的區別在於,圖4是基礎設施依賴領域層,圖5是領域層依賴基礎設施層,這兩層的關係倒置了。這樣的倒置會更凸顯領域層的核心地位,也有人認爲這樣已經算是六邊形架構了。
DDD各層次職責解析:
用戶界面層:
原版----負責向用戶展示信息以及解釋用戶命令。
補充---- MVC中V和C都屬於UI層,V展示信息,C解析用戶命令。UI像地圖同樣把各個控制器關聯了起來。
應用層:
原版----很薄的一層,用來協調應用的活動。它不包含業務邏輯。它不保留業務對象的狀態,但它保有應用任務的進度狀態。
補充----協調應用的活動這句話太抽象了,我充實一下它:從數據訪問中獲取領域對象,調用領域對象的方法完成任務,而後再調用數據訪問代碼把領域對象的改變持久化。事務、權限檢查、記錄日誌、處理異常的職責也歸它管。這點和前面三層的服務層的職責實際上是同樣的。
領域層:
原版----本層包含關於領域的信息。這是業務軟件的核心所在。在這裏保留業務對象的狀態,對業務對象和它們狀態的持久化被委託給了基礎設施層。
補充----業務對象的持久化工做咱們已經提高到應用層了,通常狀況下,這層最好不要涉及資源庫的調用,可是並不絕對。資源庫的抽象要麼在領域層中,要麼提高到了「應用程序框架」,領域層是不會依賴基礎設施的。
基礎設施層:
原版----本層做爲其餘層的支撐庫存在。它提供了層間的通訊,實現對業務對象的持久化,包含對用戶界面層的支撐庫等做用。
補充----這層的職責包含了三層的數據訪問,可是並不表明UI層就能夠調用數據訪問的代碼,並且職責範圍擴充了,有人可能會把它看成存放公用代碼的地方,可是建議這裏只存放本項目公用的東西,若是可以跨項目公用的代碼應該放在一個叫作「應用程序框架」的項目來完成,每一個公司都應該有本身的應用程序框架。
對比一下三層分層與DDD分層:
a. UI層技術基本同樣,一些比較智能的綁定可能沒法進行了。
b. 服務層基本同樣。
d. 業務實體+業務邏輯 = 領域層
e. 若是三層架構不採用業務實體與數據表一致的作法,這層也是同樣。因爲內存結構與數據表結構之間存在阻抗失配,存取領域對象沒那麼簡單。
DDD模式: 聚合,實體,值對象,工廠,資源庫,模塊。若是徹底沒有DDD經驗,建議首先閱讀《領域驅動設計精簡版》一書,可快速掌握DDD的理念。
聚合:這是個難點,通常建議是要設計小聚合,聚合與聚合之間用ID關聯,不要直接引用。聚合包含實體,值對象。表達聚合的對象叫作聚合根。聚合內部全部對象的變動必須經過聚合根。聚合根的本質是一個實體。若是聚合要傳遞給其餘模塊(系統),通常不要直接傳遞聚合根,新建一個粗粒度的值對象來進行傳遞,即DTO對象,DTO也可由接收方創建,由接收方決定須要什麼數據,這樣就解耦了模塊間的關聯,至於數據庫中是否須要把這個DTO冗餘存儲,則看實際狀況。聚合的數據表設計原則:大表小類。即數據表採用粗粒度,聚合根內部使用細粒度對象,有可能的話,儘可能每一條數據表記錄就是一個聚合。
實體:特色是必需要有一個ID來標識本身,可包含值對象和其餘實體。
值對象:特色是個只讀對象,沒有ID標識。
工廠:因爲聚合的建立多是個很是麻煩的事情,用工廠來封裝這個複雜麻煩的過程。
資源庫:資源庫就是持久化聚合的地方,就是說數據存儲的最小粒度是聚合。可是數據查詢的需求可能很是靈活,實踐中這條規則有點僵化,通常使用是讀寫分離方案,就是寫的時候使用聚合對象,可是讀的時候能夠根據業務仔細創建一些查詢模型(QueryModel)進行讀取。至於數據庫是否須要分紅讀寫兩個模型,仍是要看實際狀況,在系統更復雜和須要更高性能的時候,數據庫的模型也要分紅兩個,不過它們之間的同步就比較麻煩了。領域事件、CQRS、Event Sourcing等技術就是用來解決這個麻煩的。
領域服務:總有一些須要多個聚合進行合做才能完成的業務,它們不能簡單地劃歸參與的其中一個聚合,要用一個領域服務來表達,注意領域服務不是應用層的服務。
模塊:如何劃分模塊,通常有橫向劃分和縱向劃分兩種,橫向劃分例如:實體模塊,工廠模塊,資源庫模塊。縱向劃分例如:商品模塊,訂單模塊,支付模 塊,每一個模塊內部都會具有聚合,資源庫,值對象等元素。通常的經驗是橫向劃分對項目沒有什麼幫助,縱向劃分能夠減小系統的複雜度。模塊間的交互在應用層進 行。
在重構的過程當中,用各類代碼壞味做爲切入點,DDD模式、設計模式等做爲方向,利用成熟的重構手法掌控重構過程,而後用SOLID設計原則評估你的重構成果。進行改動架構的重構,必需要整個團隊取得共識,而且須要掌握DDD的人增強代碼走查,確保一直走在正確的道路上。
再次強調,重構,是不少人的口頭禪,可是對整個系統架構進行重構,這是個高風險的大工程,必須在團隊中取得共識,並有明確的目的地和到達目的地的安全路徑。
至此,三層到DDD的轉換完成了。這是三層的終結,也是DDD的開端。路,還長。
comming soon...
comming...
comming...
comming...