此文已由做者黎星受權網易雲社區發佈。html
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗前端
記資源投放後端工程的架構調整與優化 git
一直以來對軟件工程架構有着極大的興趣,不管是以前負責的移動端Android工程,亦或是如今轉到後端開發後維護的資源投放工程。能夠說一個團隊中並不是每一個開發都可以深刻掌握架構知識,但須要每一個人可以擁有軟件架構的意識。架構是對工程總體結構與組件的抽象描述,是軟件工程的基礎骨架。架構在工程層面不分領域,且思想是通用的。引用維基百科對於軟件架構的定義^1:github
軟件體系結構是構建計算機軟件實踐的基礎。與建築師設定建築項目的設計原則和目標,做爲繪圖員畫圖的基礎同樣,軟件架構師或者系統架構師陳述軟件架構以做爲知足不一樣客戶需求的實際系統設計方案的基礎。從和目的、主題、材料和結構的聯繫上來講,軟件架構能夠和建築物的架構相比擬。一個軟件架構師須要有普遍的軟件理論知識和相應的經驗來實施和管理軟件產品的高級設計。軟件架構師定義和設計軟件的模塊化,模塊之間的交互,用戶界面風格,對外接口方法,創新的設計特性,以及高層事物的對象操做、邏輯和流程。數據庫
架構的合理設計能夠解決面對複雜系統時可能面臨的不少問題,例如:apache
業務邊界與模塊職責劃分問題後端
代碼權限控制問題(數據庫不該直接被業務方調用)緩存
代碼重複,邏輯分支多,壞味道多的問題安全
因爲考慮不周,可能存在隱藏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,等到編譯的時候再把業務邏輯代碼一塊兒編譯進來,而後在運行時經過必定的方式調用對應的業務邏輯。
下面的兩張圖簡單介紹了後端工程和移動端工程基於業務邏輯隔離的工程架構思路。其中,虛線表示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)。
免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 常見的三種撞庫方法
【推薦】 全局腳手架了解一下【fle-cli】