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