說一說Web開發中兩種常用的分層架構及其對應的代碼模型

昨天妹子讓我幫她解決個問題,本以為可以輕鬆搞定,但是打開他們項目的一瞬間,我頭皮發麻。本身功能不多的一個小項目,解決方案里竟然有幾十個類庫。僅僅搞明白各個類庫的作用,代碼層次之間的引用關係就花了一個多小時。

顯然可能他們項目結構的代碼模型出了問題,設計混亂,不容易上手。

項目中一個好的的代碼模型一定是簡單、清晰、明了、易於上手的。它總是會讓人用起來很舒服,它可以讓項目團隊成員更好地理解代碼,根據代碼規範實現團隊協作;它可以讓各層的邏輯互不干擾、分工協作、各據其位、各司其職,避免不必要的代碼混淆。它可以讓我們的代碼擴展性很好,可以讓系統的可維護性更好……

而代碼模型的搭建跟項目的分層架構有關係,絕大多數項目開發中都會會採用分層開發,它有着分散關注、鬆散耦合、邏輯復用、標準定義、擴展性強等眾多好處。而在分層架構中最常用的有兩種:三層架構和DDD(領域驅動)分層架構。我們選擇的分層方式也決定了我們的項目結構中的代碼模型。

1.三層架構

三層架構是一種嚴格分層模式,而嚴格分層架構模式的特點是上層只能訪問相鄰的下層,其他層次間的調用都不允許。它把職責劃分為界面展示、業務邏輯、數據訪問三層,還有一個業務實體,前面三層都要依賴它,所以它並不構成一個層。

三層架構是一種面向過程的編程思想,它有幾個特點

  • 業務實體類中基本上只有屬性沒有方法。
  • 業務邏輯層的類基本上只有方法沒有屬性。

在使用三層架構開發的時候我們通常會直接將數據表結構映射為業務實體類。這樣的好處是只需要理解一套模型,以至於市場上也產生了一系列的代碼生成工具可以一鍵生成實體和增刪改查的代碼。但對於複雜點的業務,這樣做也會產生很多的問題。

而當業務不再是簡單的增刪查改時,我們可以在三層架構的基礎上有個簡單的變形:提取一個服務層出來,用來組合模塊間的交互,還為業務邏輯層提供了一個防腐層,可以把記錄日誌、驗證權限、處理異常等職責分配給服務層。

2. DDD分層架構

三層架構有一個顯著缺點就是它的內存模型不是基於業務模型而是是基於數據庫模型建立的。而很多時候我們的業務模型並不能和我們的數據庫模型完全吻合。

例如:如果你的數據庫模型的粒度很小。有些業務就需要連接多張數據庫表才能實現。而如果數據庫模型的粒度很大(這是大部分項目的選擇),代碼的質量(重用性、穩定性、擴展性)就很差。由於沒有從業務的角度去仔細定義每一個對象,每個人會根據自己的需要建立各種QueryModel或ViewModel(相信三層架構用久了的同學都會遇到這個問題)。

而且現在很多大型系統都會採用分佈式的架構,如現在的微服務架構。服務內不僅僅是簡單的增刪查改,會有更多的與其它服務交互的內容,而服務的拆分也是以業務模型為導向的。

這個時候DDD(領域驅動)分層架構就會更有優勢。

依賴倒置的DDD分層架構

DDD分層架構主要包含四層:用戶接口層、應用層、領域層、基礎層。

  • 用戶接口層:面向前端提供服務適配,面向資源層提供資源適配。這一層聚集了接口適配相關的功能。
  • 應用層:位於領域層的上一層,理論上不應該有業務規則或邏輯。主要用來實現服務組合和編排,協作完成業務操作,負責處理業務用例的執行順序以及結果的拼裝,以粗粒度的服務通過 API 網關向前端發佈提供安全認證、權限校驗、事務控制、發送或訂閱領域事件等功能。
  • 領域層:實現領域的核心業務邏輯。這一層聚集了領域模型的聚合、聚合根、實體、值對象、領域服務和事件等領域對象,以及它們組合所形成的業務能力。
  • 基礎層:貫穿所有層的,為其它各層提供通用的技術和基礎服務,包括第三方工具、驅動、消息中間件、網關、文件、緩存以及數據庫等。比較常見的功能還是提供數據庫持久化。基礎層包含基礎服務,它可以採用依賴倒置設計,封裝基礎資源服務,實現應用層、領域層與基礎層的解耦,降低外部資源變化對應用的影響。

DDD分層架構

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 三層架構

簡單的三層架構

這是最簡單的三層架構代碼模型,業務邏輯層,數據訪問層,應用接口層(界面層,界面層可以是mvc,也可以是webapi。(因為現在很多項目都是前後端分離,服務端開發人員不需要寫頁面,所以就沒有寫MVC,界面層我也改叫用戶接口層了)。。當然現在幾乎是沒有人這麼用的。因為這樣做的依賴性很高。不利於擴展。因此我們要引入依賴倒置的概念。因此我們的結構需要做如下變形

依賴倒置的三層架構

這種結構在簡單三層的基礎上對業務邏輯層和數據訪問層引入了其抽象層,這樣就很好的將層與層之間的依賴關係進行了解耦。

比如,我曾經遇到多個項目數據庫從MSSQL轉到MySql。在三層架構中,其實大量的邏輯都應該被封裝在業務邏輯層。這個時候我們是需要把DAL換成MySql的DAL,業務邏輯不需要任何改動。如果是最簡單的三層架構那種絕對依賴關係,我們必然要改動業務邏輯層以接入新的DAL。而這種依賴倒置的層次結構則不需要。

由於三層架構中同層引用時應該避免的。業務邏輯也是基於數據庫模型建立的,而有些業務則需要組合多個業務來實現,為了實現業務邏輯代碼復用有些可以採用繼承和多態的方式來實現。有些時候則需要再引入一個服務層(防腐層)來組合模塊間的交互。也可以把記錄日誌、驗證權限、處理異常等職責分配給這一層。

完整的三層架構

很多項目可能還要加個工具層,用來放一些常用的工具類。但是工具類這個東西,與項目有關的可以放在這裡,如果是多項目之間可以復用的,最好用更專業的做法:單獨管理維護,打包成Nuget包(maven包),由各個項目進行調用

4.2 DDD分層代碼模型

按照 DDD 分層架構模型設計出來的代碼模型應該長什麼樣子呢?

如上圖所示:

  1. 用戶接口層
  • Controller:提供較粗粒度的調用接口,將用戶請求委派給一個或多個應用服務進行處理
    • DTO:數據傳輸的載體,內部不存在任何業務邏輯,我們通過 DTO 把內部的領域對象與外界隔離。
    • Mapper: 實現 DTO 與領域對象之間的相互轉換和數據交換。
  1. 應用層

    • Event:存放事件相關代碼,可以進行事件的發佈和處理。事件發佈和訂閱放在應用層,事件相關的業務邏輯放在領域層。

    • Service:對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。應用服務主要實現服務組合和編排,是一段獨立的業務邏輯。

  2. 領域層

    • Aggregate:是Entity、Event、Service、Repository的根目錄,聚合內實現高內聚的業務邏輯,可以根據實際項目中業務的聚合名稱命名。在聚合內定義聚合根、實體和值對象以及領域服務之間的關係和邊界。如果我們的項目需要進行微服務的拆分,那麼一個聚合里的內容可以拆分為一個單獨的服務。
    • Entity:用來存放聚合根、實體、值對象以及工廠模式(Factory)相關代碼。實體類採用充血模型,同一實體相關的業務邏輯都在實體類代碼中實現。跨實體的業務邏輯代碼在領域服務中實現。
    • Event:存放事件實體以及與事件活動相關的業務邏輯代碼。
    • Service:存放領域服務代碼。一個領域服務是多個實體組合出來的一段業務邏輯。領域服務封裝多個實體或方法後向上層提供應用服務調用。
    • Repository:存放所在聚合的查詢或持久化領域對象的代碼,通常包括倉儲接口和倉儲實現方法。為了方便聚合的拆分和組合,最好一個聚合對應一個倉儲。
  3. 基礎設施層

    • Config:主要存放一些配置相關代碼。
    • Util:其它諸如數據庫、緩存、文件,第三方類庫相關的代碼可以在這個目錄下建立子目錄。

在DDD的代碼模型中需要注意的是:

  • 分層的概念一定要清晰,明確各層的職責。
  • 聚合的代碼邊界一定要清晰,聚合之間一定是松耦合低關聯的。

小結

毫無疑問,項目中選擇合適的分層架構並設計一個優秀的代碼模型有着巨大的好處,但實際上無論是三層架構還是DDD分層架構都沒有明確的規定其標準的代碼模型。因此以上兩種代碼模型僅供大家參考。

而經常會有些設計者在設計的時候總喜歡「炫技」,設計出來的代碼模型「深奧複雜」、晦澀難懂,美其名曰「高大上」。熟不知大道至簡,優秀的設計是用簡單的方式解決複雜的問題,而不是把簡單的問題複雜化,在解決問題的基礎上,簡單實用才是正途!

最後,希望每個項目都能有一個好的設計人員,結合實際情況,設計出一個好的代碼模型,利己利人!

Tags: