一、寫在前面 應用分層這件事情看起來很簡單,但每個程式員都有自己的一套,哪怕是初學者。如何讓一家公司的幾百個應用採用統一的分層結構,並得到大部分程式員的認同呢?這可不是件簡單的事情,接下來以我們真實案例與大家一起探討,先問大家兩個技術問題: 服務的調用代碼你覺得放到哪一層好呢? A 表現層 B 業務 ...
一、寫在前面
應用分層這件事情看起來很簡單,但每個程式員都有自己的一套,哪怕是初學者。如何讓一家公司的幾百個應用採用統一的分層結構,並得到大部分程式員的認同呢?這可不是件簡單的事情,接下來以我們真實案例與大家一起探討,先問大家兩個技術問題:
服務的調用代碼你覺得放到哪一層好呢?
- A 表現層
- B 業務邏輯層
- C 數據層
- D 公共層
如何組織好 VO(View Object 視圖對象)、BO(Business Object 業務對象)、DO(Data Object 數據對象)、DTO(Data Transfer Object 數據傳輸對象) 呢?
不同的人會有不同的答案,所以要統一公司應用分層,以減少開發維護學習成本。統一應用分層要可大可小、簡單易用、支持多種場景,我們採用 IPO 方式:I 是 Input、O 是 Output、P 是 Process,一進一齣一處理。應用系統的本質是機器,是處理設備,一進一齣一處理。
IPO 原理圖
二、統一邏輯架構
統一應用分層的邏輯架構圖
職責說明:
- 文件夾分層法:應用分層採用文件夾方式的優點是可大可小、簡單易用、統一規範,可以包括 5 個項目,也可以包括 50 個項目,以滿足所有業務應用的多種不同場景;
- 調用規約:在開發過程中,需要遵循分層架構的約束,禁止跨層次的調用;
- 下層為上層服務:以用戶為中心,以目標為導向。上層(業務邏輯層)需要什麼,下層(數據訪問層)提供什麼,而不是下層(數據訪問層)有什麼,就向上層(業務邏輯層)提供什麼;
- 實體層規約:DO 是數據表對象,不是數據訪問層對象,不是只能給數據訪問層使用;DTO 是網路傳輸對象,不是表現層對象,不是只能給表現層使用;BO 是記憶體計算邏輯對象,不是業務邏輯層對象,不是只能給業務邏輯層使用 。如果僅限定在本層訪問,則導致單個應用內大量沒有價值的對象轉換。以用戶為中心來設計實體類,可以減少無價值重覆對象和無用轉換;
- U 型訪問:下行時表現層是 Input,業務邏輯層是 Process,數據訪問層是 Output。上行時數據訪問層是 Input,業務邏輯層是 Process, 表現層就 Output。
三、我們的具體規範
此規範我們用了四年,牽涉幾百個應用,200 多個研發人員,是一個成功的實踐。接下來就借用本文提供下載的 TripOrderService、TripSellerMVCSite 這兩個 Demo 來進行具體規範的說明,以下是截圖:
3.1、項目命名規則
項目命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.{項目職責英文名全稱},如:Trip.Seller.DTO。
3.2、業務邏輯層的項目規範
規範說明:
- 1、項目名的命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.xxxBusiness,如上圖的 Trip.Order.Business。
- 2、類名以 Logic 結尾,如上圖的 OrderLogic.cs。
3.3、數據操作項目規範
規範說明:
- 1、各數據操作項目名根據使用什麼資料庫進行分類,然後以 DB 為結尾,具體命名規則是:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.{使用什麼資料庫}DB,如上圖的 Trip.Seller.MSSQLDB。
- 2、如果涉及到多個資料庫訪問的,那麼數據操作項目下的類文件需要按資料庫名稱(以 DB 為結尾)創建文件夾分開,如上圖的 TripOrderDB 文件夾。
- 3、建議在應用中使用 SQL 語句,不使用存儲過程。在資料庫中不新增存儲過程,但舊的存儲過程可以繼續使用和修改。
- 4、分頁建議使用資料庫(如 SQLServer)的最新特性進行分頁,並將每個分頁 SQL 直接寫到應用中。
3.4、實體類項目規範
數據傳輸對象 DTO 規範
規範說明:
- 1、DTO 項目命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.DTO,如上圖的 Trip.Order.DTO。
- 2、請求參數 DTO 實體類、響應 DTO 實體類存放規範以及其命名規則:3、如果請求參數 DTO 實體類、響應 DTO 實體類有基類要繼承,那麼建議為基類取名為 RequestBase.cs、ResponseBase.cs。且這些基類直接放在 DTO 項目的 Common 文件夾下。
- a、請求參數 DTO 實體類放在 Request 文件夾下,且命名規則為:以 Request 結尾,如上圖的 SearchOrderRequest.cs。
- b、響應 DTO 實體類放在 Response 文件夾下,且命名規則為:以 Response 結尾,如上圖的 SearchOrderResponse.cs。
- c、如果請求參數 DTO 實體類或響應 DTO 實體類的屬性中有對象或枚舉,那麼這些對象所屬的類、枚舉放在 DTO 項目的 Common 文件夾下。
- 3、如果請求參數 DTO 實體類、響應 DTO 實體類有基類要繼承,那麼建議為基類取名為 RequestBase.cs、ResponseBase.cs。且這些基類直接放在 DTO 項目的 Common 文件夾下。
視圖對象 VO 規範
規範說明:
- 1、VO 項目命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.ViewModel,如上圖的 Trip.Seller.ViewModel。
- 2、各 VO 實體類,我們用 Controller 名作為文件夾名進行分開,如上圖的 Order 文件夾。
- 3、VO 實體類名的命名建議:
- a、請求參數 VO 實體類以 Input/Form/Query 結尾,如上圖的 SearchOrderInput.cs。
- b、響應 VO 實體類以 Output/List/Result 結尾,如上圖的 SearchOrderOutput.cs。
業務對象 BO 規範(可選)
BO 實體類名以 Model 為結尾:
規範說明:
- 1、BO 項目命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.BO,如上圖的 Trip.Order.BO;
- 2、以 Model 結尾,如上圖的 OrderModel.cs;
- 3、為了簡化設計,BO 項目為可選,可在 DO 項目里建文件夾。
數據對象 DO 規範(可選)
規範說明:
- 1、DO 項目命名規則:{產品線英文名全稱}.{子系統英文名全稱 + 應用名}.Entity,如上圖的 Trip.Seller.Entity;
- 2、如果涉及到多個資料庫訪問的,那麼需要按資料庫名稱(以 DB 為結尾)創建文件夾分開,如上圖的 TripOrderDB 文件夾;
- 3、表名 +Entity 結尾,如上圖的 OrderEntity.cs;
- 4、DO 是數據表對象,供單表 CURD 操作。對於多表查詢請求對象和返回對象,可定義新對象或使用現有對象(DTO/BO)來完成。
3.5、資料庫連接配置規範
規範說明:
- 1、資料庫連接的配置必須讀寫分離。
- 2、資料庫連接字元串建議加密處理。
- 3、資料庫連接配置名的命名規則:{以 DB 為結尾的資料庫名稱}_ 讀寫類型,如:TripOrderDB_SELECT、TripOrderDB_INSERT。
3.6、配置文件方面的規範
規範說明:
- 1、所有配置文件(除 Web.config 文件外)都必須放到 Config 文件夾下。
- 2、所有配置文件(除 Web.config 文件外)按不同環境區分開,具體命名規則是:{功能模塊英文名}.{環境英文簡稱名}.config,其中本地環境的英文簡稱名是 Dev,測試環境的英文簡稱名是 Test,正式環境的英文簡稱名是 Prod,如上圖的 AppSetting.Dev.config。
- 3、保持 Web.config 配置文件的乾凈,只留環境設置節點。
3.7、靜態資源文件方面的規範
規範說明:
- 1、公共的靜態資源文件(css、js、image 等)放在另外的靜態站點中,統一由前端進行開發和維護。一般,css 文件放在 css 文件夾下,js 文件放在 js 文件夾下,image 圖片文件放在 img 文件夾下。
- 2、與某項業務有關的 js 文件可以放到各自業務項目的表現層 PresentationLayer 下,以方便開發人員調試,js 文件可放在項目的 js 文件夾下。
- 3、靜態資源文件必須使用版本號管理,以防更新後由於客戶端瀏覽器緩存而導致站點使用的依然是舊版本的靜態資源文件:
<script src="~/js/[email protected]"></script>
四、寫在最後
4.1、問題回答
問:服務的調用代碼應該放到哪一層呢?A 表現層、B 業務邏輯層 、C 數據層、D 公共層。
我們的規範是統一放到數據資源訪問層即 C。上層提供服務,下層調用服務,中間處理業務邏輯。
問:如何組織好 VO(View Object 視圖對象)、BO(Business Object 業務對象)、DO(Data Object 數據對象)、DTO(Data Transfer Object 數據傳輸對象) 呢?
通常有兩種做法,限定訪問範圍和不限定訪問範圍,實際項目中可根據需要選擇、折中或裁剪。我們使用後者,將 EntityLayer 作為通用對象放到左側,具體可參考實體層規約:
“DO 是數據表對象,不是數據訪問層對象,不是只能給數據訪問層使用;DTO 是網路傳輸對象,不是表現層對象,不是只能給表現層使用;BO 是記憶體計算邏輯對象,不是業務邏輯層對象,不是只能給業務邏輯層使用 。如果僅限定在本層訪問,則導致單個應用內大量沒有價值的對象轉換。以用戶為中心來設計實體類,可以減少無價值重覆對象和無用轉換。”
問:應用分層範例代碼的編寫需要註意些什麼?
應用分層範例的代碼要想寫好,非常不容易,很容易引起爭議,很難讓所有人滿意。我們在具體實踐時遵循以下幾點:
應用分層範例的主要價值是明確層的職責和交互,每個層的職責是什麼,哪些要乾,哪些不要乾,以及層與層之間依賴和交互;
私人定製:減少通用幫助類的編寫,如果每一個應用中有大量相同的幫助類,這在架構層面上是有問題。在我們的幾百個線上應用中,儘管減少通用的代碼,包括分頁幫助類、資料庫幫助類、緩存幫助類、MQ 幫助類、日誌幫助類、AOP 幫助類、線程幫助類。業務應用的重點是為業務服務,每一個應用都是特別的,都需要私人定製,極少有通用的代碼,如果有,那麼應該由框架或組件專門解決;
少即是多:應用的場景多,參考人員多,每個人想法不同,牽涉的時間長,所以儘量只做大家都認同的規範、正確的事情,要自底向上、要減少有爭議的代碼範例,否則一個錯誤將會放大百倍、一個有爭議的規範將會很難推行。
追求簡單:代碼編寫可分為三個層次,簡單、複雜、簡單。第一簡單是不知道的簡單,第二個複雜是知道後的複雜,第三個簡單是知道後有取捨的簡單。範例代碼要追求簡單,既可輕鬆擴展支持複雜場景,又要簡單到初級程式員也能操作。
內聚大於解耦:內聚是什麼,內聚是部門內有共同的目標,然後大家緊密合作。解耦是什麼,解耦是部門間各自職責明確,然後減少不必要的連接。一個應用如同一個部門,應有一個共同的目標和職責,然後大家緊密合作。
換句話說,應用內部應減少不必要契約介面(如同公司間才簽約合同),減少不必要的依賴註入實現,減少不必要且代價過大的解耦。一切以簡單實用為主,以應用價值輸出、應用的目標(介面或界面)為導向。
4.2、Demo 下載
LayerDemo 下載地址:https://github.com/das2017/LayerDemo