天天看點

分層架構

最近在項目設計時采用了領域驅動設計,在觀摩《領域驅動設計》這本書時提到了layered architecture這個單詞,也就是分層體系架構,覺得自己有必要對此分層架構進行一次系統的複習,記錄下來以備後用

分層架構

      • 什麼是分層架構?
      • 分層架構的優劣
      • 詳解四層架構
        • 表現層
          • 門面層
        • 應用層
        • 領域層
          • 什麼是業務邏輯?
        • 基礎設施層
        • 綜合說明

什麼是分層架構?

按照字面意思了解,即将程式分成多層的一種架構模式。那需要分成幾層合适呢?Eric Evans建議分為四層,分别對應表現層,應用層,領域層以及基礎設施層。而最基本的是分層架構是三層,即表現層,領域層和資料持久層。

分層架構

三層架構

名稱 用途
表現層 複雜向使用者顯示資訊和解釋使用者指令,使用者可以是使用界面的人,也可以時另一台計算機
領域層 負責表達業務概念,業務狀态資訊以及業務規則,是整個系統的核心層
資料持久層 為領域層提供持久化服務,提供資料的查詢和存儲

四層架構

名稱 用途
表現層 與三層架構中表現層一緻
應用層 定義軟體要完成的任務,并且指揮表達領域概念的對象來解決問題,應用層要盡量簡單,不包含業務規則,隻為下一層的領域對象協調任務,配置設定工作
領域層 與三層架構中領域層一緻
基礎設施層 為上面各層提供通用化技術,為表現層繪制螢幕元件,為應用層傳遞消息,為領域層持久化對象

上述分層架構可以按照需要多分幾層,但是分層體系架構需要遵循每層中的任何元素都僅依賴于本層的其他元素或者其下層的元素,向上通訊必須經過間接的傳遞機制進行。

從書上截取的一個關于網上轉賬的四層架構案例:

分層架構

分層架構的優劣

分層架構的目的是通過關注點分離來降低系統的複雜度,同時滿足單一職責、高内聚、低耦合、提高可複用性和降低維護成本。

  • 單一職責:每一層隻負責一個職責,職責邊界清晰,如持久層隻負責資料查詢和存儲,領域層隻負責處理業務邏輯。
  • 高内聚:分層是把相同的職責放在同一個層中,所有業務邏輯内聚在領域層。這樣做有什麼好處呢?試想一下假如業務邏輯分散在每一層,修改功能需要去各層修改,測試業務邏輯需要測試所有層的代碼,這樣增加了整個軟體的複雜度和測試難度。
  • 低耦合:依賴關系非常簡單,上層隻能依賴于下層,沒有循環依賴。
  • 可複用:某項能力可以複用給多個業務流程。比如持久層提供按照還款狀态查詢信用卡的服務,既可以給申請信用卡做判斷使用,也可以給展示未還款信用卡使用。
  • 易維護:面對變更容易修改。把所有對外接口都放在對外接口層,一旦外部依賴的接口被修改,隻需要改這個層的代碼即可。

以上這些既是分層的好處也是分層的原則,大家在分層時需要遵循以上原則,不恰當的分層會違背了分層架構的初衷。

分層架構的缺點

  • 開發成本高:因為多層分别承擔各自的職責,增加功能需要在多個層增加代碼,這樣難免會增加開發成本。但是合理的能力抽象可以提高了複用性,又能降低開發成本。
  • 性能略低:業務流需要經過多層代碼的處理,性能會有所消耗。
  • 可擴充性低:因為上下層之間存在耦合度,所有有些功能變化可能涉及到多層的修改。

詳解四層架構

每個邏輯層之間有着明确的職責劃分。

表現層

為外部使用者提供了通路界面與資料展示。主要職責為接收使用者指令對底層資料進行修改或展示。

詳解:

  • 典型的使用者是人類使用者,但是也可能是别的計算機系統。例如如果ERP系統要通路我們的系統擷取資訊,它也是一種使用者。
  • 不同類型的使用者需要不同形式的使用者接口,例如為人類使用者提供Web界面和手機App,為ERP軟體使用者提供REST服務接口。
  • 不同類型的使用者需要不同形式的資料表示,包括表現形式的不同(XML、JSON、HTML)和内容的不同(例如手機App中呈現的資料内容往往比Web頁面中呈現的少)。
  • 使用者接口層對應用層進行封裝,使用者接口層的操作與應用層上定義的操作通常是一一對應的關系。使用者接口層從外部使用者處接受輸入,轉換成應用層方法的參數形式,調用應用層方法将任務交由底層系統執行,并将傳回結果轉換成合适的形式傳回給外部使用者。
  • 使用者界面層的典型任務是下面三個:校驗——校驗外部客戶輸入的資料是否合法;轉換——将外部客戶的輸入轉換成對底層系統的方法調用參數,以及将底層系統的調用結果轉換成外部客戶需要的形式;轉發——将外部客戶的請求轉發給底層系統。

有時候,為了某些需要,我們可以從使用者接口層中分離出一個亞層,可命名為門面層(Facade)。位于真正的使用者接口層和應用層之間。

門面層

門面層隔離前台和背景系統,定義特定于使用者接口層的資料結構,從背景擷取資料内容并轉化為使用者接口層的資料形式。

從使用者接口層中分離出專門的門面層,具有下面的優勢:

  • 使得使用者接口層能夠獨立于背景系統,與背景系統并行開發。

    使用者接口層通過門面層接口與應用層和領域層解耦,意味着使用者接口層可以獨立開發,不必等待背景系統的完成,亦不受背景系統重構的影響,在需求調研階段系統原型出來并得到使用者确認之後,就可以開始使用者接口層的開發了。可以根據界面原型定義使用者接口層需要的資料結構,該資料結構與底層資料結構解耦,不需要知道底層資料類型和資料之間的關聯關系。将底層資料和界面資料連接配接起來并互相轉換是門面層實作類的職責,這方面工作可以等待前背景系統分别完成之後進行。

  • 使得分布式部署成為可能。

    如果沒有門面層的隔離,使用者接口層隻能直接使用領域層的領域對象作為自己的資料展現結構。這樣我們就不能将系統進行分布式部署,将使用者接口層和背景系統(領域層、應用層等)分别部署到不同的伺服器上。因為在JPA和Hibernate等技術實作中,領域實體綁定到目前伺服器的持久化上下文中,必須脫管之後才能夠跨越JVM進行傳輸。更大的問題是事務問題,事務要跨越伺服器的邊界,複雜性增加,性能嚴重下降。門面層的存在使得實體和事務都限制在背景系統,不需要擴充到前台伺服器。

  • 避免Hibernate中“會話已關閉”的問題,消除成本巨大的“Open Session in View”模式的需要。

    在采用JPA或Hibernate作為持久化手段的系統中存在臭名昭著的“會話已關閉”問題,對付這一問題的主要手段是Open Session in View這一存在潛在性能問題的方案。如果不采用門面層隔離背景資料結構,在前端展現資料需要通路實體的延遲初始化屬性時就會遇到“會話已關閉”問題,而采用Open Session in View模式處理這個問題就意味着事務不是在後端完成而是擴充到前端使用者接口層,在大通路量的網站上會遭遇嚴重的性能問題并降低吞吐量。采用門面模式的話,有關聯關系的資料在背景拼裝完畢再一次性傳回給前端,事務局限在後端範圍,不再有“會話已關閉”和性能問題。

門面層說明:

  • 門面層特定于使用者接口層,由使用者接口層定義和控制(包括操作和資料的形式和内容),這意味着需要為不同類型的使用者接口層開發專門的門面層。
  • 查詢結果通常以資料傳輸對象(DTO)的形式表示。DTO的結構由使用者接口層而不是後端決定,代表前端需要的資料形式,與底層資料結構脫耦。
  • 通過門面層實作類通路後端的應用層。實作類将後端資料拼裝為DTO并傳回給前端,它可以将資料裝配職責委托給專門的Assembler工具類去執行。
  • 在分布式系統中,可以在前端和後端分别部署門面層。前後端的門面層接口相同,但後端的門面層實作類負責資料裝配和釋出,前端的門面層實作類負責通過某種通信機制(Web Service等)與後端門面層通訊,擷取後者裝配好的資料。傳輸過程中DTO可能序列化為JSON或XML等形式。

應用層

應用層定義系統的業務功能,并指揮領域層中的領域對象實作這些功能。

應用層是整個系統的功能外觀,封裝了領域層的複雜性并隐藏了其内部實作機制。

  • 應用層映射到系統用例模型,是系統用例模型在軟體中的反映。
  • 應用層接口描述了系統的全部功能,意味着系統用例模型中的所有用例都可以在應用層接口中找到對應的方法。
  • 應用層實作類不實作業務邏輯,它通過排列組合領域層的領域對象來實作用例,它的職責可表示為“編排和轉發”,即将它要實作的功能委托給一個或多個領域對象來實作,它本身隻負責安排工作順序和拼裝操作結果。

領域層

領域層實作業務邏輯。

什麼是業務邏輯?

什麼是業務邏輯?業務邏輯就是存在于問題域即業務領域中的實體、概念、規則和政策等,與具體的實作技術無關,主要包含下面的内容:

  • 業務實體(領域對象)。例如銀行儲蓄領域中的賬戶、信用卡等等業務實體。
  • 業務規則。例如借記卡取款數額不得超過賬戶餘額,信用卡支付不得超過授信金額,轉賬時轉出賬戶餘額減少的數量等于轉入賬戶餘額增加的數量,取款、存款和轉賬必須留下記錄,等等。
  • 業務政策。例如機票預訂的超訂政策(賣出的票的數量稍微超過航班座位的數量,以防有些旅客臨時取消登機導緻座位空置)等。
  • 完整性限制。例如賬戶的賬号不得為空,借記卡餘額不得為負數等等。本質上,完整性限制是業務規則的一部分。
  • 業務流程。例如,“線上訂購”是一個業務流程,它包括“使用者登入-選擇商品-結算-下訂單-付款-确認收貨”這一系列流程。

對領域層的進一步說明如下:

  • 領域層映射到領域模型,是問題域的領域模型在軟體中的反映。
  • 包含實體、值對象和領域服務等領域對象,通常這些領域對象和問題域中的概念實體一一對應,具有相同或相似的屬性和行為。
  • 在實體、值對象和領域服務等領域對象的方法中封裝實作業務規則和保證完整性限制(這一點是與CRUD模式相比最明顯的差别,CRUD中的領域對象沒有行為)。
  • 領域對象在實作業務邏輯上具備堅不可摧的完整性,意味着不管外界代碼如何操作,都不可能建立不合法的領域對象(例如沒有賬戶号碼或餘額為負數的借記卡對象),亦不可能打破任何業務規則(例如在多次轉賬之後,錢憑空丢失或憑空産生)。
  • 領域對象的功能是高度内聚的,具有單一的職責,任何不涉及業務邏輯的複雜的組合操作都不在領域層而在應用層中實作。
  • 領域層中的全部領域對象的總和在功能上是完備的,意味着系統的所有行為都可以由領域層中的領域對象組合實作。

基礎設施層

基礎設施層為其餘各層提供技術支援。

基礎設施層是系統中的技術密集部分。它為領域層、應用層的業務服務(例如持久化、消息通信等等)提供具體的技術支援,使用者接口層通常使用特定的表示層架構(例如SpringMVC、Struts或Tapestry)實作,但有需要時也可以申請技術設施層提供專門的技術支援。

一些例子:

  • 領域層需要持久化服務,在DDD中,領域層通過倉儲(Repository)接口定義持久化需求,基礎設施層通過采用JDBC、JPA、Hibernate、NoSQL等技術之一實作領域層的倉儲接口,為領域層提供持久化服務。
  • 領域層需要消息通知服務,在領域層中定義了一個NotificationService領域服務接口,基礎設施層通過采用手機短信、電子郵件、Jabber等技術實作NotificationService領域服務接口,為領域層提供消息通知服務。
  • 使用者接口層需要一個對象序列化服務,将任何JavaBean序列化為JSON字元串,可以在使用者接口層定義一個ObjectSerializer服務接口,基礎設施層通過采用Gson實作這一接口,為使用者接口層提供對象序列化服務。

以上例子都是滿足依賴倒置原則,通過控制反轉的方式為高層子產品提供低層服務,在實踐中,可以通過Spring等IoC容器将基礎設施層的實作類執行個體進行依賴注入。

基礎設施層的典型實作形式是提供一個一個的類,這些類使用某些專有的技術實作其餘各層(主要是領域層)定義的接口,例如提供一個領域層的倉儲接口的實作類,使用Hibernate實作持久化,以及提供領域層的通知接口的實作類,使用ActiveMQ廣播領域層中發生的事件,等等。

基礎設施層也被稱為資料源層或資料通路層。這些名稱的一個缺點是給讀者一個強烈的暗示:基礎設施層隻負責資料庫通路。雖然資料庫通路是基礎設施層的職責之一,但基礎設施層的負責範圍比單純資料庫通路寬廣的多,它實作了系統的全部技術性需求,例如上面例子中的通知服務和對象序列化服務,等等。

綜合說明

  • 在四層架構中,領域層和應用層純粹表達業務意圖和機制,不包含任何技術邏輯;而基礎設施層和使用者接口層純粹提供技術實作,不包含任何業務邏輯。在業務和技術之間存在清晰的關注點分離。
  • 應用層定義系統的全部業務功能,領域層具體實作這些功能。領域層“動于内”,應用層“形諸外”。
  • 應用層和領域層合在一起代表了整個業務系統,具備概念上的完整性(包含了全部領域概念,實作了全部的業務行為),但不具備實作上的完整性(沒有基礎設施層的技術支援,系統不具備可運作性;沒有使用者接口層支援,系統不具備可通路性)。
  • 所有業務邏輯都在領域層實作,業務邏輯洩漏到應用層是一個錯誤,洩露到基礎設施層或使用者接口層是嚴重錯誤(在使用者接口層中實作業務邏輯是采用CRUD模式的常犯的典型錯誤)。
  • 領域層在履行職責的過程中如果需要技術支援,則在領域層中定義一個表達業務意圖的領域服務接口,交由基礎設施層采用各種具體技術去實作這一接口。保證領域層(和應用層)不被各種具體技術污染是邏輯分層的第一要務。
  • 判斷業務層(領域層和應用層)是否被具體技術污染一個友善的方式是檢查它們是否有對具體技術架構(例如Spring和Hibernate)的編譯時依賴。業務層代碼應該隻依賴于JDK(java.)、Java規範(javax.),以及一些被廣泛使用的類庫如commons-lang、Guava、SLF4J、JodaTime等,這些類庫本質上可視為對JDK的補充,不是一種具體技術架構。
  • 應用層和門面層的差別:應用層屬于後端,門面層屬于前端。應用層方法的參數和傳回值可以包含領域對象,門面層方法的參數通常是字元串和數字等簡單值,傳回值是簡單值或DTO。以轉賬操作為例子,應用層中的方法簽名是這樣的:void transferFund(Account from, Account to, Money amount, Date transferTime),門面層中的方法簽名是這樣的:void transferFund(String fromAccountNumber, String toAccountNumber, BigDecimal amount, String currency, Date transferTime)。在門面層的實作類中,負責根據賬戶号碼從倉儲中擷取Account對象,将amount和currency拼裝成Money對象,然後以這些對象和transferTime為參數通路應用層中的相應方法。
  • 領域層中的領域對象具有領域通用性或行業通用性,意味着可以在基本相同的領域層上建立不同的應用層(就像三極管、二極管、電容、電阻等在電子工業領域具有通用性,可以用來組裝收音機、錄音機、電視機等不同應用),應用層是應用特定或客戶特定的,隻為特定的應用或客戶定制。相比應用層,領域層對象具備高度的可重用性。例如一套完備的使用者管理領域層子產品可以被OA、ERP、CRM、HRM、MES等多個應用重用。因為領域對象中封裝了業務邏輯,這種重用是非常有價值的。
  • 可以基于相同的應用層建立不同的使用者接口層,例如Web頁面,手機App、BI報表、RESTful Web Service等等。

參考:https://www.cnblogs.com/legend886/articles/6889811.html

繼續閱讀