在 Go 中如何優(yōu)雅的使用 wire 依賴注入工具提高開(kāi)發(fā)效率?下篇
共 26301字,需瀏覽 53分鐘
·
2024-06-17 11:37
在《在 Go 中如何優(yōu)雅的使用 wire 依賴注入工具提高開(kāi)發(fā)效率?上篇》,我講解了 Go 語(yǔ)言中依賴注入工具 wire 的基本使用及高級(jí)用法。本篇就來(lái)介紹下 wire 的生產(chǎn)實(shí)踐。
Wire 生產(chǎn)實(shí)踐
這里以一個(gè) user 服務(wù)作為示例,演示下一個(gè)生產(chǎn)項(xiàng)目中是如何使用 wire 依賴注入工具的。
user 項(xiàng)目目錄結(jié)構(gòu)如下:
$ tree user
user
├── assets
│ ├── curl.sh
│ └── schema.sql
├── cmd
│ └── main.go
├── go.mod
├── go.sum
├── internal
│ ├── biz
│ │ └── user.go
│ ├── config
│ │ └── config.go
│ ├── controller
│ │ └── user.go
│ ├── model
│ │ └── user.go
│ ├── router.go
│ ├── store
│ │ └── user.go
│ ├── user.go
│ ├── wire.go
│ └── wire_gen.go
└── pkg
├── api
│ └── user.go
└── db
└── db.go
12 directories, 16 files
NOTE:
user項(xiàng)目源碼在此(https://github.com/jianghushinian/blog-go-example/tree/main/wire/user),你可以點(diǎn)擊查看,建議下載下來(lái)執(zhí)行啟動(dòng)下程序,加深理解。
這是一個(gè)典型的 Web 應(yīng)用,用來(lái)對(duì)用戶進(jìn)行 CRUD。不過(guò)為了保持代碼簡(jiǎn)潔清晰,方便理解,user 項(xiàng)目?jī)H實(shí)現(xiàn)了創(chuàng)建用戶的功能。
我先簡(jiǎn)單介紹下各個(gè)目錄的功能。
assets 努目錄用于存放項(xiàng)目資源。schema.sql 中是建表語(yǔ)句,curl.sh 保存了一個(gè) curl 請(qǐng)求命令,用于測(cè)試創(chuàng)建用戶功能。
cmd 中當(dāng)然是程序入口文件。
internal 下保存了項(xiàng)目業(yè)務(wù)邏輯。
pkg 目錄存放可導(dǎo)出的公共庫(kù)。api 用于存放請(qǐng)求對(duì)象;db 用于構(gòu)造數(shù)據(jù)庫(kù)對(duì)象。
項(xiàng)目設(shè)計(jì)了 4 層架構(gòu),controller 即對(duì)應(yīng) MVC 經(jīng)典模式中的 Controller,biz 是業(yè)務(wù)層,store 層用于跟數(shù)據(jù)庫(kù)交互,還有一個(gè) model 層定義模型,用于映射數(shù)據(jù)庫(kù)表。
router.go 用于注冊(cè)路由。
user.go 用于定義創(chuàng)建和啟動(dòng) user 服務(wù)的應(yīng)用對(duì)象。
而 wire.go 和 wire_gen.go 兩個(gè)文件就無(wú)需我過(guò)多講解了。
NOTE: 本項(xiàng)目目錄結(jié)構(gòu)遵循最佳實(shí)踐,可以參考我的另一篇文章《如何設(shè)計(jì)一個(gè)優(yōu)秀的 Go Web 項(xiàng)目目錄結(jié)構(gòu)》。
簡(jiǎn)單介紹完了目錄結(jié)構(gòu),再來(lái)梳理下我們所設(shè)計(jì)的 4 層架構(gòu)依賴關(guān)系:首先 controller 層依賴 biz 層,然后 biz 層又依賴 store 層,接著 store 層又依賴了數(shù)據(jù)庫(kù)(即依賴 pkg/db/),而 controller、biz、store 這三者又都依賴 model 層。
現(xiàn)在看了我的講解,你可能有些發(fā)懵,沒(méi)關(guān)系,下面我將主要代碼邏輯都貼出來(lái),加深你的理解。
assets/schema.sql 中的建表語(yǔ)句如下:
CREATE TABLE `user`
(
`id` BIGINT NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255),
`nickname` VARCHAR(255),
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`createdAt` DATETIME,
`updatedAt` DATETIME,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
user 項(xiàng)目?jī)H有一張表。
cmd/main.go 代碼如下:
package main
import (
user "github.com/jianghushinian/blog-go-example/wire/user/internal"
"github.com/jianghushinian/blog-go-example/wire/user/internal/config"
"github.com/jianghushinian/blog-go-example/wire/user/pkg/db"
)
func main() {
cfg := &config.Config{
MySQL: db.MySQLOptions{
Address: "127.0.0.1:3306",
Database: "user",
Username: "root",
Password: "123456",
},
}
app, cleanup, err := user.NewApp(cfg)
if err != nil {
panic(err)
}
defer cleanup()
app.Run()
}
入口函數(shù) main 中先創(chuàng)建了配置對(duì)象 cfg,接著實(shí)例化 app 對(duì)象,最后調(diào)用 app.Run() 啟動(dòng) user 服務(wù)。
這也是一個(gè)典型的 Web 應(yīng)用啟動(dòng)步驟。
Config 定義如下:
type Config struct {
MySQL db.MySQLOptions `json:"mysql" yaml:"mysql"`
}
type MySQLOptions struct {
Address string
Database string
Username string
Password string
}
user.go 中的 App 定義如下:
// App 代表一個(gè) Web 應(yīng)用
type App struct {
*config.Config
g *gin.Engine
uc *controller.UserController
}
// NewApp Web 應(yīng)用構(gòu)造函數(shù)
func NewApp(cfg *config.Config) (*App, func(), error) {
gormDB, cleanup, err := db.NewMySQL(&cfg.MySQL)
if err != nil {
return nil, nil, err
}
userStore := store.New(gormDB)
userBiz := biz.New(userStore)
userController := controller.New(userBiz)
engine := gin.Default()
app := &App{
Config: cfg,
g: engine,
uc: userController,
}
return app, cleanup, err
}
// Run 啟動(dòng) Web 應(yīng)用
func (a *App) Run() {
// 注冊(cè)路由
InitRouter(a)
if err := a.g.Run(":8000"); err != nil {
panic(err)
}
}
App 代表一個(gè) Web 應(yīng)用,它嵌入了配置、gin 框架的 *Engine 對(duì)象,以及 controller。
NewApp 是 App 的構(gòu)造函數(shù),通過(guò) Config 來(lái)創(chuàng)建一個(gè) *App 對(duì)象。
根據(jù)其內(nèi)部代碼邏輯,也能看出項(xiàng)目的 4 層架構(gòu)依賴關(guān)系:創(chuàng)建 App 對(duì)象依賴 Config,Config 是通過(guò)參數(shù)傳遞進(jìn)來(lái)的;*Engine 對(duì)象可以通過(guò) gin.Default() 得到;而 userController 則通過(guò) controller.New 創(chuàng)建,controller 依賴 biz,biz 依賴 store,store 依賴 *gorm.DB。
可以發(fā)現(xiàn),依賴關(guān)系非常清晰,并且我們使用了依賴注入思想編寫代碼,那么此時(shí),正是 wire 的用武之地。
不過(guò),我們先不急著講解如何在這里使用 wire。我先將項(xiàng)目剩余主要代碼貼出來(lái),便于你理解這個(gè) Web 應(yīng)用。
我們可以通過(guò) pkg/db/db.go 中的 NewMySQL 創(chuàng)建出 *gorm.DB 對(duì)象:
// NewMySQL 根據(jù)選項(xiàng)構(gòu)造 *gorm.DB
func NewMySQL(opts *MySQLOptions) (*gorm.DB, func(), error) {
// 可以用來(lái)釋放資源,這里僅作為示例使用,沒(méi)有釋放任何資源,因?yàn)?nbsp;gorm 內(nèi)部已經(jīng)幫我們做了
cleanFunc := func() {}
db, err := gorm.Open(mysql.Open(opts.DSN()), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
return db, cleanFunc, err
}
有了 *gorm.DB 就可以創(chuàng)建 store 對(duì)象了,internal/store/user.go 主要代碼如下:
package store
...
// ProviderSet 一個(gè) Wire provider sets,用來(lái)初始化 store 實(shí)例對(duì)象,并將 UserStore 接口綁定到 *userStore 類型實(shí)現(xiàn)上
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserStore), new(*userStore)))
// UserStore 定義 user 暴露的 CRUD 方法
type UserStore interface {
Create(ctx context.Context, user *model.UserM) error
}
// UserStore 接口實(shí)現(xiàn)
type userStore struct {
db *gorm.DB
}
// 確保 userStore 實(shí)現(xiàn)了 UserStore 接口
var _ UserStore = (*userStore)(nil)
// New userStore 構(gòu)造函數(shù)
func New(db *gorm.DB) *userStore {
return &userStore{db}
}
// Create 插入一條 user 記錄
func (u *userStore) Create(ctx context.Context, user *model.UserM) error {
return u.db.Create(&user).Error
}
有了 store 就可以創(chuàng)建 biz 對(duì)象了,internal/biz/user.go 主要代碼如下:
package biz
...
// ProviderSet 一個(gè) Wire provider sets,用來(lái)初始化 biz 實(shí)例對(duì)象,并將 UserBiz 接口綁定到 *userBiz 類型實(shí)現(xiàn)上
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserBiz), new(*userBiz)))
// UserBiz 定義 user 業(yè)務(wù)邏輯操作方法
type UserBiz interface {
Create(ctx context.Context, r *api.CreateUserRequest) error
}
// UserBiz 接口的實(shí)現(xiàn)
type userBiz struct {
s store.UserStore
}
// 確保 userBiz 實(shí)現(xiàn)了 UserBiz 接口
var _ UserBiz = (*userBiz)(nil)
// New userBiz 構(gòu)造函數(shù)
func New(s store.UserStore) *userBiz {
return &userBiz{s: s}
}
// Create 創(chuàng)建用戶
func (b *userBiz) Create(ctx context.Context, r *api.CreateUserRequest) error {
var userM model.UserM
_ = copier.Copy(&userM, r)
return b.s.Create(ctx, &userM)
}
接著,有了 biz 就可以創(chuàng)建 controller 對(duì)象了,internal/controller/user.go 主要代碼如下:
package controller
...
// UserController 用來(lái)處理用戶請(qǐng)求
type UserController struct {
b biz.UserBiz
}
// New controller 構(gòu)造函數(shù)
func New(b biz.UserBiz) *UserController {
return &UserController{b: b}
}
// Create 創(chuàng)建用戶
func (ctrl *UserController) Create(c *gin.Context) {
var r api.CreateUserRequest
if err := c.ShouldBindJSON(&r); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
return
}
if err := ctrl.b.Create(c, &r); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"err": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{})
}
這些對(duì)象都有了,就可以調(diào)用 NewApp 構(gòu)造出 App 了。
App 在啟動(dòng)前,還會(huì)調(diào)用 InitRouter 進(jìn)行路由注冊(cè):
// InitRouter 初始化路由
func InitRouter(a *App) {
// 創(chuàng)建 users 路由分組
u := a.g.Group("/users")
{
u.POST("", a.uc.Create)
}
}
現(xiàn)在 user 項(xiàng)目邏輯已經(jīng)清晰了,是時(shí)候啟動(dòng)應(yīng)用程序了:
$ cd user
$ go run cmd/main.go
程序啟動(dòng)后,會(huì)監(jiān)聽(tīng) 8000 端口,可以使用 assets/curl.sh 中的 curl 命令進(jìn)行訪問(wèn):
$ curl --location --request POST 'http://127.0.0.1:8000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "[email protected]",
"nickname": "江湖十年",
"username": "jianghushinian",
"password": "pass"
}'
不出意外,你將在數(shù)據(jù)庫(kù)中看到新創(chuàng)建的用戶。
執(zhí)行以下 SQL:
USE user;
SELECT * FROM user;
將輸出新創(chuàng)建出來(lái)的用戶。
+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+
| id | email | nickname | username | password | createdAt | updatedAt |
+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+
| 1 | [email protected] | 江湖十年 | jianghushinian | pass | 2024-06-11 00:01:35 | 2024-06-11 00:01:35 |
+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+
現(xiàn)在,是時(shí)候討論如何在 user 項(xiàng)目中使用 wire 來(lái)提高開(kāi)發(fā)效率了。
回顧下 NewApp 的定義:
// NewApp Web 應(yīng)用構(gòu)造函數(shù)
func NewApp(cfg *config.Config) (*App, func(), error) {
gormDB, cleanup, err := db.NewMySQL(&cfg.MySQL)
if err != nil {
return nil, nil, err
}
userStore := store.New(gormDB)
userBiz := biz.New(userStore)
userController := controller.New(userBiz)
engine := gin.Default()
app := &App{
Config: cfg,
g: engine,
uc: userController,
}
return app, cleanup, err
}
其實(shí)這里面一層層的依賴注入,都是套路代碼,基本上一個(gè) Web 應(yīng)用都可以按照這個(gè)套路來(lái)寫。
這就涉及到套路代碼寫多了其實(shí)是比較煩的,這還只是一個(gè)微型項(xiàng)目,如果是中大項(xiàng)目,可以預(yù)見(jiàn)這個(gè) NewApp 代碼量會(huì)很多,所以是時(shí)候讓 wire 出場(chǎng)了:
func NewApp(cfg *config.Config) (*App, func(), error) {
engine := gin.Default()
app, cleanup, err := wireApp(engine, cfg, &cfg.MySQL)
return app, cleanup, err
}
我們可以將 NewApp 中的主邏輯全部拿走,放在 wireApp 中(在 wire.go 文件中)。
wireApp 定義如下:
func wireApp(engine *gin.Engine, cfg *config.Config, mysqlOptions *db.MySQLOptions) (*App, func(), error) {
wire.Build(
db.NewMySQL,
store.ProviderSet,
biz.ProviderSet,
controller.New,
wire.Struct(new(App), "*"),
)
return nil, nil, nil
}
有了前文的講解,其實(shí)這里無(wú)需我多言,你都能夠看懂,因?yàn)椴](méi)有新的知識(shí)。
不過(guò)我們還是簡(jiǎn)單分析下這里都用到了 wire 的哪些特性。
首先 wireApp 返回值是典型的三件套:(*App, func(), error),對(duì)象、清理函數(shù)和 error。
這里使用了兩個(gè) wire.ProviderSet 進(jìn)行分組,定義如下:
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserStore), new(*userStore)))
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserBiz), new(*userBiz)))
并且在構(gòu)造 wire.ProviderSet 時(shí),還使用了 wire.Bind(new(UserStore), new(*userStore)) 將一個(gè)結(jié)構(gòu)體綁定到接口。
最后,我們使用了 struct 作為 provider:wire.Struct(new(App), "*") ,通配符 * 用來(lái)表示所有字段。
在真實(shí)項(xiàng)目中,wire 就這么使用。
如果你覺(jué)得 user 項(xiàng)目太小,使用 wire 的價(jià)值還不夠大。你可以看看 onex 項(xiàng)目,比如 usercenter 中的代碼(https://github.com/superproj/onex/tree/master/internal/usercenter),這個(gè)開(kāi)源項(xiàng)目完全是生產(chǎn)級(jí)別。
為什么選擇 Wire
通常來(lái)說(shuō),這部分內(nèi)容是應(yīng)該放在文章開(kāi)頭的。我將其放在這里,目的是為了讓你熟悉 wire 后,再回過(guò)頭來(lái)對(duì)比,wire 有哪些優(yōu)勢(shì),加深你對(duì)為什么選擇 wire 的理解。
其實(shí) Go 生態(tài)中依賴注入工具不止有 Google 的 wire 一家獨(dú)大,還有 Uber 開(kāi)源的 dig,以及 Facebook 開(kāi)源的 inject 比較流行。
但我為什么要選擇 wire?
一句話概括:wire 使用代碼生成,而非反射。
我們可以分別舉例看下 dig 以及 inject 是如何使用的。
dig 的使用示例如下:
package main
import (
"fmt"
"log"
"go.uber.org/dig"
)
type User struct {
name string
}
// NewUser - Creates a new instance of User
func NewUser(name string) User {
return User{name: name}
}
// Get - A method with user as dependency
func (u *User) Get(message string) string {
return fmt.Sprintf("Hello %s - %s", u.name, message)
}
// Run - Depends on user and calls the Get method on User
func Run(user User) {
result := user.Get("It's nice to meet you!")
fmt.Println(result)
}
func main() {
// Initialize a new dig container
container := dig.New()
// Provide a name parameter to the container
container.Provide(func() string { return "jianghushinian" })
// Provide a new User instance to the container using the name injected above
if err := container.Provide(NewUser); err != nil {
log.Fatal(err)
}
// Invoke the Run function; Dig automatically injects the User instance provided above
if err := container.Invoke(Run); err != nil {
log.Fatal(err)
}
}
簡(jiǎn)單解釋下示例代碼:
dig.New() 實(shí)例化一個(gè) dig 容器。
container.Provide(func() string { return "jianghushinian" }) 將一個(gè)匿名函數(shù)提供給容器。
然后調(diào)用 container.Provide(NewUser),dig 首先將字符串值 jianghushinian 作為 name 參數(shù)提供給 NewUser 函數(shù)。之后,NewUser 函數(shù)會(huì)根據(jù)此值創(chuàng)建出來(lái)一個(gè) User 結(jié)構(gòu)體的新實(shí)例,隨后 dig 將其提供給容器。
最后,container.Invoke(Run) 會(huì)將容器中保存的 User 結(jié)構(gòu)體傳遞給 Run 函數(shù)并運(yùn)行。
我們可以類比 wire 來(lái)學(xué)習(xí) dig:可以把 Provide 看作 providers,Invoke 看作 injectors,這樣就好理解了。
以上示例代碼可以直接執(zhí)行,無(wú)需像使用 wire 一樣需要提前生成代碼:
$ go run main.go
Hello jianghushinian - It's nice to meet you!
這就是 dig 的使用。
再來(lái)看一個(gè) inject 的使用示例:
package main
import (
"fmt"
"log"
"github.com/facebookgo/inject"
)
type User struct {
Name string `inject:"name"`
}
// Get - A method with user as dependency
func (u *User) Get(message string) string {
return fmt.Sprintf("Hello %s - %s", u.Name, message)
}
// Run - Depends on user and calls the Get method on User
func Run(user *User) {
result := user.Get("It's nice to meet you!")
fmt.Println(result)
}
func main() {
// new an inject Graph
var g inject.Graph
// inject name
name := "jianghushinian"
// provide string value
err := g.Provide(&inject.Object{Value: name, Name: "name"})
if err != nil {
log.Fatal(err)
}
// create a User instance and supply it to the dependency graph
user := &User{}
err = g.Provide(&inject.Object{Value: user})
if err != nil {
log.Fatal(err)
}
// resolve all dependencies
err = g.Populate()
if err != nil {
log.Fatal(err)
}
Run(user)
}
這個(gè)示例代碼我就不詳細(xì)講解了,學(xué)會(huì)了 wire 和 dig,這段代碼很容易理解。
可以發(fā)現(xiàn)的是,無(wú)論是 dig 還是 inject,它們使用的都是運(yùn)行時(shí)反射機(jī)制,來(lái)實(shí)現(xiàn)依賴注入功能。
這會(huì)帶來(lái)最直觀的兩個(gè)問(wèn)題:
-
使用反射可能影響性能。 -
我們需要根據(jù)工具的要求編寫代碼,而這份代碼正確與否,只有在運(yùn)行期間才能確定。也就是說(shuō),代碼是“黑盒”的,通過(guò) review 代碼,很難一眼看出代碼是否存在問(wèn)題。
而 wire 采用代碼生成,它會(huì)根據(jù)我們編寫的 injector 函數(shù)簽名,生成最終代碼。所以在執(zhí)行代碼之前,我們就已經(jīng)有了 injector 函數(shù)的源碼。
這既不會(huì)影響性能,也不會(huì)讓代碼變成“黑盒”,在執(zhí)行程序之前我們就知道代碼長(zhǎng)什么樣。而這樣做還能帶來(lái)一個(gè)好處,能夠大大簡(jiǎn)化我們排錯(cuò)的過(guò)程。
Python 之禪中有一句話叫「顯式優(yōu)于隱式」,wire 做到了。
Wire 命令行工具
文章最后,我再來(lái)簡(jiǎn)單介紹下 wire 命令行工具。
之所以放在最后講解,是因?yàn)?wire 的子命令確實(shí)不太常用,如果你去網(wǎng)上搜索,幾乎沒(méi)人介紹。不過(guò)為了保證文章的完整性,我還是簡(jiǎn)單講解下,作為擴(kuò)展內(nèi)容,你好有個(gè)印象。
使用 --help 查看使用幫助信息。
$ wire --help
Usage: wire <flags> <subcommand> <subcommand args>
Subcommands:
check print any Wire errors found
commands list all command names
diff output a diff between existing wire_gen.go files and what gen would generate
flags describe all known top-level flags
gen generate the wire_gen.go file for each package
help describe subcommands and their syntax
show describe all top-level provider sets
可以發(fā)現(xiàn) wire 連最基本的 --version 命令都不存在,即不支持查看版本信息。起初這點(diǎn)我是疑惑的,不過(guò)看了官方描述,也就不足為奇了。因?yàn)?wire 已經(jīng)不再加入新功能,所以你可以理解為它就這一個(gè)版本。
官方描述說(shuō)當(dāng)前項(xiàng)目狀態(tài)不接受新功能,只接受錯(cuò)誤報(bào)告和 Bug fix??磥?lái)官方也想保持 wire 的簡(jiǎn)潔。
有人說(shuō)項(xiàng)目不維護(hù)了。但我認(rèn)為這又何嘗不是一件好事情,其實(shí)項(xiàng)目還在維護(hù),只是不增加新功能了。這在日新月異的技術(shù)行業(yè)里,是好事,極大的好事。我們不用投入太多精力學(xué)習(xí)這個(gè)工具,學(xué)一次受用很久。這也是我寫這篇想著盡量把 wire 功能介紹完全,方便大家學(xué)習(xí)。
回歸正題,首先要講解的是 gen 子命令。已經(jīng)是我們的老朋友了,可以根據(jù)我們編寫的 injector 函數(shù)簽名,自動(dòng)生成目標(biāo)代碼。
其實(shí)如果直接使用 wire 命令,后面什么也不接,wire 默認(rèn)會(huì)調(diào)用 gen 子命令:
$ wire
wire: github.com/jianghushinian/blog-go-example/wire/getting-started: wrote /Users/jianghushinian/projects/blog-go-example/wire/getting-started/wire_gen.go
check 子命令可以幫我們檢查代碼錯(cuò)誤,比如我們將 Wire 快速開(kāi)始 部分的示例中的 injector 函數(shù) InitializeEvent 故意寫錯(cuò)。
InitializeEvent 代碼如下:
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
現(xiàn)在修改成錯(cuò)誤的,漏寫了 NewMessage 方法:
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter)
return Event{}
}
使用 wire check 檢查代碼錯(cuò)誤:
$ wire check
wire: wire.go:7:1: inject InitializeEvent: no provider found for github.com/jianghushinian/blog-go-example/wire/getting-started.Message
needed by github.com/jianghushinian/blog-go-example/wire/getting-started.Greeter in provider "NewGreeter" (main.go:15:6)
needed by github.com/jianghushinian/blog-go-example/wire/getting-started.Event in provider "NewEvent" (main.go:27:6)
wire: error loading packages
但其實(shí)我們直接執(zhí)行 wire 命令生成代碼時(shí),也會(huì)得到相同的錯(cuò)誤。
commands 子命令可以打印 wire 支持的所有子命令,嗯,僅此而已。
$ wire commands
commands
flags
help
check
diff
gen
show
flags 子命令可以打印每個(gè)子命令接收的標(biāo)志:
$ wire flags gen
-header_file string
path to file to insert as a header in wire_gen.go
-output_file_prefix string
string to prepend to output file names.
-tags string
append build tags to the default wirebuild
可以發(fā)現(xiàn) gen 子命令支持 3 個(gè)標(biāo)志,至于效果你可以自行嘗試。
diff 子命令用于打印 wire 生成的 wire_gen.go 文件和之前有何不同:
$ wire diff .
github.com/jianghushinian/blog-go-example/wire/getting-started: diff from wire_gen.go:
@@ -11,2 +11,2 @@
-func InitializeEvent() Event {
- message := NewMessage()
+func InitializeEvent(string2 string) Event {
+ message := NewMessage(string2)
show 子命令用于分析和展示指定包中的依賴注入配置:
$ wire show .
Injectors:
"github.com/jianghushinian/blog-go-example/wire/getting-started".InitializeEvent
wire 命令行工具的講解就介紹到這里。
總結(jié)
終于到了總結(jié)環(huán)節(jié),又是喜聞樂(lè)見(jiàn)的萬(wàn)字長(zhǎng)文系列,上下兩篇共計(jì) 2w+ 字。
本文主旨是為了講解在 Go 中,如何優(yōu)雅的使用 wire 依賴注入工具提高開(kāi)發(fā)效率。
首先介紹了什么是依賴注入,以及在 Go 中如何使用依賴注入思想編寫代碼。
接著又對(duì)依賴注入工具 wire 進(jìn)行了簡(jiǎn)單介紹,并安裝了 wire 命令行工具。
然后通過(guò)一個(gè) wire 快速開(kāi)始的示例程序,極速入門 wire 的使用。
有了使用經(jīng)驗(yàn),我又講解了為什么要使用 wire?因?yàn)樗鼈儙臀覀冏詣?dòng)生成依賴注入代碼,提高開(kāi)發(fā)效率。
接下來(lái)我對(duì) wire 的核心概念進(jìn)行了講解。我們知道了什么是 providers 和 injectors,知道了這兩個(gè)核心概念,wire 就入門了。
我還介紹了 wire 和很多高級(jí)特性。injector 函數(shù)支持參數(shù),也支持返回清理函數(shù)和錯(cuò)誤。我們可以使用 ProviderSet 對(duì) providers 進(jìn)行分組。可以使用 wire.Struct 將一個(gè)結(jié)構(gòu)體作為 provider。也可以指定結(jié)構(gòu)體的具體某個(gè)字段作為 provider。wire.Value 可以將一個(gè)值構(gòu)造成 provider。wire.InterfaceValue 可以將一個(gè)接口構(gòu)造成 provider。通過(guò) wire.Bind(new(Fooer), new(MyFoo))) 可以將 MyFoo 結(jié)構(gòu)體綁定到 Fooer 接口。wire 還為我們提供了備用注入器語(yǔ)法,可以使用 panic 取代在 injector 函數(shù)中編寫返回值。
wire 的用法都講解完成以后,我又以一個(gè) user Web 應(yīng)用作為案例,為你講解了在生產(chǎn)實(shí)踐中 wire 的使用。
既然我們學(xué)會(huì)了 wire,那就應(yīng)該知道我們?yōu)槭裁匆x擇使用 wire。我對(duì)比了 Uber 開(kāi)源的 dig,以及 Facebook 開(kāi)源的 inject,為你講解了選擇 wire 的原因??梢杂靡痪湓捀爬ǎ簑ire 使用代碼生成,而非反射。
最后,我又簡(jiǎn)單介紹了 wire 命令行工具的使用。
記住,依賴注入并不神秘,wire 的作用也顯而易見(jiàn),就是為了解放雙手。如果你更喜歡手動(dòng)編寫代碼,那么也完全沒(méi)有任何問(wèn)題。不要過(guò)于神化依賴注入工具,起碼在 Go 語(yǔ)言中是這樣。
本文示例源碼我都放在了 GitHub 中,歡迎點(diǎn)擊查看。
由于篇幅所限,有些示例文章中并沒(méi)有給出執(zhí)行結(jié)果,你一定要把我的示例代碼 clone 下來(lái),依次執(zhí)行一遍,這樣才能更加深刻的理解。
至此本文完結(jié),如果你想要更深入的了解 wire,那就去看它的源碼吧,祝你好運(yùn) :)。
希望此文能對(duì)你有所啟發(fā)。
延伸閱讀
-
Compile-time Dependency Injection With Go Cloud's Wire: https://go.dev/blog/wire -
Wire README: https://github.com/google/wire/blob/main/README.md -
Wire Documentation: https://pkg.go.dev/github.com/google/wire/internal/wire -
Wire 源碼: https://github.com/google/wire -
onex usercenter: https://github.com/superproj/onex/tree/master/internal/usercenter -
Go Dependency Injection with Wire: https://blog.drewolson.org/go-dependency-injection-with-wire/ -
Golang Dependency Injection Using Wire: https://clavinjune.dev/en/blogs/golang-dependency-injection-using-wire/ -
Dependency Injection in Go using Wire: https://www.mohitkhare.com/blog/go-dependency-injection/ -
Wire 依賴注入: https://go-kratos.dev/docs/guide/wire/ -
Dependency Injection with Dig: https://www.jetbrains.com/guide/go/tutorials/dependency_injection_part_one/di_with_dig/ -
inject Documentation: https://pkg.go.dev/github.com/facebookgo/inject -
Build Constraints: https://pkg.go.dev/go/build#hdr-Build_Constraints -
控制反轉(zhuǎn): https://zh.wikipedia.org/wiki/控制反轉(zhuǎn) -
依賴注入: https://zh.wikipedia.org/wiki/依賴注入 -
SOLID (面向?qū)ο笤O(shè)計(jì)): https://zh.wikipedia.org/wiki/SOLID_(面向?qū)ο笤O(shè)計(jì)) -
設(shè)計(jì)模式之美 —— 19 | 理論五:控制反轉(zhuǎn)、依賴反轉(zhuǎn)、依賴注入,這三者有何區(qū)別和聯(lián)系?: https://time.geekbang.org/column/article/177444 -
本文 GitHub 示例代碼:https://github.com/jianghushinian/blog-go-example/tree/main/wire
推薦閱讀:
6 個(gè)必須嘗試的將代碼轉(zhuǎn)換為引人注目的圖表的工具
Go早期是如何在Google內(nèi)部發(fā)展起來(lái)的
2024 Gopher Meetup 武漢站活動(dòng)
Go區(qū)不大,創(chuàng)造神話,科目三殺進(jìn)來(lái)了
想要了解Go更多內(nèi)容,歡迎掃描下方??關(guān)注公眾號(hào),掃描 [實(shí)戰(zhàn)群]二維碼 ,即可進(jìn)群和我們交流~
- 掃碼即可加入實(shí)戰(zhàn)群 -
