Skip to main content

DI與Constructor

當你的程式越來越大,你需要依賴的東西就會越來越多 這時候你就會發現前一篇提到的依賴注入是一件很擾人的事情 如果一個struct裡面有三個interface要依賴,而這三個interface也有各自的interface要依賴,那在未來如果正式要運行程式的時候,光是初始化就是一件令人頭痛的事情

因次,又有人專門為了這個問題寫出了一個工具來進行管理 在golang中有不少套專門在管理這種DI的問題的工具,這次我選擇的是Wire

這套工具他很棒的是他做的其實只是幫你產生自動的註冊的程式碼,而你要做的就是把他會用到的struct的constructor全部餵給他,接下來他就會去檢查,你這個constructor需要什麼樣結構的input,他就會自動幫你找到相對應你提供的內容幫你做填入

舉例 以下是我所提供的constructor

interface/rest_api/echo.go

type EchoServer struct {
fileStorer application.FileStorer
}

func NewEchoServer(fileStorer application.FileStorer) ServerInterface {
return &EchoServer{fileStorer: fileStorer}
}

application/file_store.go

type FileStorer interface {
UploadAsset(filename string, i []byte, s string) (file domain.CloudFile, err error)
GetPreviewLink(asset domain.CloudFile) (link string, err error)
}

type FileStore struct {
}

func NewFileStore() FileStorer {
return &FileStore{}
}

di/di.go

type DI struct {
RestAPI api.ServerInterface
}

func NewDI(restAPI api.ServerInterface) *DI {
return &DI{RestAPI: restAPI}
}

而接下來是wire的設定檔

di/di_wire.go

//go:build wireinject
// +build wireinject

// The build tag makes sure the stub is not built in the final build.
package di

import (
"2023_asset_management/application"
api "2023_asset_management/interface/rest_api"
"github.com/google/wire"
)

// InitializeAuthCmd creates an Auth Init Struct. It will error if the Event is staffed with
// a grumpy greeter.
func InitializeDICmd() *DI {
wire.Build(
application.NewFileStore,
api.NewEchoServer,
NewDI,
)
return nil
}

在事前,我們預先寫好了好幾個struct的constructor

func NewEchoServer(fileStorer application.FileStorer) ServerInterface
func NewFileStore() FileStorer
func NewDI(restAPI api.ServerInterface) *DI

而在這些constructor需要的參數,是可以全部都被滿足的

  1. 先用NewFileStore產生一個FileStorer
  2. 再用NewEchoServer產生ServerInterface
  3. 最後用NewDI產生一個DI

只要照著這個順序,就可以完成依賴注入,所以看到di_wire.go我們就放入了這三個func

緊接著執行

wire

接下來就會順利產生出

// Code generated by Wire. DO NOT EDIT.

//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package di

import (
"2023_asset_management/application"
"2023_asset_management/interface/rest_api"
)

import (
_ "github.com/google/subcommands"
)

// Injectors from di_wire.go:

// InitializeAuthCmd creates an Auth Init Struct. It will error if the Event is staffed with
// a grumpy greeter.
func InitializeDICmd() *DI {
fileStorer := application.NewFileStore()
serverInterface := api.NewEchoServer(fileStorer)
di := NewDI(serverInterface)
return di
}

此時,你就可以在你需要的地方使用 InitializeDICmd 這個func就可以輕鬆取得需要的DI物件了

另外分享幾個注意事項

在使用wire的時候,如果最後你的output是一個struct,那你在定義的時候要回傳同一個struct的空物件,如果是指標或是interface的話,要回傳nil,這點要特別注意

func NewDI(restAPI api.ServerInterface) *DI {
return &DI{}
}

func InitializeDICmd() *DI {
wire.Build(
application.NewFileStore,
api.NewEchoServer,
NewDI,
)
return nil
}

func NewDI(restAPI api.ServerInterface) DI {
return DI{}
}

func InitializeDICmd() DI {
wire.Build(
application.NewFileStore,
api.NewEchoServer,
NewDI,
)
return DI{}
}

另外,會建議在di的檔案中加上這段內容,來避免在產生程式碼的時候,出現找不到依賴的問題發生

_ "github.com/google/subcommands"
package di

import (
api "2023_asset_management/interface/rest_api"
_ "github.com/google/subcommands"
)

type DI struct {
RestAPI api.ServerInterface
}

func NewDI(restAPI api.ServerInterface) *DI {
return &DI{RestAPI: restAPI}
}