golang 注入工具 wire

wire 是個啥米

wire 是一個靜態的注入工具,不像是其他的注入工具(uber/fx, facebook/inject)使用 reflect 來達成,他選擇使用 gen code 的方式產生可以使用的注入程式碼。
能夠在編譯階段將注入的動作完成,而不是在執行程式的當下才知道發生了什麼錯誤

Q: Should I use Wire for small applications?

Probably not. Wire is designed to automate more intricate setup code found in larger applications. For small applications, hand-wiring dependencies is simpler. 不複雜的小型專案還是手動注入會更乾淨喔~

go build tag

菜鳥如我這時候才知道,在 .go 檔案最前面加入 build tag 的檔案,在編譯的時候是不會被加進去的(ex: //+build foo ) 所以可以放重複的 function 在同一個 package 裡面,這個操作在 lorca 那篇也有出現過,可以交叉比較一下~

這是一個 wire.go 檔案

//+build wireinject

package main

import (
	"learnGo/wire/repo"
	"learnGo/wire/service"

	"github.com/google/wire"
)

func CreateService() (service.Service, error) {
	wire.Build(repo.NewRepo, service.NewService)
	panic("never be triggered")
	return nil, nil
}

這是一個 wire_gen.go 檔案,他們在同一個 package 下,定義著相同的函式,但是編譯的時候只會看 wire_gen.go,因為 wire.go 的最前面有標記 build tag

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
	"learnGo/wire/repo"
	"learnGo/wire/service"
)

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

// Injectors from wire.go:

func CreateService() (service.Service, error) {
	repoRepo := repo.NewRepo()
	serviceService := service.NewService(repoRepo)
    return serviceService, nil
}

假設我們有個 main.go 來執行…

package main

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

func main() {
	svc, err := CreateService()
	if err != nil {
		panic(err)
	}
	svc.SayHello("wire main package")
}

執行 go build -tags wireinject 這樣會使用 wire.go 來編譯,長出來的執行檔就會跑 wire.go:14 的 panic

根據這樣的經驗,進階可以利用 golang build tag 的功能,依照不同的需求來編譯出不同的執行檔

踩到坑: 如果用 go run *.go 來跑,那麼你會得到 {function} redeclared in this block,你的所有檔案還是會被拿來用QQ

Wire 的使用方法

使用上一段落的範例, wire_gen.go 是怎麼長出來的呢? 首先需要安裝 wire

go get github.com/google/wire/cmd/wire

然後輸入

wire

就可以看見 wire_gen.go 長出來囉
詳細看一下內容的前幾行,

// 提醒你和編譯器這是一個產生出來的檔案,不是給人類直接修改的
// Code generated by Wire. DO NOT EDIT.

// 可以使用 `go generate` 指令來觸發後面的指令,
// 在這裡會幫你跑 wire 來 gen code
//go:generate wire

// 註記 build tags 不存在 wireinject 用的
//+build !wireinject

// ...

注入 Interface

在 github 的 document guide 上有寫,很實用也寫一份易讀版

任務: repo.NewRepo 回傳 *repo.DefaultRepo (他實作 repo.Repoer) svc.NewService 需要一個 repo.Repoer 因此我們需要多做一層將 pointer 轉成 interface

// Repo ...
type Repoer interface {
	// 
}

// NewRepo ...
func NewRepo(cfg *config.Config) *DefultRepo {
	return &DefultRepo{
		Prefix: cfg.DBCfg.Prefix,
	}
}

// NewService ...
func NewService(repo repo.Repoer) *DefultService {
	return &DefultService{
		repo: repo,
	}
}

var Set = wire.NewSet(
    // 產生 *DefaultRepo
    repo.NewRepo,
    
    // 將 *DefaultRepo 轉成 Repoer
    wire.Bind(new(repo.Repo), new(*repo.DefultRepo)),
    
    // 注入需要 Repoer
    service.NewService,
)

Ref

https://medium.com/@dche423/master-wire-cn-d57de86caa1b https://github.com/google/wire/blob/main/docs/guide.md