使用 google-wire 對 Go 進行依賴注入

【導讀】需要依賴注入時,Go 有什麼實現方案?本文詳細介紹了使用 google/wire 庫實現依賴注入的方法。

google/wire 是 Go 語言的編譯時依賴注入框架,與 Spring IoC 一樣,wire 的目的也是讓開發者從對項目中大量依賴的創建和管理中解脫出來,但兩者在實現方式上有着很大的不同。

Go 中的依賴注入

func main() {
    NewUserStore(conf.Load(),db.InitMySQL())
}

func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}

這在小規模項目中效果很好,但當項目規模變大時,單個對象的創建往往需要多個依賴,而這些依賴通常還有它自己的依賴,這就導致對象的創建變得繁瑣,容易出錯。

wire 如何完成依賴注入

在開發中,我們創建對象的過程可以分爲兩步:

  1. 定義結構體的構造函數
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
  1. 調用結構體的構造函數進行實例化
NewUserStore(conf.Load(),db.InitMySQL())

第一步我們聲明瞭構造結構體需要的依賴,而 wire 做的,就是 幫我們 “寫” 好第二步的代碼

依賴聲明

爲了生成第二步中的代碼,我們首先需要將所有的構造函數 (準確的說是所有需要注入的依賴) 進行聲明並傳遞給 wire.Build 方法:

func setup(ctx context.Context) (sv *server.Server, clean func(), err error) {
    wire.Build(
        conf.Load,
        db.InitMySQL,
        userstore.NewUserStore,
    )
    return nil, nil, nil
}

setup 函數的內容最終會被 wire 用下面的實現替換 (接下來會進行說明):

func setup(ctx context.Context) (sv *server.Server, clean func(), err error) {
    config := conf.Load()
    engine := db.InitMySQL()
    userstoreHandler, clean1, err := userstore.NewUserStore(config,engine)
    if err != nil {
        clean()
        return nil, nil, err
    }
    sv := server.New(userstoreHandler)
    return sv, func() { clean1() }, nil
}

可以看到上面 wire 爲我們 “寫” 好的代碼其實跟我們自己將會寫的代碼是一樣的,可以很容易的讀懂。但隨着依賴增多,這樣的代碼就會增加,想象一下,如果項目中有上百個依賴,那麼就會有上百行的 New...(...), if err != nil {...} 代碼。

使用 go generate 生成依賴注入代碼

wire 就是做了這樣一件事:幫我們生成所有需要的對象創建代碼,開發時我們只需要在結構體的構造函數中聲明自己需要什麼。

wire 在實際項目中的使用步驟:

  1. 通過 wire.Bind 方法進行依賴聲明,假設這部分代碼放在 inject.go 文件中

  2. 使用 go generate http://c.biancheng.net/view/4442.html 命令生成代碼

go generate 需要通過 //go:generate 註釋的方式使用,創建 wire_gen.go 文件並添加該註釋:

//go:generate wire
  1. 在項目目錄下執行 go generate 命令。

運行該命令時,它將掃描與當前包相關的源代碼文件,找出所有包含 //go:generate 的特殊註釋,提取並執行該特殊註釋後面的命令。

至此,你就可以看到 wire_gen.go 裏已經生成好 func setup(ctx context.Context) (sv *server.Server, clean func(), err error) 方法,方法體爲依賴注入 (對象創建) 代碼。

  1. 最後一步,在 main 方法中調用 setup 方法 (這裏假設我們的項目是一個提供 RESTful 接口的 HTTP 服務)
package main

func main() {
    ctx := context.Background()
    srv, cleanup, err := setupGCP(ctx)
    if err != nil {
        log.Fatalf("failed to setup the server, got error: %s", err)
    }
    defer cleanup()

    log.Fatal(srv.ListenAndServe(":8080"))
}

需要注意的是,如果 inject.gowire_gen.go 在同一個 package 下,那此時 IDE 會提示語法錯誤,因爲兩個文件下存在方法簽名一樣的兩個方法。此時可以使用 //+build go build 構建約束對其中一個文件進行排除。

首先在 inject.go 中加入如下的構建標籤:

//+build wireinject

然後在 wire_gen.go 中加入構建約束使 go build 時排除帶 wireinject 標籤的文件:

//+build !wireinject

使用 wire.Bind, wire.Value 等方法聲明和組織依賴

wire 在 go generate 掃描代碼時從 wire.Bind 中提取項目依賴關係併爲我們生成依賴注入代碼,那我們要怎樣將依賴關係更高效,清晰的 “告知” 給 wire 呢?

wire 提供了幾個函數幫助我們組織和聲明項目中的依賴關係:

轉自:cafebabe

segmentfault.com/a/1190000039060354

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JLxNf4qX-21XKt34L-yusw