博客 / 詳情

返回

萬字長文:在 Go 中如何優雅的使用 wire 依賴注入工具提高開發效率?下篇

在《萬字長文:在 Go 中如何優雅的使用 wire 依賴注入工具提高開發效率?上篇》,我講解了 Go 語言中依賴注入工具 wire 的基本使用及高級用法。本篇就來介紹下 wire 的生產實踐。

Wire 生產實踐

這裏以一個 user 服務作為示例,演示下一個生產項目中是如何使用 wire 依賴注入工具的。

user 項目目錄結構如下:

$ 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 項目源碼在此,你可以點擊查看,建議下載下來執行啓動下程序,加深理解。

這是一個典型的 Web 應用,用來對用户進行 CRUD。不過為了保持代碼簡潔清晰,方便理解,user 項目僅實現了創建用户的功能。

我先簡單介紹下各個目錄的功能。

assets 努目錄用於存放項目資源。schema.sql 中是建表語句,curl.sh 保存了一個 curl 請求命令,用於測試創建用户功能。

cmd 中當然是程序入口文件。

internal 下保存了項目業務邏輯。

pkg 目錄存放可導出的公共庫。api 用於存放請求對象;db 用於構造數據庫對象。

項目設計了 4 層架構,controller 即對應 MVC 經典模式中的 Controller,biz 是業務層,store 層用於跟數據庫交互,還有一個 model 層定義模型,用於映射數據庫表。

router.go 用於註冊路由。

user.go 用於定義創建和啓動 user 服務的應用對象。

wire.gowire_gen.go 兩個文件就無需我過多講解了。

NOTE: 本項目目錄結構遵循最佳實踐,可以參考我的另一篇文章《如何設計一個優秀的 Go Web 項目目錄結構》。

簡單介紹完了目錄結構,再來梳理下我們所設計的 4 層架構依賴關係:首先 controller 層依賴 biz 層,然後 biz 層又依賴 store 層,接着 store 層又依賴了數據庫(即依賴 pkg/db/),而 controllerbizstore 這三者又都依賴 model 層。

現在看了我的講解,你可能有些發懵,沒關係,下面我將主要代碼邏輯都貼出來,加深你的理解。

assets/schema.sql 中的建表語句如下:

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 項目僅有一張表。

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()
}

入口函數 main 中先創建了配置對象 cfg,接着實例化 app 對象,最後調用 app.Run() 啓動 user 服務。

這也是一個典型的 Web 應用啓動步驟。

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 代表一個 Web 應用
type App struct {
    *config.Config

    g  *gin.Engine
    uc *controller.UserController
}

// NewApp Web 應用構造函數
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 啓動 Web 應用
func (a *App) Run() {
    // 註冊路由
    InitRouter(a)

    if err := a.g.Run(":8000"); err != nil {
        panic(err)
    }
}

App 代表一個 Web 應用,它嵌入了配置、gin 框架的 *Engine 對象,以及 controller

NewAppApp 的構造函數,通過 Config 來創建一個 *App 對象。

根據其內部代碼邏輯,也能看出項目的 4 層架構依賴關係:創建 App 對象依賴 ConfigConfig 是通過參數傳遞進來的;*Engine 對象可以通過 gin.Default() 得到;而 userController 則通過 controller.New 創建,controller 依賴 bizbiz 依賴 storestore 依賴 *gorm.DB

可以發現,依賴關係非常清晰,並且我們使用了依賴注入思想編寫代碼,那麼此時,正是 wire 的用武之地。

不過,我們先不急着講解如何在這裏使用 wire。我先將項目剩餘主要代碼貼出來,便於你理解這個 Web 應用。

我們可以通過 pkg/db/db.go 中的 NewMySQL 創建出 *gorm.DB 對象:

// NewMySQL 根據選項構造 *gorm.DB
func NewMySQL(opts *MySQLOptions) (*gorm.DB, func(), error) {
    // 可以用來釋放資源,這裏僅作為示例使用,沒有釋放任何資源,因為 gorm 內部已經幫我們做了
    cleanFunc := func() {}

    db, err := gorm.Open(mysql.Open(opts.DSN()), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Silent),
    })
    return db, cleanFunc, err
}

有了 *gorm.DB 就可以創建 store 對象了,internal/store/user.go 主要代碼如下:

package store

...

// ProviderSet 一個 Wire provider sets,用來初始化 store 實例對象,並將 UserStore 接口綁定到 *userStore 類型實現上
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 接口實現
type userStore struct {
    db *gorm.DB
}

// 確保 userStore 實現了 UserStore 接口
var _ UserStore = (*userStore)(nil)

// New userStore 構造函數
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 就可以創建 biz 對象了,internal/biz/user.go 主要代碼如下:

package biz

...

// ProviderSet 一個 Wire provider sets,用來初始化 biz 實例對象,並將 UserBiz 接口綁定到 *userBiz 類型實現上
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserBiz), new(*userBiz)))

// UserBiz 定義 user 業務邏輯操作方法
type UserBiz interface {
    Create(ctx context.Context, r *api.CreateUserRequest) error
}

// UserBiz 接口的實現
type userBiz struct {
    s store.UserStore
}

// 確保 userBiz 實現了 UserBiz 接口
var _ UserBiz = (*userBiz)(nil)

// New userBiz 構造函數
func New(s store.UserStore) *userBiz {
    return &userBiz{s: s}
}

// Create 創建用户
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 就可以創建 controller 對象了,internal/controller/user.go 主要代碼如下:

package controller

...

// UserController 用來處理用户請求
type UserController struct {
    b biz.UserBiz
}

// New controller 構造函數
func New(b biz.UserBiz) *UserController {
    return &UserController{b: b}
}

// Create 創建用户
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{})
}

這些對象都有了,就可以調用 NewApp 構造出 App 了。

App 在啓動前,還會調用 InitRouter 進行路由註冊:

// InitRouter 初始化路由
func InitRouter(a *App) {
    // 創建 users 路由分組
    u := a.g.Group("/users")
    {
        u.POST("", a.uc.Create)
    }
}

現在 user 項目邏輯已經清晰了,是時候啓動應用程序了:

$ cd user   
$ go run cmd/main.go

程序啓動後,會監聽 8000 端口,可以使用 assets/curl.sh 中的 curl 命令進行訪問:

$ curl --location --request POST 'http://127.0.0.1:8000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "jianghushinian007@outlook.com",
    "nickname": "江湖十年",
    "username": "jianghushinian",
    "password": "pass"
}'

不出意外,你將在數據庫中看到新創建的用户。

執行以下 SQL:

USE user;
SELECT * FROM user;

將輸出新創建出來的用户。

+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+
| id | email                         | nickname | username       | password | createdAt           | updatedAt           |
+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+
|  1 | jianghushinian007@outlook.com | 江湖十年  | jianghushinian | pass     | 2024-06-11 00:01:35 | 2024-06-11 00:01:35 |
+----+-------------------------------+----------+----------------+----------+---------------------+---------------------+

現在,是時候討論如何在 user 項目中使用 wire 來提高開發效率了。

回顧下 NewApp 的定義:

// NewApp Web 應用構造函數
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
}

其實這裏面一層層的依賴注入,都是套路代碼,基本上一個 Web 應用都可以按照這個套路來寫。

這就涉及到套路代碼寫多了其實是比較煩的,這還只是一個微型項目,如果是中大項目,可以預見這個 NewApp 代碼量會很多,所以是時候讓 wire 出場了:

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
}

有了前文的講解,其實這裏無需我多言,你都能夠看懂,因為並沒有新的知識。

不過我們還是簡單分析下這裏都用到了 wire 的哪些特性。

首先 wireApp 返回值是典型的三件套:(*App, func(), error),對象、清理函數和 error

這裏使用了兩個 wire.ProviderSet 進行分組,定義如下:

var ProviderSet = wire.NewSet(New, wire.Bind(new(UserStore), new(*userStore)))
var ProviderSet = wire.NewSet(New, wire.Bind(new(UserBiz), new(*userBiz)))

並且在構造 wire.ProviderSet 時,還使用了 wire.Bind(new(UserStore), new(*userStore)) 將一個結構體綁定到接口。

最後,我們使用了 struct 作為 providerwire.Struct(new(App), "*") ,通配符 * 用來表示所有字段。

在真實項目中,wire 就這麼使用。

如果你覺得 user 項目太小,使用 wire 的價值還不夠大。你可以看看 onex 項目,比如 usercenter 中的代碼,這個開源項目完全是生產級別。

為什麼選擇 Wire

通常來説,這部分內容是應該放在文章開頭的。我將其放在這裏,目的是為了讓你熟悉 wire 後,再回過頭來對比,wire 有哪些優勢,加深你對為什麼選擇 wire 的理解。

其實 Go 生態中依賴注入工具不止有 Google 的 wire 一家獨大,還有 Uber 開源的 dig,以及 Facebook 開源的 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)
    }
}

簡單解釋下示例代碼:

dig.New() 實例化一個 dig 容器。

container.Provide(func() string { return "jianghushinian" }) 將一個匿名函數提供給容器。

然後調用 container.Provide(NewUser),dig 首先將字符串值 jianghushinian 作為 name 參數提供給 NewUser 函數。之後,NewUser 函數會根據此值創建出來一個 User 結構體的新實例,隨後 dig 將其提供給容器。

最後,container.Invoke(Run) 會將容器中保存的 User 結構體傳遞給 Run 函數並運行。

我們可以類比 wire 來學習 dig:可以把 Provide 看作 providersInvoke 看作 injectors,這樣就好理解了。

以上示例代碼可以直接執行,無需像使用 wire 一樣需要提前生成代碼:

$ go run main.go
Hello jianghushinian - It's nice to meet you!

這就是 dig 的使用。

再來看一個 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)
}

這個示例代碼我就不詳細講解了,學會了 wire 和 dig,這段代碼很容易理解。

可以發現的是,無論是 dig 還是 inject,它們使用的都是運行時反射機制,來實現依賴注入功能。

這會帶來最直觀的兩個問題:

  1. 使用反射可能影響性能。
  2. 我們需要根據工具的要求編寫代碼,而這份代碼正確與否,只有在運行期間才能確定。也就是説,代碼是“黑盒”的,通過 review 代碼,很難一眼看出代碼是否存在問題。

而 wire 採用代碼生成,它會根據我們編寫的 injector 函數簽名,生成最終代碼。所以在執行代碼之前,我們就已經有了 injector 函數的源碼。

這既不會影響性能,也不會讓代碼變成“黑盒”,在執行程序之前我們就知道代碼長什麼樣。而這樣做還能帶來一個好處,能夠大大簡化我們排錯的過程。

Python 之禪中有一句話叫「顯式優於隱式」,wire 做到了。

Wire 命令行工具

文章最後,我再來簡單介紹下 wire 命令行工具。

之所以放在最後講解,是因為 wire 的子命令確實不太常用,如果你去網上搜索,幾乎沒人介紹。不過為了保證文章的完整性,我還是簡單講解下,作為擴展內容,你好有個印象。

使用 --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

可以發現 wire 連最基本的 --version 命令都不存在,即不支持查看版本信息。起初這點我是疑惑的,不過看了官方描述,也就不足為奇了。因為 wire 已經不再加入新功能,所以你可以理解為它就這一個版本。

官方描述説當前項目狀態不接受新功能,只接受錯誤報告和 Bug fix。看來官方也想保持 wire 的簡潔。

有人説項目不維護了。但我認為這又何嘗不是一件好事情,其實項目還在維護,只是不增加新功能了。這在日新月異的技術行業裏,是好事,極大的好事。我們不用投入太多精力學習這個工具,學一次受用很久。這也是我寫這篇想着儘量把 wire 功能介紹完全,方便大家學習。

迴歸正題,首先要講解的是 gen 子命令。已經是我們的老朋友了,可以根據我們編寫的 injector 函數簽名,自動生成目標代碼。

其實如果直接使用 wire 命令,後面什麼也不接,wire 默認會調用 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 子命令可以幫我們檢查代碼錯誤,比如我們將 Wire 快速開始 部分的示例中的 injector 函數 InitializeEvent 故意寫錯。

InitializeEvent 代碼如下:

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

現在修改成錯誤的,漏寫了 NewMessage 方法:

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter)
    return Event{}
}

使用 wire check 檢查代碼錯誤:

$ 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

但其實我們直接執行 wire 命令生成代碼時,也會得到相同的錯誤。

commands 子命令可以打印 wire 支持的所有子命令,嗯,僅此而已。

$ wire commands
commands
flags
help
check
diff
gen
show

flags 子命令可以打印每個子命令接收的標誌:

$ 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

可以發現 gen 子命令支持 3 個標誌,至於效果你可以自行嘗試。

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 命令行工具的講解就介紹到這裏。

總結

終於到了總結環節,又是喜聞樂見的萬字長文系列,上下兩篇共計 2w+ 字。

本文主旨是為了講解在 Go 中,如何優雅的使用 wire 依賴注入工具提高開發效率。

首先介紹了什麼是依賴注入,以及在 Go 中如何使用依賴注入思想編寫代碼。

接着又對依賴注入工具 wire 進行了簡單介紹,並安裝了 wire 命令行工具。

然後通過一個 wire 快速開始的示例程序,極速入門 wire 的使用。

有了使用經驗,我又講解了為什麼要使用 wire?因為它們幫我們自動生成依賴注入代碼,提高開發效率。

接下來我對 wire 的核心概念進行了講解。我們知道了什麼是 providersinjectors,知道了這兩個核心概念,wire 就入門了。

我還介紹了 wire 和很多高級特性。injector 函數支持參數,也支持返回清理函數和錯誤。我們可以使用 ProviderSetproviders 進行分組。可以使用 wire.Struct 將一個結構體作為 provider。也可以指定結構體的具體某個字段作為 providerwire.Value 可以將一個值構造成 providerwire.InterfaceValue 可以將一個接口構造成 provider。通過 wire.Bind(new(Fooer), new(MyFoo))) 可以將 MyFoo 結構體綁定到 Fooer 接口。wire 還為我們提供了備用注入器語法,可以使用 panic 取代在 injector 函數中編寫返回值。

wire 的用法都講解完成以後,我又以一個 user Web 應用作為案例,為你講解了在生產實踐中 wire 的使用。

既然我們學會了 wire,那就應該知道我們為什麼要選擇使用 wire。我對比了 Uber 開源的 dig,以及 Facebook 開源的 inject,為你講解了選擇 wire 的原因。可以用一句話概括:wire 使用代碼生成,而非反射。

最後,我又簡單介紹了 wire 命令行工具的使用。

記住,依賴注入並不神秘,wire 的作用也顯而易見,就是為了解放雙手。如果你更喜歡手動編寫代碼,那麼也完全沒有任何問題。不要過於神化依賴注入工具,起碼在 Go 語言中是這樣。

本文示例源碼我都放在了 GitHub 中,歡迎點擊查看。

由於篇幅所限,有些示例文章中並沒有給出執行結果,你一定要把我的示例代碼 clone 下來,依次執行一遍,這樣才能更加深刻的理解。

至此本文完結,如果你想要更深入的瞭解 wire,那就去看它的源碼吧,祝你好運 :)。

希望此文能對你有所啓發。

延伸閲讀

  • 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-u...
  • 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_injec...
  • inject Documentation: https://pkg.go.dev/github.com/facebookgo/inject
  • Build Constraints: https://pkg.go.dev/go/build#hdr-Build_Constraints
  • 控制反轉: https://zh.wikipedia.org/wiki/控制反轉
  • 依賴注入: https://zh.wikipedia.org/wiki/依賴注入
  • SOLID (面向對象設計): https://zh.wikipedia.org/wiki/SOLID_(面向對象設計)
  • 設計模式之美 —— 19 | 理論五:控制反轉、依賴反轉、依賴注入,這三者有何區別和聯繫?: https://time.geekbang.org/column/article/177444
  • 本文 GitHub 示例代碼:https://github.com/jianghushinian/blog-go-example/tree/main/wire

聯繫我

  • 公眾號:Go編程世界
  • 微信:jianghushinian
  • 郵箱:jianghushinian007@outlook.com
  • 博客:https://jianghushinian.cn
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.