一、寫在前面
應用分層這件事情看起來很簡單,但每個程序員都有自己的一套,哪怕是初學者。如何讓一家公司的幾百個應用采用統一的分層結構,並得到大部分程序員的認同呢?這可不是件簡單的事情,接下來以我們真實案例與大家一起探討,先問大家兩個技術問題:
- 服務的調用代碼你覺得放到哪一層好呢?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原理圖
二、統一邏輯架構
統一應用分層的邏輯架構圖
職責說明:
層英文名 |
中文名 |
說明 |
|
PresentationLayer |
表現層文件夾 |
上層向用戶提供服務,負責視圖展示。項目類型包括WebSite、WebForm、MVC、WCF、WebService等。 |
|
BusinessLayer |
業務邏輯層文件夾 |
中間邏輯處理,負責應用系統的業務邏輯的處理。 |
|
DataLayer |
數據訪問層文件夾 |
下層調用服務,負責數據資源提供方如數據庫、SOA、OpenAPI的交互。 |
|
EntityLayer |
實體層文件夾 |
VO:View Object視圖對象; DTO:Data Transfer Object數據傳輸對象; BO:Business Object業務對象; DO:Data Object數據對象; 在實際項目中,為簡化設計可進行裁剪,BO和DO為可選,DTO屬於服務項目類型,VO屬於網站項目類型,也不會同時存在。 |
|
CommonLayer |
公共層文件夾 |
工具類庫,負責提供應用系統中常用的操作。 |
|
TestLayer |
測試層文件夾 |
單元測試(可選),負責對其它類庫的自動化單元測試。 |
|
|
|
|
|
- 文件夾分層法:應用分層采用文件夾方式的優點是可大可小、簡單易用、統一規范,可以包括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、實體類項目規范
規范說明:
1、DTO項目命名規則:{產品線英文名全稱}.{子系統英文名全稱+應用名}.DTO,如上圖的Trip.Order.DTO。
2、請求參數DTO實體類、響應DTO實體類存放規范以及其命名規則:
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文件夾下。
規范說明:
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實體類名以Model為結尾:
規范說明:
1、BO項目命名規則:{產品線英文名全稱}.{子系統英文名全稱+應用名}.BO,如上圖的Trip.Order.BO;
2、以Model結尾,如上圖的OrderModel.cs;
3、為了簡化設計,BO項目為可選,可在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/order.js?v=@AppSetting.StaticFileVersion"></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下載