Dependency Injectino(依賴注入) 為了簡化初始化結構的設計模式。
在開始之前,我們先來檢視一下目前的 main.NewApp()
// file: ./cmd/olivine/app.go
func NewApp(cfg *data.Config) (*App, error) {
var handler server.Handler
var restorer server.Restorer
var aof service.AOF
if cfg.AOFEnabled {
var err error
aof, err = service.NewAOF(cfg, AOFPath)
if err != nil {
return nil, err
}
handler = server.NewHandler(cmd.NewCommands(repo.NewStorage()), server.NewAOFMiddleware(aof))
restorer = server.NewRestorer(aof, handler)
} else {
handler = server.NewHandler(cmd.NewCommands(repo.NewStorage()))
}
return &App{
cfg: cfg,
aof: aof,
srv: server.NewServer(handler, restorer),
}, nil
}
不難發現,結構的建立往往是環環相扣的,父結構依賴於子結構:
main.(*App),我們需要 server.(Server)。
server.(Server),所以需要 server.(Handler) 與 server.(Handler)
server.(Handler),所以需要 server.(AOF)
此時,就是使用依賴注入的好時機。
依賴注入可以像目前一樣手動管理(就像現在 main.NewApp() 中的做法一樣),而使用函式庫進行管理往往能夠簡化我們的心智負擔。
主流有兩種依賴注入實現方法:
go generate 自動生成依賴注入reflect 特性自動完成依賴注入我個人偏好使用編譯期注入,因為它可以生成可讀、可分析的 go 原始碼,所有事情都是可預期的;雖然執行期注入的方式可以讓開發者少寫很多 code,但 reflect 的使用上讓它有著太多的「魔法」。
google/wire 是一個很優秀的編譯期依賴注入函式庫,但是它在 2025 年 8 月就被標記為 archived。我個人使用 mazrean/kessoku 作為替代。
安裝它非常簡單:
$ go get -tool github.com/mazrean/kessoku/cmd/kessoku
註:go module 在 Go 1.24 之後支援工具管理,可以不用
go install在全域安裝工具。
main.NewApp()建立 cmd/olivine/kessoku.go:
// file: cmd/olivine/kessoku.go
package main
import (
"github.com/mazrean/kessoku"
)
//go:generate go tool kessoku $GOFILE
var _ = kessoku.Inject[*App](
"NewApp",
kessoku.Provide(func(cfg *data.Config) (*App, error) { return nil, nil }),
)
kessoku.Inject 會提示 kessoku 以 go generate 以給定的 cfg *data.Config 來產生 NewApp()。
我們可以直接將 main.NewApp() 原封不動地移到這裡:
var _ = kessoku.Inject[*App](
"NewApp",
kessoku.Provide(func(cfg *data.Config) (*App, error) {
var handler server.Handler
var restorer server.Restorer
var aof service.AOF
if cfg.AOFEnabled {
var err error
aof, err = service.NewAOF(cfg, AOFPath)
if err != nil {
return nil, err
}
handler = server.NewHandler(cmd.NewCommands(repo.NewStorage()), server.NewAOFMiddleware(aof))
restorer = server.NewRestorer(aof, handler)
} else {
handler = server.NewHandler(cmd.NewCommands(repo.NewStorage()))
}
return &App{
cfg: cfg,
aof: aof,
srv: server.NewServer(handler, restorer),
}, nil
}),
)
刪除原本的 main.NewApp() 之後便可以用 go generate ./... 產生 cmd/olivine/kessuku_band.go。
目前,我們仍是手動管理整個套件的相依性而未能享受到 kessoku 所帶來的便利。
利用 kessoku.Bind() 與 kessoku.Privde() 來改寫:
// file: ./cmd/olivine/kessoku.go
var _ = kessoku.Inject[*App](
"NewApp",
kessoku.Bind[service.AOF](kessoku.Provide(func(cfg *data.Config) (service.AOF, error) {
if !cfg.AOFEnabled {
return nil, nil
}
return service.NewAOF(cfg, AOFPath)
})),
kessoku.Bind[server.Handler](kessoku.Provide(func(cfg *data.Config, aof service.AOF) server.Handler {
middlewares := []server.Middleware{}
if cfg.AOFEnabled {
middlewares = append(middlewares, server.NewAOFMiddleware(aof))
}
return server.NewHandler(cmd.NewCommands(repo.NewStorage()), middlewares...)
})),
kessoku.Bind[server.Restorer](kessoku.Provide(func(cfg *data.Config, aof service.AOF, handler server.Handler) server.Restorer {
if !cfg.AOFEnabled {
return nil
}
return server.NewRestorer(aof, handler)
})),
kessoku.Bind[server.Server](kessoku.Provide(server.NewServer)),
kessoku.Provide(func(cfg *data.Config, aof service.AOF, srv server.Server) *App {
return &App{
cfg: cfg,
aof: aof,
srv: srv,
}
}),
)
有的時候,我們可能會定義一個介面:
interface Server {
Handle()
Restore()
}
但是卻在不同的 service 中定義了分別實現了這個介面:例如,在 service.(Handler) 中定義了 Handle()、而在 service.(Restorer) 定義了 Restore()。
這時,我們可以用一個匿名介面來解決這個問題:
kessoku.Provide(service.NewHandler), // create `*service.Handler{}`
kessoku.Provide(service.NewRestorer), // create `*service.Restorer{}`
kessoku.Bind[server.Server](
kessoku.Provide(func(h *service.Handler, r *service.Restorer) server.Server {
return &struct{
*service.Handler,
*service.Restorer,
}{ Handler: h, Restorer: r }
}),
),