【架構設計】你的應用該如何分層呢?

2023-01-05 12:01:13

前言

最近review公司的程式碼,發現現在整個程式碼層級十分混亂,一個service類的長度甚至達到了5000多行。而且各種分層模型DTO、VO亂用, 最終出現邏輯不清晰、各模組相互依賴、程式碼擴充套件性差、改動一處就牽一髮而動全身等問題。

我們在吸取了阿里巴巴的分層規範以及網上的一些經驗後,重新梳理總結了屬於我們專案的分層規範。

歡迎關注個人公眾號『JAVA旭陽』交流溝通

三層架構VS四層架構

我們公司原來的分層採用的是傳統的三層架構,比如在構建專案的時候,我們通常會建立三個目錄:Web、Service 和 Dao,它們分別對應了表現層、邏輯層還有資料存取層。

這樣導致一個很大的問題,隨著業務越來越複雜,邏輯層也就是service層越來越龐大,所以出現了前面說的5000多行的類,可想而知維護成本有多大。

參照阿里釋出的《阿里巴巴 Java 開發手冊 v1.4.0(詳盡版)》,我們可以將原先的三層架構細化成下面的樣子:

  • 終端顯示層:各端模板渲染並執行顯示的層。當前主要是 Velocity 渲染,JS 渲染, JSP 渲染,行動端展示等。
  • 開放介面層:將 Service 層方法封裝成開放介面,同時進行閘道器安全控制和流量控制等。
  • Web 層:主要是對存取控制進行轉發,各類基本引數校驗,或者不復用的業務簡單處理等。
  • Service 層:業務邏輯層。
  • Manager 層:通用業務處理層。主要實現下面的功能,1) 對第三方平臺封裝的層,預處理返回結果及轉化異常資訊,適配上層介面。2) 對 Service 層通用能力的下沉,如快取方案、中介軟體通用處理。3) 與 DAO 層互動,對多個 DAO 的組合複用。
  • DAO 層:資料存取層,與底層 MySQLOracleHbase 等進行資料互動。
  • 外部介面或第三方平臺:包括其它部門 RPC 開放介面,基礎平臺,其它公司的 HTTP 介面。

在這個分層架構中主要增加了 Manager 層,它可以將Service層中的一些通用能力比如操作快取、訊息佇列的操作下沉,也可以將通過feign呼叫其他服務的介面進行一層包裝,再提供給Service呼叫,這也就是所謂的防腐層。

VO、DTO、BO、DO區別

前面講解了整體的一個分層架構,那麼在不同的層級之間必然需要一些模型物件進行流轉傳遞,VO,BO,DO,DTO, 那麼他們之間有什麼區別呢?

  • VO(View Object):檢視物件,用於展示層,它的作用是把某個指定頁面(或元件)的所有資料封裝起來。
  • DTO(Data Transfer Object):資料傳輸物件,用於展示層與服務層之間的資料傳輸物件。
  • BO(Business Object):業務物件,把業務邏輯封裝為一個物件,這個物件可以包括一個或多個其它的物件。
  • DO(Domain Object):領域物件,阿里巴巴規範中引入,此物件與資料庫表結構一一對應,通過 DAO 層向上傳輸資料來源物件。
  1. VO和DTO什麼區別?

VO比較容易混淆的是DTODTO是展示層與服務層之間傳遞資料的物件,可以這樣說,對於絕大部分的應用場景來說,DTOVO的屬性值基本是一致的,而且他們通常都是POJO,那麼既然有了VO,為什麼還需要DTO呢?

例如服務層有一個getUser的方法返回一個系統使用者,其中有一個屬性是gender(性別),對於服務層來說,它只從語意上定義:1-男性,2-女性,0-未指定,而對於展示層來說,它可能需要用「帥哥」代表男性,用「美女」代表女性,用「祕密」代表未指定。說到這裡,可能你還會反駁,在服務層直接就返回「帥哥美女」不就行了嗎?對於大部分應用來說,這不是問題,但設想一下,如果需求允許客戶可以客製化風格,而不同風格對於「性別」的表現方式不一樣,又或者這個服務同時供多個使用者端使用(不同門戶),而不同的使用者端對於表現層的要求有所不同,那麼,問題就來了。再者,回到設計層面上分析,從職責單一原則來看,服務層只負責業務,與具體的表現形式無關,因此,它返回的DTO,不應該出現與表現形式的耦合。

  1. BO和DTO的區別?

從用途上進行根本的區別,BO是業務物件,DTO是資料傳輸物件,雖然BO也可以排列組合資料,但它的功能是對內的,但在提供對外介面時,BO物件中的某些屬性物件可能用不到或者不方便對外暴露,那麼此時DTO只需要在BO的基礎上,抽取自己需要的資料,然後對外提供。在這個關係上,通常不會有資料內容的變化,內容變化要麼在BO內部業務計算的時候完成,要麼在解釋VO的時候完成。

我們專案根據實際情況總結了分層領域模型的規範:

  1. 前端傳入的引數統一使用DTO接收
  2. 由於公司是TO B專案,只有一個電腦web端,所以返回給展示層或者Feign返回的物件建議直接用DTO返回,特殊情況採用VO返回
  3. 由於歷史原因,和資料庫模型對應的不採用DO結尾,直接用原始物件,比如學生表student直接使用Student物件
  4. 目前很多程式碼存在繼承關係,比如DTO、VO繼承資料庫物件,這個堅決不允許

專案目錄結構最佳實踐

可以參考github上面的https://github.com/alvinlkk/mall4cloud這個專案,它是一個極度遵守阿里巴巴程式碼規約的專案。

這裡面有個關於Feign設計的亮點, 主要是為了避免服務提供方修改了介面,而呼叫方沒有修改導致異常的問題。

  1. feign介面抽取出一個獨立的模組

  1. 服務中依賴feign模組,實現FeignClient

我們來看下整體的一個結構。

mall4cloud
├─mall4cloud-api -- 內網介面
│  ├─mall4cloud-api-auth  -- 授權對內介面
│  ├─mall4cloud-api-biz  -- biz對內介面
│  ├─mall4cloud-api-leaf  -- 美團分散式id生成介面
│  ├─mall4cloud-api-multishop  -- 店鋪對內介面
│  ├─mall4cloud-api-order  -- 訂單對內介面
│  ├─mall4cloud-api-platform  -- 平臺對內介面
│  ├─mall4cloud-api-product  -- 商品對內介面
│  ├─mall4cloud-api-rbac  -- 使用者角色許可權對內介面
│  ├─mall4cloud-api-search  -- 搜尋對內介面
│  └─mall4cloud-api-user  -- 使用者對內介面
├─mall4cloud-auth  -- 授權校驗模組
├─mall4cloud-biz  -- mall4cloud 業務程式碼。如圖片上傳/簡訊等
├─mall4cloud-common -- 一些公共的方法
│  ├─mall4cloud-common-cache  -- 快取相關公共程式碼
│  ├─mall4cloud-common-core  -- 公共模組核心(公共中的公共程式碼)
│  ├─mall4cloud-common-database  -- 資料庫連線相關公共程式碼
│  ├─mall4cloud-common-order  -- 訂單相關公共程式碼
│  ├─mall4cloud-common-product  -- 商品相關公共程式碼
│  ├─mall4cloud-common-rocketmq  -- rocketmq相關公共程式碼
│  └─mall4cloud-common-security  -- 安全相關公共程式碼
├─mall4cloud-gateway  -- 閘道器
├─mall4cloud-leaf  -- 基於美團leaf的生成id服務
├─mall4cloud-multishop  -- 商家端
├─mall4cloud-order  -- 訂單服務
├─mall4cloud-payment  -- 支付服務
├─mall4cloud-platform  -- 平臺端
├─mall4cloud-product  -- 商品服務
├─mall4cloud-rbac  -- 使用者角色許可權模組
├─mall4cloud-search  -- 搜尋模組
└─mall4cloud-user  -- 使用者服務

結束語

關於應用分層這塊我覺得沒那麼簡單,特別是團隊大了以後,人員水平參差不齊,很難約束,很容易自由發揮,各寫各的。不知道大家有沒有什麼好的辦法呢?

歡迎關注個人公眾號『JAVA旭陽』交流溝通