圖的拓撲排序管理 Go 服務啟動時的組件初始化順序

在構建復雜的 Go 應用程序時,服務的啟動過程往往涉及多個組件的初始化,例如日志、配置、數據庫連接、緩存、服務管理器、適配器等等。這些組件之間通常存在著復雜的依賴關系:日志可能需要配置信息,數據庫連接可能依賴日志和追蹤(trace),服務管理器又可能依賴數據庫存儲。

如果手動管理這些初始化順序,不僅容易出錯,而且隨著項目規模的增長,維護起來會變得異常困難。想象一下,當新的組件加入或現有依賴關系改變時,你不得不小心翼翼地調整啟動代碼的順序。這不僅耗時,還可能引入難以發現的啟動問題。

拓撲排序是一種對有向無環圖(DAG)的頂點進行線性排序的方法,使得對于圖中每一條從頂點 A 指向頂點 B 的有向邊,A 都出現在 B 之前。這完美契合了組件初始化時的“依賴”關系:如果組件 B 依賴于組件 A,那么 A 必須在 B 之前初始化。

這樣做的好處顯而易見:

  • 自動化依賴管理: 無需手動維護復雜的 if-else 或順序調用鏈。
  • 避免循環依賴: 如果不小心引入了循環依賴(A 依賴 B,B 依賴 A),拓撲排序算法能夠立即檢測到并報錯,防止運行時死鎖或錯誤。
  • 可擴展性強: 增加新的組件及其依賴時,只需修改依賴圖的定義,而無需修改核心的初始化邏輯。
  • 錯誤隔離: 某個組件初始化失敗,能清晰地知道是哪個組件在哪個階段失敗了。

通過一個 initial 包來演示這個機制。它包含一個通用的拓撲排序算法和一套組件注冊機制。

package initialimport ("context" // 引入 context 包,以支持帶 context 的初始化函數"errors""fmt""your_project/internal/model/dao""your_project/internal/pkg/config""your_project/internal/pkg/logger""your_project/internal/pkg/trace""your_project/internal/service/adapter""your_project/internal/service/core"
)// InitializerType 定義了初始化器的類型標識符
type InitializerType string// 定義各種初始化器常量
const (CONFIG  InitializerType = "config"TRACE   InitializerType = "trace"LOGGER  InitializerType = "logger"STORE   InitializerType = "store"MANAGER InitializerType = "manager"ADAPTER InitializerType = "adapter"
)// 定義不同類型的初始化函數或接口,以便于在 initializers 映射中存儲
type Initializer interface {Init() error
}
type InitializerFunc func() error
type InitializerFuncWithCtx func(ctx context.Context) errorvar (// init_graph 定義了初始化器的依賴關系:key 依賴 value(s)// 例如: TRACE 依賴 CONFIG,意味著 CONFIG 必須在 TRACE 之前初始化。init_graph = map[InitializerType][]InitializerType{TRACE:   {CONFIG},  // TRACE 模塊依賴 CONFIG 模塊CONFIG:  {LOGGER},  // CONFIG 模塊依賴 LOGGER 模塊LOGGER:  {},        // LOGGER 模塊沒有外部依賴STORE:   {TRACE, CONFIG, LOGGER}, // STORE 模塊依賴 TRACE, CONFIG, LOGGERMANAGER: {STORE},   // MANAGER 模塊依賴 STORE 模塊ADAPTER: {MANAGER}, // ADAPTER 模塊依賴 MANAGER 模塊}// initializers 映射了初始化器類型到具體的初始化函數或接口實現initializers = map[InitializerType]interface{}{LOGGER:  InitializerFunc(logger.Init),      // 假設 logger.Init 是 func() errorCONFIG:  InitializerFunc(config.Init),      // 假設 config.Init 是 func() errorTRACE:   InitializerFunc(trace.Init),       // 假設 trace.Init 是 func() errorSTORE:   InitializerFunc(dao.Init),         // 假設 dao.Init 是 func() errorMANAGER: InitializerFunc(core.InitManager), // 假設 core.InitManager 是 func() errorADAPTER: InitializerFunc(adapter.Init),     // 假設 adapter.Init 是 func() error}
)// Run 執行所有組件的初始化,并按照依賴關系進行排序
func Run() error {// 1. 生成初始化順序init_order, err := topoSort(init_graph)if err != nil {return fmt.Errorf("failed to generate initialization order: %w", err)}// 2. 按照拓撲排序的順序逐一初始化組件for _, it := range init_order {if i, ok := initializers[it]; ok {switch initializer := i.(type) {case Initializer:if err := initializer.Init(); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}case InitializerFunc:if err := initializer(); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}case InitializerFuncWithCtx:// 對于需要 context 的初始化函數,這里傳入 nil context,實際應用中可能需要更具體的 contextif err := initializer(nil); err != nil {return fmt.Errorf("failed to initialize %s: %w", it, err)}default:return fmt.Errorf("unknown initializer type for %s", it)}}}return nil
}// topoSort 執行初始化依賴圖的拓撲排序 (Kahn's Algorithm)
// graph: key 依賴 value(s),表示如果 key 存在,它依賴 value(s) 中的所有節點。
// 換句話說,在依賴圖中,存在從 value -> key 的邊。
func topoSort(graph map[InitializerType][]InitializerType) ([]InitializerType, error) {// 1. 構建鄰接表 (adjList) 和計算入度 (inDegree)// adjList[node]: 存儲 node 指向的所有節點 (即 node 是誰的依賴)// inDegree[node]: 存儲有多少條邊指向 node (即 node 被多少個其他節點依賴)adjList := make(map[InitializerType][]InitializerType)inDegree := make(map[InitializerType]int)// 初始化所有在依賴圖中出現的節點for node := range graph {// 確保所有作為依賴出現但未作為主鍵的節點也被初始化if _, exists := adjList[node]; !exists {adjList[node] = []InitializerType{}}if _, exists := inDegree[node]; !exists {inDegree[node] = 0}}for _, dependencies := range graph {for _, dep := range dependencies {if _, exists := adjList[dep]; !exists {adjList[dep] = []InitializerType{}}if _, exists := inDegree[dep]; !exists {inDegree[dep] = 0}}}// 填充 adjList 和計算入度for dependent, dependencies := range graph {for _, dep := range dependencies {// 如果 dependent 依賴 dep (即 dep -> dependent 有一條邊)// adjList[dep] 存儲 dep 依賴的節點// inDegree[dependent]++adjList[dep] = append(adjList[dep], dependent)inDegree[dependent]++}}// 2. 找到所有入度為 0 的節點,放入隊列var queue []InitializerTypefor node, degree := range inDegree {if degree == 0 {queue = append(queue, node)}}// 3. 循環處理隊列中的節點var topoOrder []InitializerTypeprocessedNodesCount := 0 // 記錄已處理的節點數量for len(queue) > 0 {node := queue[0] // 取出隊列頭部節點queue = queue[1:]topoOrder = append(topoOrder, node) // 將節點加入拓撲排序結果processedNodesCount++// 遍歷當前節點所指向的所有鄰居(即依賴它的節點),減少它們的入度// 根據 `adjList` 的構建方式,adjList[node] 存儲的是 node 依賴的節點// 它們在圖中是以 node 為起點的邊所指向的節點for _, neighbor := range adjList[node] {inDegree[neighbor]--if inDegree[neighbor] == 0 {queue = append(queue, neighbor)}}}// 4. 檢查是否存在循環依賴// 如果拓撲排序結果的節點數量不等于圖中所有節點的數量,則存在循環if processedNodesCount != len(inDegree) {return nil, errors.New("cycle detected in graph")}return topoOrder, nil
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88362.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88362.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88362.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【物理重建】SPLART:基于3D高斯潑濺的鉸鏈估計與部件級重建

標題:《SPLART: Articulation Estimation and Part-Level Reconstruction with 3D Gaussian Splatting》 項目:https://github.com/ripl/splart 文章目錄 摘要一、引言二、相關工作2.1 數據驅動的鉸鏈學習2.2 物體重建的表征方法2.3 鉸鏈物體重建 三、方…

vscode中vue自定義組件的標簽失去特殊顏色高亮

遇到的問題 最近接觸了一個歷史遺留項目時,我遭遇了堪稱"史詩級屎山"的代碼結構——各種命名混亂的自定義組件和原生HTML標簽混雜在一起,視覺上完全無法區分。這讓我突然想起,之前在使用vue或者其他框架開發的時候,只要…

【Dify精講】第19章:開源貢獻指南

今天,讓我們深入 Dify 的開源貢獻體系,看看這個項目是如何在短短時間內聚集起一個活躍的開發者社區的。作為想要參與 Dify 開發的你,這一章將是你的實戰指南。 一、代碼貢獻流程:從想法到合并的完整路徑 1.1 貢獻前的準備工作 …

Web攻防-CSRF跨站請求偽造Referer同源Token校驗復用刪除置空聯動上傳或XSS

知識點: 1、Web攻防-CSRF-原理&檢測&利用&防御 2、Web攻防-CSRF-防御-Referer策略隱患 3、Web攻防-CSRF-防御-Token校驗策略隱患 一、演示案例-WEB攻防-CSRF利用-原理&構造 CSRF 測試功能點 刪除帳戶 更改電子郵件 如果不需要舊密碼,請…

Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights

“拖拽式大模型定制”(Drag-and-Drop LLMs: Zero-Shot Prompt-to-Weights)。 核心問題: 現在的大模型(比如GPT-4)很厲害,但想讓它們專門干好某個特定任務(比如解數學題、寫代碼)&am…

抖音視頻怎么去掉抖音號水印保存

隨著抖音成為短視頻平臺的領軍者,越來越多的人喜歡在上面拍攝、觀看和分享各種創意內容。對于用戶來說,下載抖音視頻并去除水印保存,以便后續使用或分享成為了一種常見需求。抖音號水印的存在雖然能幫助平臺追溯視頻源頭,但也讓許…

【RAG技術(1)】大模型為什么需要RAG

文章目錄 為什么需要RAG?RAG的工作原理關鍵的Embedding技術 RAG vs 模型微調:選擇的核心邏輯RAG的關鍵挑戰與解決思路1. 檢索質量決定一切2. 上下文長度限制 實際應用場景分析企業知識問答技術文檔助手法律咨詢系統 構建RAG系統的關鍵步驟總結 為什么需要…

JS紅寶書筆記 - 8.1 理解對象

對象就是一組沒有特定順序的值,對象的每個屬性或者方法都可由一個名稱來標識,這個名稱映射到一個值。可以把對象想象成一張散列表,其中的內容就是一組名值對,值可以是數據或者函數 創建自定義對象的通常方式是創建Object的一個新…

Meson介紹及編譯Glib庫

一.概述 1.Meson 的簡介 Meson(The Meson Build System)是個項目構建系統,類似的構建系統有 Makefile、CMake、automake …。 Meson 是一個由 Python 實現的開源項目,其思想是,開發人員花費在構建調試上的每一秒都是…

Qt元對象系統實踐指南:從入門到應用

目錄 摘要 元對象系統核心概念 項目示例:動態UI配置工具 元對象系統在項目中的應用 1. 信號與槽機制 2. 動態屬性系統 3. 運行時反射能力 4. 屬性綁定與響應 實際項目應用場景 動態UI配置 對象序列化 插件系統 性能優化建議 結論 參考資料 摘要 本文…

Kafka 與其他 MQ 的對比分析:RabbitMQ/RocketMQ 選型指南(一)

消息隊列簡介 在當今的分布式系統架構中,消息隊列(Message Queue,MQ)扮演著舉足輕重的角色。隨著業務規模的不斷擴大和系統復雜度的日益提升,各個組件之間的通信和協同變得愈發關鍵 。消息隊列作為一種異步的通信機制…

[創業之路-441]:行業 - 互聯網+移動互聯網和大數據時代的100個預言:技術個性、商業變革、社會重構、文化娛樂、環境、教育、健康醫療、未來生活方式

目錄 一、技術革新 二、商業變革 三、社會重構 四、文化與娛樂 六、環境與可持續發展 七、教育與知識傳播 八、健康與醫療 九、倫理與法律 十、未來生活方式 十一、終極預言 結語 在移動互聯網和大數據時代,技術革新正以前所未有的速度重塑社會、經濟與文…

基于STM32單片機WIFI無線APP控燈亮度滅設計

基于STM32單片機控燈設計 (程序+原理圖+設計報告) 功能介紹 具體功能: 本設計由STM32F103C8T6單片機核心電路兩位白色高亮LED燈電路WIFI模塊ESP8266電路電源電路組成。 1、stm32實時監測wifi數據,解析數…

學會C++中的vector的基本操作

vector 是 C 標準庫中的一個動態數組類,它可以在運行時自動調整大小,非常適合用于處理大小不確定的集合。下面是 vector 的常見用法示例,幫助你更好地理解如何使用它。 注意:所有用數組完成的任務都可以用vector完成。 1. 引入頭…

AI時代工具:AIGC導航——AI工具集合

大家好!AIGC導航是一個匯集多種AIGC工具的平臺,提供了豐富的工具和資源。 工具功能?: 該平臺整合了多樣的AIGC工具,涵蓋了繪畫創作、寫作輔助以及視頻制作等多個領域。繪畫工具能夠生成高質量的圖像作品;寫作工具支持從構思到潤色的全流程寫…

java-SpringBoot框架開發計算器網頁端編程練習項目【web版】

今天分享一個使用springboot 寫一個 前后端不分離的項目,網頁計算器,來熟悉springboot框架的使用。 java版本:8。 springboot:2.6.13 使用的技術是: Java Spring Boot Thymeleaf HTML/CSS/JS 構建的 Web 端簡約按鈕…

linux操作系統的軟件架構分析

一、linux操作系統的層次結構 1.內核的主要功能 1)進程管理 2)內存管理 3)文件系統 4)進程間通信、I/O系統、網絡通信協議等 2.系統程序 1)系統接口函數庫,比如libc 2)shell程序 3)編譯器、編輯…

淺談Java對象在內存中的存儲形式

我們知道計算機以二進制的方式存儲數據,以 64 位虛擬機為例,Java 對象在內存中的存儲形式為: 開頭是 8 個字節的 markword,用于標記對象的狀態。(也就是一個 long 型數據的大小。不妨記作對象頭里有一個長長的 markwo…

Android 開發問題:Wrong argument type for formatting argument ‘#2‘ in info_message

<string name"info_message">name: %1$s, age: %2$d</string>String str getString(R.string.info_message, "zs");在 Android 開發中&#xff0c;上述代碼&#xff0c;出現如下警告信息 Wrong argument type for formatting argument #2 in…

Vue+spring boot前后端分離項目搭建---小白入門

首先&#xff0c;介紹一下軟件準備工作 1.vscode 2.maven 3.vue搭建&#xff1a;node.jsyarnvite 一.后端搭建 打開vscode,建立一個springboot項目&#xff0c;參考鏈接&#xff1a;sping boot項目搭建 建立一個項目&#xff0c;目錄結構如下&#xff1a; helloController.java…