Go語言官方依賴注入工具Wire的使用教程_第1頁
Go語言官方依賴注入工具Wire的使用教程_第2頁
Go語言官方依賴注入工具Wire的使用教程_第3頁
Go語言官方依賴注入工具Wire的使用教程_第4頁
Go語言官方依賴注入工具Wire的使用教程_第5頁
已閱讀5頁,還剩11頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

第Go語言官方依賴注入工具Wire的使用教程目錄1.前言2.依賴注入(DI)是什么3.WireCome3.1簡介3.2快速使用3.3基礎(chǔ)概念4.Wire使用實踐4.1基礎(chǔ)使用4.2高級特性4.3高階使用5.注意事項5.1相同類型問題5.2單例問題6.結(jié)語

1.前言

接觸Golang有一段時間了,發(fā)現(xiàn)Golang同樣需要類似Java中Spring一樣的依賴注入框架。如果項目規(guī)模比較小,是否有依賴注入框架問題不大,但當項目變大之后,有一個合適的依賴注入框架是十分必要的。通過調(diào)研,了解到Golang中常用的依賴注入工具主要有Inject、Dig等。但是今天主要介紹的是Go團隊開發(fā)的Wire,一個編譯期實現(xiàn)依賴注入的工具。

2.依賴注入(DI)是什么

說起依賴注入就要引出另一個名詞控制反轉(zhuǎn)(IoC)。IoC是一種設(shè)計思想,其核心作用是降低代碼的耦合度。依賴注入是一種實現(xiàn)控制反轉(zhuǎn)且用于解決依賴性問題的設(shè)計模式。

舉個例子,假設(shè)我們代碼分層關(guān)系是dal層連接數(shù)據(jù)庫,負責(zé)數(shù)據(jù)庫的讀寫操作。那么我們的dal層的上一層service負責(zé)調(diào)用dal層處理數(shù)據(jù),在我們目前的代碼中,它可能是這樣的:

//

dal/user.go

func

(u

*UserDal)

Create(ctx

context.Context,

data

*UserCreateParams)

error

{

db

:=

mysql.GetDB().Model(entity.User{})

user

:=

entity.User{

Username:

data.Username,

Password:

data.Password,

return

db.Create(user).Error

//

service/user.go

func

(u

*UserService)

Register(ctx

context.Context,

data

*schema.RegisterReq)

(*schema.RegisterRes,

error)

{

params

:=

dal.UserCreateParams{

Username:

data.Username,

Password:

data.Password,

err

:=

dal.GetUserDal().Create(ctx,

params)

if

err

!=

nil

{

return

nil,

err

registerRes

:=

schema.RegisterRes{

Msg:

"register

success",

return

registerRes,

nil

在這段代碼里,層級依賴關(guān)系為service-dal-db,上游層級通過Getxxx實例化依賴。但在實際生產(chǎn)中,我們的依賴鏈比較少是垂直依賴關(guān)系,更多的是橫向依賴。即我們一個方法中,可能要多次調(diào)用Getxxx的方法,這樣使得我們代碼極不簡潔。

不僅如此,我們的依賴都是寫死的,即依賴者的代碼中寫死了被依賴者的生成關(guān)系。當被依賴者的生成方式改變,我們也需要改變依賴者的函數(shù),這極大的增加了修改代碼量以及出錯風(fēng)險。

接下來我們用依賴注入的方式對代碼進行改造:

//

dal/user.go

type

UserDal

struct{

DB

*gorm.DB

func

NewUserDal(db

*gorm.DB)

*UserDal{

return

UserDal{

DB:

db

}

func

(u

*UserDal)

Create(ctx

context.Context,

data

*UserCreateParams)

error

{

db

:=

u.DB.Model(entity.User{})

user

:=

entity.User{

Username:

data.Username,

Password:

data.Password,

return

db.Create(user).Error

//

service/user.go

type

UserService

struct{

UserDal

*dal.UserDal

func

NewUserService(userDal

dal.UserDal)

*UserService{

return

UserService{

UserDal:

userDal

}

func

(u

*UserService)

Register(ctx

context.Context,

data

*schema.RegisterReq)

(*schema.RegisterRes,

error)

{

params

:=

dal.UserCreateParams{

Username:

data.Username,

Password:

data.Password,

err

:=

u.UserDal.Create(ctx,

params)

if

err

!=

nil

{

return

nil,

err

registerRes

:=

schema.RegisterRes{

Msg:

"register

success",

return

registerRes,

nil

//

main.go

db

:=

mysql.GetDB()

userDal

:=

dal.NewUserDal(db)

userService

:=

dal.NewUserService(userDal)

如上編碼情況中,我們通過將db實例對象注入到dal中,再將dal實例對象注入到service中,實現(xiàn)了層級間的依賴注入。解耦了部分依賴關(guān)系。

在系統(tǒng)簡單、代碼量少的情況下上面的實現(xiàn)方式確實沒什么問題。但是項目龐大到一定程度,結(jié)構(gòu)之間的關(guān)系變得非常復(fù)雜時,手動創(chuàng)建每個依賴,然后層層組裝起來的方式就會變得異常繁瑣,并且容易出錯。這個時候勇士wire出現(xiàn)了!

3.WireCome

3.1簡介

Wire是一個輕巧的Golang依賴注入工具。它由GoCloud團隊開發(fā),通過自動生成代碼的方式在編譯期完成依賴注入。它不需要反射機制,后面會看到,Wire生成的代碼與手寫無異。

3.2快速使用

wire的安裝:

go

get

/google/wire/cmd/wire

上面的命令會在$GOPATH/bin中生成一個可執(zhí)行程序wire,這就是代碼生成器??梢园?GOPATH/bin加入系統(tǒng)環(huán)境變量$PATH中,所以可直接在命令行中執(zhí)行wire命令。

下面我們在一個例子中看看如何使用wire。

現(xiàn)在我們有這樣的三個類型:

type

Message

string

type

Channel

struct

{

Message

Message

type

BroadCast

struct

{

Channel

Channel

三者的init方法:

func

NewMessage()

Message

{

return

Message("Hello

Wire!")

func

NewChannel(m

Message)

Channel

{

return

Channel{Message:

m}

func

NewBroadCast(c

Channel)

BroadCast

{

return

BroadCast{Channel:

c}

假設(shè)Channel有一個GetMsg方法,BroadCast有一個Start方法:

func

(c

Channel)

GetMsg()

Message

{

return

c.Message

func

(b

BroadCast)

Start()

{

msg

:=

b.Channel.GetMsg()

fmt.Println(msg)

如果手動寫代碼的話,我們的寫法應(yīng)該是:

func

main()

{

message

:=

NewMessage()

channel

:=

NewChannel(message)

broadCast

:=

NewBroadCast(channel)

broadCast.Start()

如果使用wire,我們需要做的就變成如下的工作了:

1.提取一個init方法InitializeBroadCast:

func

main()

{

b

:=

demo.InitializeBroadCast()

b.Start()

2.編寫一個wire.go文件,用于wire工具來解析依賴,生成代碼:

//+build

wireinject

package

demo

func

InitializeBroadCast()

BroadCast

{

wire.Build(NewBroadCast,

NewChannel,

NewMessage)

return

BroadCast{}

注意:需要在文件頭部增加構(gòu)建約束://+buildwireinject

3.使用wire工具,生成代碼,在wire.go所在目錄下執(zhí)行命令:wiregenwire.go。會生成如下代碼,即在編譯代碼時真正使用的Init函數(shù):

//

Code

generated

by

Wire.

DO

NOT

EDIT.

//go:generate

wire

//+build

!wireinject

func

InitializeBroadCast()

BroadCast

{

message

:=

NewMessage()

channel

:=

NewChannel(message)

broadCast

:=

NewBroadCast(channel)

return

broadCast

我們告訴wire,我們所用到的各種組件的init方法(NewBroadCast,NewChannel,NewMessage),那么wire工具會根據(jù)這些方法的函數(shù)簽名(參數(shù)類型/返回值類型/函數(shù)名)自動推導(dǎo)依賴關(guān)系。

wire.go和wire_gen.go文件頭部位置都有一個+build,不過一個后面是wireinject,另一個是!wireinject。+build其實是Go語言的一個特性。類似C/C++的條件編譯,在執(zhí)行g(shù)obuild時可傳入一些選項,根據(jù)這個選項決定某些文件是否編譯。wire工具只會處理有wireinject的文件,所以我們的wire.go文件要加上這個。生成的wire_gen.go是給我們來使用的,wire不需要處理,故有!wireinject。

3.3基礎(chǔ)概念

Wire有兩個基礎(chǔ)概念,Provider(構(gòu)造器)和Injector(注入器)

Provider實際上就是生成組件的普通方法,這些方法接收所需依賴作為參數(shù),創(chuàng)建組件并將其返回。我們上面例子的NewBroadCast就是Provider。Injector可以理解為Providers的連接器,它用來按依賴順序調(diào)用Providers并最終返回構(gòu)建目標。我們上面例子的InitializeBroadCast就是Injector。

4.Wire使用實踐

下面簡單介紹一下wire在飛書問卷表單服務(wù)中的應(yīng)用。

飛書問卷表單服務(wù)的project模塊中將handler層、service層和dal層的初始化通過參數(shù)注入的方式實現(xiàn)依賴反轉(zhuǎn)。通過BuildInjector注入器來初始化所有的外部依賴。

4.1基礎(chǔ)使用

dal偽代碼如下:

func

NewProjectDal(db

*gorm.DB)

*ProjectDal{

return

ProjectDal{

DB:db

}

type

ProjectDal

struct

{

DB

*gorm.DB

func

(dal

*ProjectDal)

Create(ctx

context.Context,

item

*entity.Project)

error

{

result

:=

dal.DB.Create(item)

return

errors.WithStack(result.Error)

//

QuestionDal、QuestionModelDal...

service偽代碼如下:

func

NewProjectService(projectDal

*dal.ProjectDal,

questionDal

*dal.QuestionDal,

questionModelDal

*dal.QuestionModelDal)

*ProjectService

{

return

projectService{

ProjectDal:

projectDal,

QuestionDal:

questionDal,

QuestionModelDal:

questionModelDal,

type

ProjectService

struct

{

ProjectDal

*dal.ProjectDal

QuestionDal

*dal.QuestionDal

QuestionModelDal

*dal.QuestionModelDal

func

(s

*ProjectService)

Create(ctx

context.Context,

projectBo

*bo.ProjectCreateBo)

(int64,

error)

{}

handler偽代碼如下:

func

NewProjectHandler(srv

*service.ProjectService)

*ProjectHandler{

return

ProjectHandler{

ProjectService:

srv

}

type

ProjectHandler

struct

{

ProjectService

*service.ProjectService

func

(s

*ProjectHandler)

CreateProject(ctx

context.Context,

req

*project.CreateProjectRequest)

(resp

*

project.CreateProjectResponse,

err

error)

{}

injector.go偽代碼如下:

func

NewInjector()(handler

*handler.ProjectHandler)

*Injector{

return

Injector{

ProjectHandler:

handler

}

type

Injector

struct

{

ProjectHandler

*handler.ProjectHandler

//

components,others...

在wire.go中如下定義:

//

+build

wireinject

package

app

func

BuildInjector()

(*Injector,

error)

{

wire.Build(

NewInjector,

//

handler

handler.NewProjectHandler,

//

services

service.NewProjectService,

//

更多service...

//dal

dal.NewProjectDal,

dal.NewQuestionDal,

dal.NewQuestionModelDal,

//

更多dal...

//

db

common.InitGormDB,

//

other

components...

return

new(Injector),

nil

執(zhí)行wiregen./internal/app/wire.go生成wire_gen.go

//

Code

generated

by

Wire.

DO

NOT

EDIT.

//go:generate

wire

//+build

!wireinject

func

BuildInjector()

(*Injector,

error)

{

db,

err

:=

common.InitGormDB()

if

err

!=

nil

{

return

nil,

err

projectDal

:=

dal.NewProjectDal(db)

questionDal

:=

dal.NewQuestionDal(db)

questionModelDal

:=

dal.NewQuestionModelDal(db)

projectService

:=

service.NewProjectService(projectDal,

questionDal,

questionModelDal)

projectHandler

:=

handler.NewProjectHandler(projectService)

injector

:=

NewInjector(projectHandler)

return

injector,

nil

在main.go中加入初始化injector的方法app.BuildInjector

injector,

err

:=

BuildInjector()

if

err

!=

nil

{

return

nil,

err

//project服務(wù)啟動

svr

:=

projectservice.NewServer(injector.ProjectHandler,

logOpt)

svr.Run()

注意,如果你運行時,出現(xiàn)了BuildInjector重定義,那么檢查一下你的//+buildwireinject與packageapp這兩行之間是否有空行,這個空行必須要有!見/google/wire/issues/117

4.2高級特性

4.2.1NewSet

NewSet一般應(yīng)用在初始化對象比較多的情況下,減少Injector里面的信息。當我們項目龐大到一定程度時,可以想象會出現(xiàn)非常多的Providers。NewSet幫我們把這些Providers按照業(yè)務(wù)關(guān)系進行分組,組成ProviderSet(構(gòu)造器集合),后續(xù)只需要使用這個集合即可。

//

project.go

var

ProjectSet

=

wire.NewSet(NewProjectHandler,

NewProjectService,

NewProjectDal)

//

wire.go

func

BuildInjector()

(*Injector,

error)

{

wire.Build(InitGormDB,

ProjectSet,

NewInjector)

return

new(Injector),

nil

4.2.2Struct

上述例子的Provider都是函數(shù),除函數(shù)外,結(jié)構(gòu)體也可以充當Provider的角色。Wire給我們提供了結(jié)構(gòu)構(gòu)造器(StructProvider)。結(jié)構(gòu)構(gòu)造器創(chuàng)建某個類型的結(jié)構(gòu),然后用參數(shù)或調(diào)用其它構(gòu)造器填充它的字段。

//

project_service.go

//

函數(shù)provider

func

NewProjectService(projectDal

*dal.ProjectDal,

questionDal

*dal.QuestionDal,

questionModelDal

*dal.QuestionModelDal)

*ProjectService

{

return

projectService{

ProjectDal:

projectDal,

QuestionDal:

questionDal,

QuestionModelDal:

questionModelDal,

//

等價于

wire.Struct(new(ProjectService),

"*")

//

"*"代表全部字段注入

//

也等價于

wire.Struct(new(ProjectService),

"ProjectDal",

"QuestionDal",

"QuestionModelDal")

//

如果個別屬性不想被注入,那么可以修改

struct

定義:

type

App

struct

{

Foo

*Foo

Bar

*Bar

NoInject

int

`wire:"-"`

4.2.3Bind

Bind函數(shù)的作用是為了讓接口類型的依賴參與Wire的構(gòu)建。Wire的構(gòu)建依靠參數(shù)類型,接口類型是不支持的。Bind函數(shù)通過將接口類型和實現(xiàn)類型綁定,來達到依賴注入的目的。

//

project_dal.go

type

IProjectDal

interface

{

Create(ctx

context.Context,

item

*entity.Project)

(err

error)

//

...

type

ProjectDal

struct

{

DB

*gorm.DB

var

bind

=

wire.Bind(new(IProjectDal),

new(*ProjectDal))

4.2.4CleanUp

構(gòu)造器可以提供一個清理函數(shù)(cleanup),如果后續(xù)的構(gòu)造器返回失敗,前面構(gòu)造器返回的清理函數(shù)都會調(diào)用。初始化Injector之后可以獲取到這個清理函數(shù),清理函數(shù)典型的應(yīng)用場景是文件資源和網(wǎng)絡(luò)連接資源。清理函數(shù)通常作為第二返回值,參數(shù)類型為func()。當Provider中的任何一個擁有清理函數(shù),Injector的函數(shù)返回值中也必須包含該函數(shù)。并且Wire對Provider的返回值個數(shù)及順序有以下限制:

第一個返回值是需要生成的對象如果有2個返回值,第二個返回值必須是func()或error如果有3個返回值,第二個返回值必須是func(),而第三個返回值必須是erro

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論