程式碼分層的設計之道

樑桂釗發表於2019-03-04

原文地址:樑桂釗的部落格

部落格地址:blog.720ui.com

歡迎轉載,轉載請註明作者及出處,謝謝!

分層思想,是應用系統最常見的一種架構模式,我們會將系統橫向切割,根據業務職責劃分。MVC 三層架構就是非常典型架構模式,劃分的目的是規劃軟體系統的邏輯結構便於開發維護。MVC:英文即 Model-View-Controller,分成模型層、檢視層、控制層。將頁面和業務邏輯分離,提高應用的可擴充套件性及可維護性。如圖所示。

MVC分層結構.png | left | 240x433

事實上,MVC 三層架構只是概念層面的指導思想,我們會將層次結構劃分的更加細緻。例如,傳統後端的 MVC 模式對於前後端的劃分界限比較模糊。一般情況下,前端開發人員負責編寫專案的靜態頁面,包括 HTML 頁面、CSS 樣式與 JavaScript 互動部分,並提供給服務端開發人員編寫檢視層業務,甚至有的專案直接讓前端開發人員完成檢視層的業務開發任務。這樣的開發模式造成的問題在於,前後端在開發過程中分工不明確,並且存在相互強依賴,前端開發人員需要關心服務端的業務,服務端開發人員也需要依賴前端的進度。並且隨著 Android、 IOS、 PC 以及 U3D 等多個客戶端加入,程式的開發成本與維護成本會指數級上升。為了提高開發效率,細化職責,前後端分離的需求越來越被重視。前後端分離在於服務端提供 API 介面,前端呼叫 AJAX 實現資料互動。如圖所示。

MVC三層模型.png | left | 428x314

此外,隨著資料儲存能力的不斷擴充套件(MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等),以及隨著微服務的流行與普及,我們經常通過 RPC(Dubbo、HSF、Thrift 等)依賴很多外部介面或 HTTP 呼叫第三方平臺。因此,我們需要一套細緻劃分的程式碼結構。此外,很多時候,我們在開發過程中,也並沒有把它們職責劃分開。例如,在程式碼結構中,我們將非常多的邏輯業務放在了 Controller 層,而只把 Service 作為資料透傳的途徑了。事實上,這個是不對的。無獨有偶,我們還會發現有的專案中在 Dao 層呼叫遠端服務,也有的會在 Service 層或者 Controller 層進行這樣的操作,由於不同研發同學的習慣不同,或者偷工取巧導致開發程式碼風格完全不同,程式碼層次結構混亂。

總結一下,MVC 三層架構只是概念層面的指導思想,我們會將層次結構劃分的更加細緻。現在,我們來深入探討“如何合理的設計程式碼分層,論程式碼分層的設計之道”。在筆者看來,合理的程式碼分層應該是這樣的。如圖所示。

程式碼分層 (1).jpg | left | 827x699
其中,資料持久層 承載了資料儲存和訪問的能力,它既與底層資料進行交換,包括 MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等,又通過 Pxoxy 的代理和包裝與遠端服務資料進行聯動。因此,在業務邏輯層呼叫時,它對底層的資料實現方式是無感知的,無論是哪種資料儲存方式,以及它是遠端資料,還是本地資料,都可以非常容易的呼叫。換句話說,我們需要將資料的查詢和更改操作限制在資料持久層,並只能被業務邏輯層訪問。

那麼,業務邏輯層 的職責是與__資料持久層__互動,對多個資料來源的操作進行聚合,並且提供組合複用的能力。此外,它也是業務通用能力的處理層,其中還包括快取方案、訊息監聽(MQ)、定時任務等等。此外,我們要將盡可能多的業務處理放在__業務邏輯層__,包括了引數校驗、資料轉換、異常處理等,而不是在 Controller 再去處理。

筆者認為:請求處理層具有三塊能力,一個是通過模板引擎渲染,例如 FreeMarket、Velocity 的頁面渲染,以及通過 Controller 層封裝的 RESTful API 的 HTTP 介面。如果專案中用到了 Dubbo、HSF、Thrift 等 RPC 服務,我們還需要提供對於的服務給上游的業務方使用,它通過 Service 來實現並暴露成 RPC 介面。這裡,Service 的命名是相對的,一般通過 Client 提供介面,通過 Service 實現具體的業務邏輯。

我們瞭解了邏輯結構,那麼,筆者認為比較清晰的物理程式碼結構應該是這樣的。

螢幕快照 2018-10-13 下午5.43.16.png | center | 604x1224

那麼,我們可以跨層級呼叫嗎?筆者認為:我們需要禁止跨層級呼叫,因為每個層級都自己的職責,並且對上層而言是透明的,就像 OSI 七層協議模型和 TCP/IP 四層協議模型一樣,只有將職責限制在自己的邊界內,整體層次結構才清晰明瞭。那麼對於同級呼叫,筆者認為在業務邏輯層是允許的,但是,要特別注意迴圈呼叫的產生。

現在,我們再橫向理解幾個領域模型:VO、BO、DO、DTO。這個概念,是由阿里編碼規約提到的,由於其業務非常複雜,因此為了更好地進行領域建模和模型隔離,提出了這幾個概念。其中,DO(Data Object)與資料庫表結構一一對應,通過 DAO 層向上傳輸資料來源物件。 而 DTO(Data Transfer Object)是遠端呼叫物件,它是 RPC 服務提供的領域模型。注意的是,對於 DTO 一定要保證其序列化,實現 Serializable 介面,並顯示提供 serialVersionUID,否則在反序列化時,如果 serialVersionUID 被修改,那麼反序列化會失敗。事實上,DO 和 DTO 唯一的區別在於,一個是本地資料來源的領域模型,一個是遠端服務的序列化領域模型。對於 BO(Business Object),它是業務邏輯層封裝業務邏輯的物件,一般情況下,它是聚合了多個資料來源的複合物件。那麼,VO(View Object) 通常是請求處理層傳輸的物件,它通過 Spring 框架的轉換後,往往是一個 JSON 物件。例如,你需要解決 Long 型別的資料精度丟失的問題(如果直接傳給 Web 端的話,在 Long 長度大於 17 位時會出現精度丟失),你就可以在 Controller 層通過 @ResponseBody 將返回資料自動轉換成 JSON 時,統一封裝成字串。

總結一下,分層思想,將系統橫向切割,根據業務職責劃分。劃分的目的是規劃軟體系統的邏輯結構便於開發維護。但是,隨著微服務的演變和不同研發的編碼習慣,往往導致了程式碼分層不徹底導致引入了“壞味道”。筆者試圖提出一個新的程式碼分層思路來規避和規範這種分層結構。

如果你有不同的想法和興趣,歡迎加我微信一起探討與交流喲~

(完,轉載請註明作者及出處。)

更多精彩文章,盡在「服務端思維」!

程式碼分層的設計之道

相關文章