在現代軟體開發中,依賴注入(Dependency Injection,簡稱 DI)已成為一種不可或缺的設計模式和程式設計正規化。它不僅能夠提高程式碼的可維護性和可測試性,還能幫助開發者構建更加靈活、松耦合的系統。本文將帶您深入瞭解依賴注入的核心概念,探討它如何改變我們設計和實現軟體的方式,並透過實際的程式碼示例,展示如何在專案中有效地應用這一技術。
相信各位對 依賴注入 不會陌生,相信大多數使用 Java
或者其他 JVM
語言作為主力語言的測試同行來說,更多經驗是集中在 Spring
框架學習和使用當中。在Java
和Spring
框架中,依賴注入是構建靈活、可維護應用程式的核心技術。Spring
的IoC
容器透過構造器注入、Setter
注入或欄位注入等方式自動管理物件間的依賴關係。開發者使用@Autowired
、@Component
等註解或 XML 配置來宣告依賴和元件,讓 Spring 負責物件的建立和生命週期管理。這種方法不僅簡化了程式碼結構,還提高了應用的可測試性和模組化程度,使得 Java 開發者能夠專注於業務邏輯的實現,而不必手動處理複雜的物件依賴關係。
而對於 Go
語言來說,顯然沒有類似 Spring
一統天下的局面。對於提升程式設計效率,可以說是百家爭鳴,好不熱鬧。在 Go
語言中,雖然沒有像 Java
或 C#
那樣內建的依賴注入框架,但依賴注入的需求同樣存在。開發者通常需要手動注入依賴項,這種方式在應用規模擴大後變得繁瑣且易出錯。fx
框架提供了一種自動化和模組化的依賴注入方式,使開發者可以更專注於業務邏輯,而不是依賴管理。
其中 fx
顯得比較耀眼奪目,下面我們來開始 fx
框架的學習。
fx 介紹
fx
框架是由 Uber
開發的。為了應對自身複雜的分散式系統和微服務架構的需求,Uber
開發了許多開源工具和框架,其中包括 fx
。fx
框架主要用於簡化 Go
語言應用程式的依賴注入和生命週期管理,並且已經在 Uber 內部和外部的許多專案中得到了廣泛應用。
fx
框架是一個用於構建 Go
應用程式的依賴注入框架,它簡化了應用程式的初始化、啟動和停止過程。fx
透過自動管理依賴關係,使開發者能夠專注於業務邏輯,而無需手動處理依賴注入。fx
提供了模組化的依賴注入方式,透過 fx.Provide
註冊依賴項,透過 fx.Invoke
呼叫需要的元件。同時,fx.In
和 fx.Out
結構體幫助開發者更方便地宣告和管理依賴項,支援按名稱和分組注入。fx.Lifecycle
是 fx
的核心功能之一,它允許開發者在應用程式的不同生命週期階段執行特定邏輯。透過 fx.Hook
,可以在應用啟動和停止時執行初始化和清理操作,如連線資料庫、啟動後臺任務等。
fx
的模組化設計使其易於擴充套件和維護,透過將各個功能模組化,開發者可以靈活地組合和重用不同的元件。總之,fx
提供了簡潔且強大的工具,使得構建複雜的 Go 應用程式更加高效和可維護。
fx 依賴注入
首先我們來先看一個 fx
框架啟動的例子。
func main() {
app := fx.New() //建立一個fx.App例項
app.Run() //執行fx.App例項
}
這是一個標準的語法,所有的應用都可以用這個語法進行建立和啟動。其中 fx.New()
方法為: func New(opts ...Option) *App {}
,其中引數 Option
是我們後面主要學習物件。
說到 依賴注入 ,我首先意識到兩個概念,就是依賴物件的提供者和使用者。得有一些物件的建立需要依賴其他物件,然後還需要提供被依賴物件,然後透過 fx
框架將這些複雜的邏輯關係進行管理,並且提供簡單的 API 給使用者。
fx 的核心功能是依賴注入,它簡化了依賴項的管理和注入過程,主要透過以下 API 實現:
- fx.Provide:用於註冊提供者函式,這些函式會返回應用程式中需要的依賴項。
下面我們透過一個例子來演示 fx
如何進行簡單依賴注入:
package main
import (
"go.uber.org/fx"
"go.uber.org/zap")
func main() {
app := fx.New( //建立fx.App例項
fx.Provide(NewTester, func() *Age {
return &Age{Num: 18} //提供Age例項
}, func() *zap.Logger {
production, _ := zap.NewProduction() //提供zap.Logger例項
return production
}), //提供NewTester函式
)
app.Run() //執行fx.App例項
}
type Age struct {
Num int //年齡,整型
}
type Tester struct {
Log *zap.Logger //日誌
Age *Age //年齡
}
func NewTester(age *Age, log *zap.Logger) *Tester {
return &Tester{
Age: age,
Log: log,
}
}
這段程式碼展示瞭如何使用 Uber
的 Fx
框架進行依賴注入和應用程式啟動。程式碼透過 fx.Provide
提供了三個建構函式:一個用於 Age 例項,一個用於 zap.Logger
例項,另一個用於 Tester 例項。然後透過
fx.New
建立一個 Fx
應用,並透過 app.Run()
執行這個應用。下面是更詳細的解釋:
-
依賴注入:
-
fx.Provide(NewTester, ...)
:透過fx.Provide
註冊三個建構函式。 -
NewTester
:這是一個建構函式,接受*Age
和*zap.Logger
作為引數,並返回一個*Tester
。 - 匿名函式
func() *Age
:提供一個*Age
例項,設定Num
為18
。 - 匿名函式
func() *zap.Logger
:建立並返回一個zap.Logger
例項,用於日誌記錄。
-
-
執行應用:
-
app.Run()
:啟動Fx
應用。Fx
將根據註冊的建構函式自動注入依賴,並呼叫相應的初始化邏輯。
-
-
型別定義:
-
Age
:一個簡單的結構體,包含一個Num
欄位,用於表示年齡。 -
Tester
:一個結構體,包含兩個欄位:Log
(型別為*zap.Logger
)和Age
(型別為*Age
)。它們將由Fx
框架自動注入。
-
-
建構函式
NewTester
:- 接受
*Age
和*zap.Logger
作為引數,並返回一個*Tester
例項。
- 接受
這個例子中,既可以將建立方法傳給 fx.Provide
也可以使用匿名方法,相比較來說是靈活的。這裡不建議使用匿名方法,因為寫多了容易亂,特別是對於 zap.Logger
這種物件來講,真實的建立程式碼可能超過 20 行,用匿名方法更是災難了。
生命週期管理
fx.Lifecycle
是 Uber Fx
框架中用於管理應用程式生命週期的一部分。它允許你在應用程式的啟動和停止階段執行特定的邏輯。fx.Lifecycle
提供了一種新增啟動和停止鉤子的機制,使你能夠在應用程式的不同階段執行初始化和清理工作。
這裡用到了 fx.Invoke
方法,顧名思義,就是呼叫某些方法,可以傳入已有的方法名也可以使用匿名方法(不建議)。 fx
宣告週期管理中 Hook
就是透過這個 API
實現的,案例如下:
package main
import (
"context"
"go.uber.org/fx" "go.uber.org/zap")
func main() {
app := fx.New( //建立fx.App例項
fx.Provide(NewTester, func() *Age {
return &Age{Num: 18} //提供Age例項
}, func() *zap.Logger {
production, _ := zap.NewProduction() //提供zap.Logger例項
return production
}), //提供NewTester函式
fx.Invoke(register), //呼叫register函式
)
app.Run() //執行fx.App例項
}
type Age struct {
Num int //年齡,整型
}
type Tester struct {
Log *zap.Logger //日誌
Age *Age //年齡
}
func NewTester(age *Age, log *zap.Logger) *Tester {
return &Tester{
Age: age,
Log: log,
}
}
func register(lifecycle fx.Lifecycle, tester *Tester) {
lifecycle.Append(fx.Hook{
OnStart: func(context.Context) error {
tester.Log.Info("Starting server")
return nil
},
OnStop: func(context.Context) error {
tester.Log.Info("Stopping server")
return nil
},
})
}
新增的 register
方法,透過 fx.Lifecycle
在應用程式啟動和停止時執行一些自定義邏輯。具體的含義如下:
-
register
函式:- 這個函式接收兩個引數:
-
lifecycle fx.Lifecycle
:用於管理應用程式的生命週期。 -
tester *Tester
:一個包含zap.Logger
和Age
的結構體例項。
-
- 這個函式接收兩個引數:
-
lifecycle.Append(fx.Hook{...})
:-
lifecycle.Append
方法用於嚮應用程式的生命週期中新增鉤子。 -
fx.Hook
結構體包含兩個回撥函式:OnStart
和OnStop
,分別在應用程式啟動和停止時呼叫。
-
-
OnStart
函式:-
OnStart: func(context.Context) error { ... }
:- 這是一個在應用程式啟動時執行的回撥函式。
- 這個函式記錄一條日誌資訊 "Starting server",表示伺服器正在啟動。
- 函式返回
nil
,表示啟動過程中沒有發生錯誤。
-
-
OnStop
函式:-
OnStop: func(context.Context) error { ... }
:- 這是一個在應用程式停止時執行的回撥函式。
- 這個函式記錄一條日誌資訊 "Stopping server",表示伺服器正在停止。
- 函式返回
nil
,表示停止過程中沒有發生錯誤。
-
在 Uber
的 Fx
框架中,fx.Hook
和 fx.Lifecycle
通常一起使用,用於管理應用程式的生命週期和執行特定的初始化或清理邏輯。下面分別介紹它們的使用場景:
fx.Lifecycle
的使用場景
-
管理資源生命週期:
- 資料庫連線:在應用程式啟動時建立資料庫連線,在停止時關閉連線。
- 快取初始化:在應用程式啟動時載入和初始化快取,在停止時清理快取。
- 訊息佇列連線:在應用程式啟動時連線訊息佇列,在停止時斷開連線。
-
服務啟動和停止:
- Web 伺服器:在應用程式啟動時啟動 Web 伺服器,在停止時優雅地關閉伺服器。
- 定時任務:在應用程式啟動時啟動定時任務,在停止時停止定時任務。
-
日誌記錄和監控:
- 在應用程式的不同階段記錄日誌,如 "應用啟動" 和 "應用停止"。
- 在應用程式啟動和停止時傳送監控指標,如 CPU 使用率、記憶體使用等。
fx.Hook
的使用場景
-
自定義初始化和清理邏輯:
-
啟動時:
- 初始化資料庫:在應用程式啟動時初始化資料庫連線池。
- 載入配置:讀取和載入應用程式的配置檔案。
- 註冊 HTTP 路由:在應用程式啟動時註冊各種 HTTP 路由和中介軟體。
-
停止時:
- 關閉資料庫連線:優雅地關閉資料庫連線。
- 清理資源:釋放所有的資源,確保應用程式停止時不留下任何未處理的事務。
-
啟動時:
-
啟動和停止通知:
- 在應用程式啟動時傳送通知,如透過郵件或訊息佇列通知團隊。
- 在應用程式停止時執行最後的清理工作,併傳送應用程式關閉通知。
-
除錯和審計:
- 記錄應用程式啟動和停止時的除錯資訊,幫助排查問題。
- 在停止時記錄審計日誌,如記錄哪些資源被關閉或清理了。
相信透過基礎的學習,已經對 fx
有了瞭解,並且可以著手構建測試專案了。下期我們繼續分享 fx
上手進階內容。
- 2021 年原創合集
- 2022 年原創合集
- 2023 年原創合集
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI 自動化
- 測試理論雞湯
- 社群風采&影片合集