此文已由作者黎星授權網易云社區發布。
歡迎訪問網易云社區,了解更多網易技術產品運營經驗
記資源投放后端工程的架構調整與優化?
架構思考
一直以來對軟件工程架構有著極大的興趣,無論是之前負責的移動端Android工程,亦或是現在轉到后端開發后維護的資源投放工程。可以說一個團隊中并非每個開發都能夠深入掌握架構知識,但需要每個人能夠擁有軟件架構的意識。架構是對工程整體結構與組件的抽象描述,是軟件工程的基礎骨架。架構在工程層面不分領域,且思想是通用的。引用維基百科對于軟件架構的定義^1:
軟件體系結構是構建計算機軟件實踐的基礎。與建筑師設定建筑項目的設計原則和目標,作為繪圖員畫圖的基礎一樣,軟件架構師或者系統架構師陳述軟件架構以作為滿足不同客戶需求的實際系統設計方案的基礎。從和目的、主題、材料和結構的聯系上來說,軟件架構可以和建筑物的架構相比擬。一個軟件架構師需要有廣泛的軟件理論知識和相應的經驗來實施和管理軟件產品的高級設計。軟件架構師定義和設計軟件的模塊化,模塊之間的交互,用戶界面風格,對外接口方法,創新的設計特性,以及高層事物的對象操作、邏輯和流程。
架構的合理設計可以解決面對復雜系統時可能面臨的很多問題,例如:
業務邊界與模塊職責劃分問題
代碼權限控制問題(數據庫不應直接被業務方調用)
代碼重復,邏輯分支多,壞味道多的問題
由于考慮不周,可能存在隱藏bug
修改一個邏輯需要修改N個地方代碼邏輯
從實際的實踐來看,的確如此。以前在移動端做的架構設計流程,在后端重新得到了實踐。
移動端架構思考
尚未接觸到強大的Spring容器之前,我一直探索著在移動端有一種能夠在編譯期暴露服務聲明,運行時自動注入實現類的做法;接觸到Spring以后,得以理解這其實就是IoC容器的概念。Android的組件化思想,以及網上發布的各類組件化的技術文章,給了我很多值得借鑒的思路。客戶端的代碼一般是以module來組織的。一個module,既可以配置成為一個獨立發布的庫,也可以編譯成一個單獨的apk。組件化的概念正是利用了module這一特點,將一個大工程中的業務拆分成一個個module,各個module間的業務相對獨立,組件間通過各自暴露的業務接口實現通信。基于此思想的移動端架構模型可以使用下圖來表示。
點擊查看原圖
該架構模型由5個部分組成,分別是Toolkit/ToolkitSDK module、基礎組件庫/基礎組件庫module、基礎服務接口/業務服務接口module、服務調度中心module以及業務module。
Toolkit/Toolkit SDK
Toolkit是工具類及與工具類相關的SDK的集合。工具類屬于工程架構里最基礎的模塊,提供了通用的方法與工具類服務(工具類服務是指可以被抽象成一個獨立的與業務無關的基礎服務,如緩存、數據庫操作等)。工具類通常作為最底層的module,被其他所有模塊引用。
基礎組件庫/基礎組件SDK
基礎組件庫是基礎組件及相關SDK的集合。基礎組件庫提供與業務相關的基礎組件,是構建一個移動端應用所需要的通用組件的集合。它與工具類的區別在于基礎組件庫可能會包含少量業務邏輯代碼,是無法拆分給其他應用使用的;另一方面,基礎組件庫是基礎服務接口的實現,是不對業務層暴露的,避免了業務層與基礎SDK打交道,有利于整體替換底層基礎框架的實現(例如Volley替換為OkHttp、Fresco替換為Glide)。
基礎服務接口/業務服務接口
基礎服務接口聲明了一組通用的基礎服務,業務層通過基礎服務接口獲取基礎服務,如網絡請求、圖片加載等。業務服務接口聲明了一組該模塊提供給其他模塊的服務,業務之間的通信也是通過服務接口來完成的。例如首頁模塊需要獲取購物車的商品數量,首先通過服務調度中心獲取購物車的服務接口,再通過服務接口調用購物車獲取商品數量的接口方法即可。
服務調度中心
服務調度中心,是一個接口收集與管理的容器。服務調度中心將所有基礎服務接口與業務接口收集起來,通過一定的方式與它們的實現類進行綁定。所有的業務都需要通過服務調度中心才能夠獲取到服務。服務的注冊與發現和Spring容器的IoC思想是類似的。
業務層
業務層是每個業務的具體實現的集合。業務層的業務之間是沒有直接引用關系的,業務層提供了業務服務接口中暴露的服務的具體實現。業務之間的通信需要通過服務調度中心獲取其他業務的服務接口。
移動端架構小結
通過接口服務架構模型,模塊之間是高度解耦的。業務負責人唯一需要維護的公共部分便是這個模塊在業務服務接口中暴露的服務。對于業務服務的接口功能增改變得非常方便,業務實現的邏輯更改、代碼優化等,只要不改變服務接口的簽名,就不需要其他業務方改動任何代碼即可完成,由此團隊的開發效率是非常高的。
后端架構思考
對于后端工程來說,架構的設計與實現必定是與工程的業務難度及復雜程度相關的,如果只是很簡單的業務模型,就沒有必要弄得太過復雜,避免得不償失。本人只接觸了幾個月后端知識,對于后端的架構體系與演進過程處于不斷地學習和探索中。投放系統是我接觸到的第一個完整的后端工程,其中Web工程采用傳統的MVC架構^2,對我具有很大地學習和借鑒意義,項目架構如下圖所示。
點擊查看原圖
該架構縱向劃分成展示層、控制層、服務層、對象關系映射層和數據服務層5個部分,層級間通過AOP的方式插入了業務監控、日志、權限控制、統計分析等功能。
展示層(View)
展示層是系統與用戶打交道的地方,提供與用戶交互的界面。對于用戶而言,只有展示層是可見的、可操作的。展示層對于某些工程來說不是必須的,例如提供純后臺服務的工程。
控制層(Controller)
主要負責與Model和View打交道,但同時又保持其相對獨立。Controller決定使用哪些Model,對Model執行什么操作,為視圖準備哪些數據,是MVC中溝通的橋梁。在Controller層提供了http服務供展示層調用。在依賴管理中,控制層需要依賴服務層提供服務。
服務層(Service/Facade)
服務層是業務邏輯實現的地方,上層需要使用的功能都在服務層來實現具體的業務邏輯。服務層就是將底層的數據通過一定的條件和方式進行數據組裝并提供給上層調用。服務層可以拆分為業務接口和業務實現,業務實現可以對外部隱藏。在投放工程中,控制層既依賴了業務接口,又依賴了業務實現。后面的改造我們可以看到,編譯期紅色線依賴是完全沒有必要的。服務層需要依賴數據關系映射層與持久層的數據打交道。
對象關系映射層(ORM)
對象關系映射層的作用是在持久層和業務實體對象之間作一層數據實體的映射,這樣在具體操作業務對象時,只需簡單的操作對象的屬性和方法,不需要去和復雜的SQL語句打交道。ORM使得業務不需要關心底層數據庫的任何細節,包括使用的數據庫類型、數據庫連接與釋放細節等。對象關系映射層只依賴數據服務層提供服務。
數據服務層(Data Server)
數據服務就是提供數據源的地方。數據服務可以提供持久化數據及緩存數據。持久,即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。持久化的主要應用是將內存中的數據存儲在關系型的數據庫中,當然也可以存儲在磁盤文件中、XML數據文件中等等。而緩存是將信息(數據或頁面)放在內存中以避免頻繁的數據庫存儲或執行整個頁面的生命周期,直到緩存的信息過期或依賴變更才再次從數據庫中讀取數據或重新執行頁面的生命周期。數據服務層是數據源頭,處于架構的最底層。
后端架構小結
后端工程,更加注重層級的概念,每一層的職責非常明確。展示層負責與用戶進行頁面交互,控制層合并業務數據并控制View的展示,服務層則是實現業務邏輯的聚集地,對象關系映射層在業務層和數據服務層之間建立通道,而數據服務層則提供數據。總體而言,投放工程的MVC架構給我的感覺是比移動端架構復雜,層級多,職責分工明確,帶來的問題是層級間的交互也比較麻煩。另外服務層里承載了幾乎所有的業務邏輯,層級偏重,如果沒有好好地梳理業務邏輯劃清邊界,很容易把服務層搞成一鍋粥。清晰的模塊職責劃分,可以幫助服務層更好地為控制層服務。
架構思想的摩擦
可以看到,客戶端與后端有著非常相似的架構模型。
從代碼組織的角度:以module作為層級代碼組織的基本工具,分為工具庫、基礎組件庫(中間件)、服務接口/API、服務層/業務層、視圖層等。module間的依賴關系幾乎是一樣的。
從業務模型的角度:后端工程分為交易組、商品組、售后組、客服組等,對應移動端的交易鏈路浮層、商品詳情頁、售后詳情頁、幫助與客服頁等,每個業務是由不同的組負責的,業務之間通過約定的接口相互提供服務,各種各樣的業務模型聚合成了整個系統。
從功能服務的角度:分為業務服務接口的暴露、業務服務實現的隔離、業務服務的查找與注冊。
下面從功能服務的角度,詳細說明本文在思想摩擦過程中想要表達的觀點。
業務接口的作用
業務接口,可以認為是這組業務向外暴露其功能的一套標準。標準一旦形成并發布,就需要業務方持續維護這套標準,使得標準變得完善和穩定。同時標準可以更新升級,可以通過版本來實現,提供新的功能。業務接口一般具備以下特性:
業務接口包含一組Java的接口集合以及與這些接口相關的POJO,通常打包成一個JAR/AAR包。
業務接口只提供接口功能的定義,不包含任務業務邏輯。
業務接口可以進行版本管理,一旦版本發布,則該版本的接口不再可變。業務需要新增功能時,只需要在原有業務接口的基礎上,增加新的功能接口或方法,同時升級業務接口版本號并發布。
其他業務方需要使用該業務的功能,只需要引入該業務的JAR/AAR包,通過服務調度中心獲取該服務接口即可。
業務邏輯的存放
業務接口僅提供了功能的定義,不包含任何業務邏輯。那么,業務邏輯(即接口的實現類)放哪里呢?不管是移動端架構還是后端架構,在工程領域,業務邏輯在任何時候都不應該對業務的使用方暴露。這樣做有兩個好處:
業務方只關心功能,不關心功能實現的過程。隱藏業務的實現邏輯可以降低業務方使用該功能的成本及復雜度。
業務功能的后端邏輯改動及必要的技術優化、性能優化,只要不更改接口簽名,則不會影響當前的業務方使用。
一般情況下,業務的邏輯實現會放在單獨的業務模塊中,該業務模塊僅限工程內部引用。后端傳統的MVC工程架構把業務的實現邏輯放在了Service/Facade層,層級之間的類不相互引用;而基于驅動領域的設計模型把業務的實現邏輯限定在了一個領域/子域里,領域之間通過界限上下文綁定。在移動端,時下較為熱門的眾多組件化方案,也是將一個獨立的功能模塊作為單獨的module,module可以獨立編譯為apk,也可以通過aar的方式集成到主Application中。
業務邏輯的隔離
業務的使用方包括工程內部的上層業務和外部服務,業務接口與業務邏輯有必要進行代碼級別的隔離,這樣才能避免上層業務引用到業務邏輯的代碼。通常,工程的每個模塊負責不同的功能,模塊之間的引用關系通過依賴管理工具(如Maven或Gradle)來配置。我們可以巧用依賴管理工具的runtime compile機制來實現運行時依賴。即在編寫對外接口的時候,不直接引用包含業務邏輯的module,等到編譯的時候再把業務邏輯代碼一起編譯進來,然后在運行時通過一定的方式調用對應的業務邏輯。
Maven通過在dependencyManagement的依賴中加入runtime標簽實現^3。
Gradle通過在dependencies將compile改為runtime實現^4。
下面的兩張圖簡單介紹了后端工程和移動端工程基于業務邏輯隔離的工程架構思路。其中,虛線表示runtime compile依賴,實線表示正常的依賴關系。
點擊查看原圖
工程的啟動入口幾乎不包含業務代碼,只包含配置文件。Controller層是一組RestfulApi的集合,給前端和客戶端提供http請求服務,業務接口/API是一組dubbo接口,給其他工程業務方提供RPC調用。Controller層在編碼的時候只依賴Service/Facade接口,在編譯期依賴Service和Facade接口的實現。這樣設計還有一個好處是對DAO層的保護,DAO層只和Service層打交道,Controller以及對外提供的dubbo接口是引用不到的,更好地保護數據安全。
點擊查看原圖
在移動端的架構中,單Application+多module已經成為主流。每個module負責一塊獨立的業務,如首頁、訂單、購物車等,核心模塊也可以拆分為獨立模塊,如網絡引擎、圖片引擎。這些獨立的模塊可以抽離出BusinessService/CoreService服務接口,模塊間的交互只需要通過Service接口通信即可,業務對于其他的p_CoreSDK/Business的邏輯實現是不可見的。
業務接口的注冊
有了業務接口和業務實現,還需要一種在運行時把它們“粘合”起來的工具,這一過程可以稱為業務接口的注冊。當業務方訪問業務接口時,這個工具需要幫助我們查找到對應的業務實現。控制反轉(IoC)或依賴注入(DI)的思想給我們提供了解決辦法。
在后端工程中,Spring是最為常用的IoC容器之一。Spring在運行時根據配置文件或注解動態生成對象,再由變量注解通過Java反射注入到對應的實例中。因此代碼中只需要通過在全局變量聲明相應的注解即可完成業務接口的注冊。
移動端由于有限的硬件資源,更多地把CPU時間分配給了頁面渲染,保證應用體驗流暢,不太可能在應用啟動的過程中大量通過反射生成實例對象,因此移動端并沒有出現Spring框架。盡管如此,依賴注入的思想是通用的。通常移動端只需要保證在運行時能夠獲取到對應的業務實現,幾乎沒有在運行時動態改變業務實現的需求,聰明的工程師想到了把服務的注冊提前到編譯期進行,這一過程可以使用JDK提供的Annocation Processing Tool
完成(例如Dagger2),也可以在編譯生成Class字節碼以后使用ASM操作字節碼注冊實現(如ARouter)。
?
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及云服務器等套餐
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】?如何學習、了解Kubernetes?
【推薦】?分析自己遇到的Excel導出報NullpointException問題
【推薦】?當我們談論計劃時我們在談論什么