說一說Web開發中兩種常用的分層架構及其對應的程式碼模型
阿新 • • 發佈:2020-05-07
昨天妹子讓我幫她解決個問題,本以為可以輕鬆搞定,但是開啟他們專案的一瞬間,我頭皮發麻。本身功能不多的一個小專案,解決方案裡竟然有幾十個類庫。僅僅搞明白各個類庫的作用,程式碼層次之間的引用關係就花了一個多小時。
顯然可能他們專案結構的程式碼模型出了問題,設計混亂,不容易上手。
專案中一個好的的程式碼模型一定是簡單、清晰、明瞭、易於上手的。它總是會讓人用起來很舒服,它可以讓專案團隊成員更好地理解程式碼,根據程式碼規範實現團隊協作;它可以讓各層的邏輯互不干擾、分工協作、各據其位、各司其職,避免不必要的程式碼混淆。它可以讓我們的程式碼擴充套件性很好,可以讓系統的可維護性更好......
而程式碼模型的搭建跟專案的分層架構有關係,絕大多數專案開發中都會會採用分層開發,它有著分散關注、鬆散耦合、邏輯複用、標準定義、擴充套件性強等眾多好處。而在分層架構中最常用的有兩種:三層架構和DDD(領域驅動)分層架構。我們選擇的分層方式也決定了我們的專案結構中的程式碼模型。
### 1.三層架構
三層架構是一種嚴格分層模式,而嚴格分層架構模式的特點是上層只能訪問相鄰的下層,其他層次間的呼叫都不允許。它把職責劃分為介面展示、業務邏輯、資料訪問三層,還有一個業務實體,前面三層都要依賴它,所以它並不構成一個層。
![](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%20%281%29.png)
三層架構是一種面向過程的程式設計思想,它有幾個特點
* 業務實體類中基本上只有屬性沒有方法。
* 業務邏輯層的類基本上只有方法沒有屬性。
在使用三層架構開發的時候我們通常會直接將資料表結構對映為業務實體類。這樣的好處是隻需要理解一套模型,以至於市場上也產生了一系列的程式碼生成工具可以一鍵生成實體和增刪改查的程式碼。但對於複雜點的業務,這樣做也會產生很多的問題。
而當業務不再是簡單的增刪查改時,我們可以在三層架構的基礎上有個簡單的變形:提取一個服務層出來,用來組合模組間的互動,還為業務邏輯層提供了一個防腐層,可以把記錄日誌、驗證許可權、處理異常等職責分配給服務層。
![](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84%E6%94%B9%E8%BF%9B%20%282%29.png)
### 2. DDD分層架構
三層架構有一個顯著缺點就是它的記憶體模型不是基於業務模型而是是基於資料庫模型建立的。而很多時候我們的業務模型並不能和我們的資料庫模型完全吻合。
例如:如果你的資料庫模型的粒度很小。有些業務就需要連線多張資料庫表才能實現。而如果資料庫模型的粒度很大(這是大部分專案的選擇),程式碼的質量(重用性、穩定性、擴充套件性)就很差。由於沒有從業務的角度去仔細定義每一個物件,每個人會根據自己的需要建立各種QueryModel或ViewModel(相信三層架構用久了的同學都會遇到這個問題)。
而且現在很多大型系統都會採用分散式的架構,如現在的微服務架構。服務內不僅僅是簡單的增刪查改,會有更多的與其它服務互動的內容,而服務的拆分也是以業務模型為導向的。
這個時候DDD(領域驅動)分層架構就會更有優勢。
![依賴倒置的DDD分層架構](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/DDD.png)
DDD分層架構主要包含四層:使用者介面層、應用層、領域層、基礎層。
* 使用者介面層:面向前端提供服務適配,面向資源層提供資源適配。這一層聚集了介面適配相關的功能。
* 應用層:位於領域層的上一層,理論上不應該有業務規則或邏輯。主要用來實現服務組合和編排,協作完成業務操作,負責處理業務用例的執行順序以及結果的拼裝,以粗粒度的服務通過 API 閘道器向前端釋出提供安全認證、許可權校驗、事務控制、傳送或訂閱領域事件等功能。
* 領域層:實現領域的核心業務邏輯。這一層聚集了領域模型的聚合、聚合根、實體、值物件、領域服務和事件等領域物件,以及它們組合所形成的業務能力。
* 基礎層:貫穿所有層的,為其它各層提供通用的技術和基礎服務,包括第三方工具、驅動、訊息中介軟體、閘道器、檔案、快取以及資料庫等。比較常見的功能還是提供資料庫持久化。基礎層包含基礎服務,它可以採用依賴倒置設計,封裝基礎資源服務,實現應用層、領域層與基礎層的解耦,降低外部資源變化對應用的影響。
![DDD分層架構](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/DDD%E5%86%85%E5%AE%B9.png)
DDD分層架構也分為嚴格分層架構和鬆散分層架構,在嚴格分層架構中,領域服務只能被應用服務呼叫,而應用服務只能被使用者介面層呼叫,服務是逐層對外封裝或組合的,依賴關係清晰。而在鬆散分層架構中,領域服務可以同時被應用層或使用者介面層呼叫,服務的依賴關係比較複雜且難管理。因此我個人推薦使用嚴格的分層架構
### 3.如何選擇三層架構還是DDD分層架構
對於專案開發中應該如何選擇我們可以基於兩點考慮:系統本身的業務複雜度和團隊人員能力。
#### 3.1 系統本身的業務複雜度
如果你們的系統只是個簡單的小系統有或者系統本身業務並不複雜,基本都是些增刪改查。那麼三層架構將是你良好的選擇。它會讓你的開發變得簡單,會大大提高你的開發效率,而且這種分層架構的學習成本很低,新人簡單熟悉就可以很容易的上手。對於簡單的系統使用DDD分層,反而增加了系統的複雜度。
而如果你們的系統很複雜或者對於一些成長性的網站系統(一開始很小,隨著業務發展慢慢壯大的系統),那麼你可以考慮使用DDD分層架構,DDD的思想可以讓你更好的對業務進行建模,這種分層架構會讓你的系統擴充套件性很好,在網站壯大的過程中,單體系統必然會向分散式系統進行發展,而DDD的思想可以對服務進行很好的拆分,如當下的微服務架構,DDD的思想就可以幫助我們很好的定義服務的邊界。
#### 3.2 團隊人員能力
團隊人員的能力仍然是選擇要考慮的因素。因為DDD對於人員的能力要求相對於三層架構要高。它需要團隊的人員要有一個良好的邏輯思想和建模能力,而且DDD的學習成本也要高一些。如果你的團隊成員能力一般或者以入行不久的新人為主,或者你的團隊人員流動性較大的話,也是不建議使用DDD的。這種情況下使用三層架構會更好一些。
總有些人會覺得DDD的概念“高大上”,因此為了使用DDD而使用DDD,更有甚至,根本都沒有真正弄明白DDD,就開始使用,最終搞的個四不像,不僅沒有解決問題,反而徒增了系統複雜度,拉低效能和效率,其實真正專案中改如何選擇,應該結合你的團隊和系統來,權衡利弊綜合考慮。簡單、優雅、方便、快捷的解決問題豈不是更好?
### 4.兩種分層架構對應的程式碼模型
一旦選定了分層架構,專案中所對應的程式碼模型也就確定了。我們以.net為例(java只需要把程式集當成jar包來看就可以了),推薦下面這兩種程式碼模型。
#### 4.1 三層架構
![簡單的三層架構](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/%E7%AE%80%E5%8D%95%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84.png)
這是最簡單的三層架構程式碼模型,業務邏輯層,資料訪問層,應用介面層(介面層,介面層可以是mvc,也可以是webapi。(因為現在很多專案都是前後端分離,服務端開發人員不需要寫頁面,所以就沒有寫MVC,介面層我也改叫使用者介面層了)。。當然現在幾乎是沒有人這麼用的。因為這樣做的依賴性很高。不利於擴充套件。因此我們要引入依賴倒置的概念。因此我們的結構需要做如下變形
![依賴倒置的三層架構](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/%E4%BE%9D%E8%B5%96%E5%80%92%E7%BD%AE%E7%9A%84%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84.png)
這種結構在簡單三層的基礎上對業務邏輯層和資料訪問層引入了其抽象層,這樣就很好的將層與層之間的依賴關係進行了解耦。
比如,我曾經遇到多個專案資料庫從MSSQL轉到MySql。在三層架構中,其實大量的邏輯都應該被封裝在業務邏輯層。這個時候我們是需要把DAL換成MySql的DAL,業務邏輯不需要任何改動。如果是最簡單的三層架構那種絕對依賴關係,我們必然要改動業務邏輯層以接入新的DAL。而這種依賴倒置的層次結構則不需要。
由於三層架構中同層引用時應該避免的。業務邏輯也是基於資料庫模型建立的,而有些業務則需要組合多個業務來實現,為了實現業務邏輯程式碼複用有些可以採用繼承和多型的方式來實現。有些時候則需要再引入一個服務層(防腐層)來組合模組間的互動。也可以把記錄日誌、驗證許可權、處理異常等職責分配給這一層。
![完整的三層架構](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/%E5%AE%8C%E5%96%84%E7%9A%84%E4%B8%89%E5%B1%82%E6%9E%B6%E6%9E%84.png)
很多專案可能還要加個工具層,用來放一些常用的工具類。但是工具類這個東西,**與專案有關的可以放在這裡,如果是多專案之間可以複用的,最好用更專業的做法:單獨管理維護,打包成Nuget包(maven包),由各個專案進行呼叫**。
#### 4.2 DDD分層程式碼模型
按照 DDD 分層架構模型設計出來的程式碼模型應該長什麼樣子呢?
![](https://hunter-image.oss-cn-beijing.aliyuncs.com/%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84/DDD%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84.png)
如上圖所示:
1. 使用者介面層
* Controller:提供較粗粒度的呼叫介面,將使用者請求委派給一個或多個應用服務進行處理
* DTO:資料傳輸的載體,內部不存在任何業務邏輯,我們通過 DTO 把內部的領域物件與外界隔離。
* Mapper: 實現 DTO 與領域物件之間的相互轉換和資料交換。
2. 應用層
* Event:存放事件相關程式碼,可以進行事件的釋出和處理。事件釋出和訂閱放在應用層,事件相關的業務邏輯放在領域層。
* Service:對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。應用服務主要實現服務組合和編排,是一段獨立的業務邏輯。
3. 領域層
* Aggregate:是Entity、Event、Service、Repository的根目錄,聚合內實現高內聚的業務邏輯,可以根據實際專案中業務的聚合名稱命名。在聚合內定義聚合根、實體和值物件以及領域服務之間的關係和邊界。如果我們的專案需要進行微服務的拆分,那麼一個聚合裡的內容可以拆分為一個單獨的服務。
* Entity:用來存放聚合根、實體、值物件以及工廠模式(Factory)相關程式碼。實體類採用充血模型,同一實體相關的業務邏輯都在實體類程式碼中實現。跨實體的業務邏輯程式碼在領域服務中實現。
* Event:存放事件實體以及與事件活動相關的業務邏輯程式碼。
* Service:存放領域服務程式碼。一個領域服務是多個實體組合出來的一段業務邏輯。領域服務封裝多個實體或方法後向上層提供應用服務呼叫。
* Repository:存放所在聚合的查詢或持久化領域物件的程式碼,通常包括倉儲介面和倉儲實現方法。為了方便聚合的拆分和組合,最好一個聚合對應一個倉儲。
4. 基礎設施層
* Config:主要存放一些配置相關程式碼。
* Util:其它諸如資料庫、快取、檔案,第三方類庫相關的程式碼可以在這個目錄下建立子目錄。
在DDD的程式碼模型中需要注意的是:
* 分層的概念一定要清晰,明確各層的職責。
* 聚合的程式碼邊界一定要清晰,聚合之間一定是鬆耦合低關聯的。
### 小結
毫無疑問,專案中選擇合適的分層架構並設計一個優秀的程式碼模型有著巨大的好處,但實際上無論是三層架構還是DDD分層架構都沒有明確的規定其標準的程式碼模型。因此以上兩種程式碼模型僅供大家參考。
而經常會有些設計者在設計的時候總喜歡“炫技”,設計出來的程式碼模型“深奧複雜”、晦澀難懂,美其名曰“高大上”。熟不知大道至簡,優秀的設計是用簡單的方式解決複雜的問題,而不是把簡單的問題複雜化,在解決問題的基礎上,簡單實用才是正途!
最後,希望每個專案都能有一個好的設計人員,結合實際情況,設計出一個好的程式碼模型,利己