go循環(huán)依賴的最佳解決方案
前言:
import cycle not allowed
(循環(huán)依賴不被允許)相信作為每一個golang語言使用研發(fā),都遇到過這個令人頭痛的報錯。循環(huán)依賴是指兩個或多個模塊之間互相依賴,形成了一個閉環(huán)的情況。這種情況下,編譯器或解釋器無法確定應(yīng)該先加載哪個模塊,從而導(dǎo)致程序無法正常運行。本文會結(jié)合部分案例對解決方案進行講解。
包引用循環(huán)依賴
golang 代碼包的加載過程:
問題一:循環(huán)依賴是在上面哪一個環(huán)節(jié)報錯?
編譯環(huán)節(jié)。 當(dāng)存在循環(huán)依賴時,golang編譯代碼時會首先解析代碼包的依賴關(guān)系,然后按照依賴關(guān)系的順序進行編譯。如果存在循環(huán)依賴,即A依賴于B,而B又依賴于A,golang會檢測到這種循環(huán)依賴關(guān)系,并報告編譯錯誤。編譯錯誤的具體信息會包括代碼包路徑和導(dǎo)致循環(huán)依賴的具體文件及行號。
問題二:循環(huán)依賴的危害有哪些?
對于golang來說,最大的危害就是連編譯都過不了...
問題三:go高版本是否支持從編譯環(huán)節(jié)解決循環(huán)依賴的問題?
不支持。 java解決循環(huán)依賴的方法有很多,延遲初始化或懶加載、三級緩存等都是解決包循環(huán)依賴的利器。然而golang并沒有配置或注解來解決循環(huán)依賴的問題。
相比于代碼的執(zhí)行效率,Go更加注重編譯的速度。雖然Go的編譯器不會像C/C++那樣花費大量的時間生成高效的機器碼,但它們更專注于快速的編譯大量的源代碼。支持循環(huán)依賴會導(dǎo)致代碼編譯時間增加,因為每次改變某個依賴項都會重新編譯整個依賴樹。此外,循環(huán)依賴還增加了鏈接時間和使獨立測試和包重用的難度(由于包之間可能不可靠分離,單元測試也會變得更加困難)。
解決方案:
重新梳理領(lǐng)域模型
在遇到循環(huán)依賴的時候,最先考慮的就是模塊分層是否不清晰,領(lǐng)域劃分是否準確。下圖是最基礎(chǔ)的DDD模型。
下層不依賴上層
我們應(yīng)該有著清晰的認知,在服務(wù)中,哪個模塊是下層,哪個是上層。
如數(shù)據(jù)庫dao層,reids包這些都是處于基礎(chǔ)設(shè)施層的模塊,那么其就不能依賴于領(lǐng)域?qū)舆壿嫿涌?。如果想要實現(xiàn)寫入數(shù)據(jù)庫并寫入redis,這個邏輯就影響在領(lǐng)域?qū)訉崿F(xiàn)而不是在基礎(chǔ)設(shè)施層。
同層不能互相調(diào)用
舉個例子,假設(shè)我們有外呼模塊和短信模塊,部分場景在外呼后要自動發(fā)送短信,部分場景發(fā)完短信后要機器人外呼,就出現(xiàn)循環(huán)依賴了。我們可以有兩種解決方案,1.將互相調(diào)用的邏輯往上層移動。2.將兩個模塊放在一起作為一個統(tǒng)一的用戶觸達模塊。
有些時候,在沒出現(xiàn)循環(huán)依賴的時候,我們可能并沒有意識到出現(xiàn)了同層調(diào)用的情況,等到出現(xiàn)循環(huán)依賴再想保持良好的架構(gòu)模式就需要代碼重構(gòu)了。 約束是死的,人是活的??偸怯幸恍﹫鼍?,你不得不在同層模塊間進行調(diào)用,但是改造的消耗的成本遠比不遵守準則來得低,則進行同層間調(diào)用也不是什么丟臉的事。
目前很多服務(wù)還是依賴于傳統(tǒng)的MVC模型進行開發(fā),三層結(jié)構(gòu)使得所有的業(yè)務(wù)邏輯都耦合在service(業(yè)務(wù)邏輯)層,雖然可閱讀性下降了,但是也降低了循環(huán)依賴問題發(fā)生(所有的業(yè)務(wù)邏輯都在一個包下)。DDD在給我們帶來高可維護性,可閱讀性的同時,也需要我們更清晰的對業(yè)務(wù)有明確的認知和一定的技術(shù)支持。兩者不論好壞,各有各自適用的業(yè)務(wù)場景。
依賴倒置原則
依賴倒置又叫做依賴反轉(zhuǎn)。依賴關(guān)系如下圖所示:
依賴倒置是解決循環(huán)依賴很常用的技巧,但是不是所有的循環(huán)依賴場景都適用依賴倒置來解決,我們通常會在架構(gòu)設(shè)計或者通用能力接口的實現(xiàn)上使用到它,恰當(dāng)?shù)氖褂?,可以降低代碼耦合性,提高代碼可讀性和可維護性。
舉例一個生活中的例子:
我們運營平臺最常見的場景就是審核,在增信提額場景中,會先對用戶提交的資料進行審核,再對用戶提交的申請進行審核。目前,人審已經(jīng)不滿足我們訴求,我們需要提供一個自動審核的能力,在審核員完成對資料的審核后,自動觸發(fā)自動審核完成對申請維度的審核。如下圖:
上圖左邊,如果我們單獨的抽出一個文件夾作為自動審核模塊,資料審核完成需要發(fā)起自動審核,自動審核需要執(zhí)行申請審核,所以上面自動審核模塊和增信提額模塊就形成了循環(huán)依賴的關(guān)系。
當(dāng)我們進行依賴反轉(zhuǎn)后,就可以得到右圖。將自動審核模塊分成兩個文件夾,一個是抽象接口,一個是邏輯實現(xiàn)。抽象接口不依賴于邏輯實現(xiàn),所以自動審核入口不再依賴于增信提額模塊中的申請審核。自動審核入口成為最底層模塊,所有的模塊都可以隨意調(diào)用,一定不會產(chǎn)生循環(huán)依賴。對于資料審核完成執(zhí)行申請審核場景下,資料審核是高層,自動審核實現(xiàn)邏輯是低層,他們都依賴于抽象。
這是一個很典型的通過依賴反轉(zhuǎn)來實現(xiàn)提供一個通用能力的例子。除了基礎(chǔ)設(shè)施層以外,我們所有實現(xiàn)的通用能力接口如果包含了業(yè)務(wù)邏輯,都應(yīng)將其通過依賴反轉(zhuǎn)來與實現(xiàn)解耦??梢詮氐捉鉀Q通用能力包導(dǎo)致的循環(huán)依賴問題。
分層架構(gòu)中也經(jīng)常會使用依賴反轉(zhuǎn)來改變不同層之間的依賴關(guān)系以此達到改進的目的。如下面一種場景:
將基礎(chǔ)設(shè)施層放到所有層最上方。其優(yōu)點有:
基礎(chǔ)設(shè)施層可以調(diào)用其他層所有的方法。
基礎(chǔ)設(shè)施層可以直接使用領(lǐng)域?qū)佣x的實體等其他層的結(jié)構(gòu)化信息。
事件驅(qū)動架構(gòu)
事件驅(qū)動架構(gòu)是一種松耦合、分布式的架構(gòu)??梢酝ㄟ^mq來實現(xiàn)。
事件驅(qū)動架構(gòu)體系結(jié)構(gòu)具備以下三個能力:
事件收集:負責(zé)收集各種應(yīng)用產(chǎn)生的數(shù)據(jù)變化,例如訂單創(chuàng)建、退換貨等狀態(tài)變更。這些變化可以被用于監(jiān)控業(yè)務(wù)流程、識別異常情況和觸發(fā)警報等。
事件處理:對采集到的數(shù)據(jù)變化進行脫敏處理,并進行初步的過濾和篩選,以確保數(shù)據(jù)的準確性和可靠性。這有助于減少誤報和漏報的風(fēng)險,并為上層應(yīng)用提供有價值的數(shù)據(jù)。
事件路由:根據(jù)數(shù)據(jù)變化的來源和性質(zhì),將事件路由分發(fā)至下游產(chǎn)品,以實現(xiàn)更高效的業(yè)務(wù)處理和分析。這可以幫助企業(yè)更好地了解自己的業(yè)務(wù)狀況,并根據(jù)需要進行優(yōu)化和改進。
舉例:
同樣是上面增信提額的例子,如果這個時候,放心借產(chǎn)品審核也需要進行自動審核了,那我們是否可以按如下架構(gòu)進行調(diào)整。
我們將自動審核抽成一個獨立服務(wù),通過mq解耦自動審核回調(diào)業(yè)務(wù)系統(tǒng)的邏輯。至此即解決了循環(huán)依賴的問題,又對自動審核服務(wù)與業(yè)務(wù)系統(tǒng)進行了明確的劃分。缺陷是:對于原流程改造成本大。
無論是依賴倒置還是事件驅(qū)動架構(gòu),都能解決以上循環(huán)依賴的問題,**沒有最好的方案,只有最適用的方案。**依賴倒置適用于同步場景。而事件驅(qū)動架構(gòu)適用于異步的場景。
丑陋的解決方式(不推薦 不推薦 不推薦)
通過go:linkname注釋來避免導(dǎo)入包。
go:linkname是一個編譯器指令(格式://go:linkname localname [importpath.name] ) 。這個特殊指令的作用域不是緊跟的下一行代碼,而是在同一個包下生效。
//go:linkname 告訴Go的編譯器本本地的變量或方法 localname 鏈接到指令的變量或方法 importpath.name 上go:linkname定義 。
這是go官方黑科技,官方也不建議使用。
結(jié)論:
遇到循環(huán)依賴不要慌。
先梳理出上下層模塊,切記,上層不依賴于下層。
如果為同層模塊,則考慮同層合并或者邏輯上移。
同步場景的通用接口考慮用依賴倒置原則,上層下層均依賴于抽象。
異步場景考慮用事件驅(qū)動模式。
永遠不要考慮linkname解決。
以上就是go循環(huán)依賴的最佳解決方案的詳細內(nèi)容,更多關(guān)于go循環(huán)依賴解決的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Go語言中new和make關(guān)鍵字的區(qū)別
本篇文章來介紹一道非常常見的面試題,到底有多常見呢?可能很多面試的開場白就是由此開始的。那就是 new 和 make 這兩個內(nèi)置函數(shù)的區(qū)別,希望對大家有所幫助2023-03-03Go中的關(guān)鍵字any interface是否會成為歷史
這篇文章主要為大家介紹了Go中的關(guān)鍵字any interface是否會成為歷史的講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07Go語言對JSON數(shù)據(jù)進行序列化和反序列化
這篇文章介紹了Go語言對JSON數(shù)據(jù)進行序列化和反序列化的方法,文中通過示例代碼介紹的非常詳細。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07