<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Golang 依賴(lài)注入設(shè)計(jì)哲學(xué)|12.6K 🌟 的依賴(lài)注入庫(kù) wire

      一、前言

      線(xiàn)上項(xiàng)目往往依賴(lài)非常多的具備特定能力的資源,如:DB、MQ、各種中間件,以及隨著項(xiàng)目業(yè)務(wù)的復(fù)雜化,單一項(xiàng)目?jī)?nèi),業(yè)務(wù)模塊也逐漸增多,如何高效、整潔管理各種資源十分重要。

      本文從“術(shù)”層面,講述“依賴(lài)注入”的實(shí)現(xiàn),帶你體會(huì)其對(duì)于整潔架構(gòu) & DDD 等設(shè)計(jì)思想的落地,起到的支撐作用。

      涉及內(nèi)容:

      • 最熱門(mén)的 golang 依賴(lài)注入庫(kù),GitHub ?? 12.5k:https://github.com/google/wire

      • GiuHub ?? 22.5k 的 golang 微服務(wù)框架 kratos 默認(rèn)使用 wire 作為依賴(lài)注入方式:https://github.com/go-kratos/kratos

      • Spring Boot 與 Golang 的依賴(lài)注入對(duì)比

      • 依賴(lài)注入的設(shè)計(jì)哲學(xué)

      ?? B站賬號(hào):白澤talk,絕大部分博客內(nèi)容都將會(huì)通過(guò)視頻講解,不過(guò)文章一般是先于視頻發(fā)布

      image-20240703002016429

      白澤的開(kāi)源 Golang 學(xué)習(xí)倉(cāng)庫(kù):https://github.com/BaiZe1998/go-learning,用于文章歸檔 & 聚合博客代碼案例

      公眾號(hào)【白澤talk】,本期內(nèi)容的 pdf 版本,可以關(guān)注公眾號(hào),回復(fù)【依賴(lài)注入】獲得,往期資源的獲取,都是類(lèi)似的方式。

      二、What

      ?? 本文所涉及編寫(xiě)的代碼,已收錄于 https://github.com/BaiZe1998/go-learning/di 目錄

      一句話(huà)概括:實(shí)例 A 的創(chuàng)建,依賴(lài)于實(shí)例 B 的創(chuàng)建,且在實(shí)例 A 的生命周期內(nèi),持有對(duì)實(shí)例 B 的訪(fǎng)問(wèn)權(quán)限。

      2.1 案例分析

      依賴(lài)注入(Dependency Injection, DI),以 Golang 為例,左側(cè)為手動(dòng)完成依賴(lài)注入,右側(cè)為不使用依賴(lài)注入

      ?? 不使用依賴(lài)注入風(fēng)險(xiǎn):

      1. 全局變量十分不安全,存在覆寫(xiě)的可能
      2. 資源散落在各處,可能重復(fù)創(chuàng)建,浪費(fèi)內(nèi)存,后續(xù)維護(hù)能力極差
      3. 提高循環(huán)依賴(lài)的風(fēng)險(xiǎn)
      4. 全局變量的引入提高單元測(cè)試的成本

      image-20240625222009500

      • 不使用依賴(lài)注入 demo
      package main
      
      var (
      	mysqlUrl = "mysql://blabla"
      	// 全局?jǐn)?shù)據(jù)庫(kù)實(shí)例
      	db = NewMySQLClient(mysqlUrl)
      )
      
      func NewMySQLClient(url string) *MySQLClient {
      	return &MySQLClient{url: url}
      }
      
      type MySQLClient struct {
      	url string
      }
      
      func (c *MySQLClient) Exec(query string, args ...interface{}) string {
      	return "data"
      }
      
      func NewApp() *App {
      	return &App{}
      }
      
      type App struct {
      }
      
      func (a *App) GetData(query string, args ...interface{}) string {
      	data := db.Exec(query, args...)
      	return data
      }
      
      // 不使用依賴(lài)注入
      func main() {
      	app := NewApp()
      	rest := app.GetData("select * from table where id = ?", "1")
      	println(rest)
      }
      
      • 手動(dòng)依賴(lài)注入 demo
      package main
      
      func NewMySQLClient(url string) *MySQLClient {
      	return &MySQLClient{url: url}
      }
      
      type MySQLClient struct {
      	url string
      }
      
      func (c *MySQLClient) Exec(query string, args ...interface{}) string {
      	return "data"
      }
      
      func NewApp(client *MySQLClient) *App {
      	return &App{client: client}
      }
      
      type App struct {
      	// App 持有唯一的 MySQLClient 實(shí)例
      	client *MySQLClient
      }
      
      func (a *App) GetData(query string, args ...interface{}) string {
      	data := a.client.Exec(query, args...)
      	return data
      }
      
      // 手動(dòng)依賴(lài)注入
      func main() {
      	client := NewMySQLClient("mysql://blabla")
      	app := NewApp(client)
      	rest := app.GetData("select * from table where id = ?", "1")
      	println(rest)
      }
      

      三、Why

      依賴(lài)注入 (Dependency Injection,縮寫(xiě)為 DI),可以理解為一種代碼的構(gòu)造模式(就是寫(xiě)法),按照這樣的方式來(lái)寫(xiě),能夠讓你的代碼更加容易維護(hù)。

      四、How

      4.1 Golang 依賴(lài)注入

      以 Golang ?? 最多的開(kāi)源庫(kù) wire 為例講解:https://github.com/google/wire/blob/main/docs/guide.md

      wire是由 google 開(kāi)源的一個(gè)供 Go 語(yǔ)言使用的依賴(lài)注入代碼生成工具。它能夠根據(jù)你的代碼,生成相應(yīng)的依賴(lài)注入 go 代碼。

      而與其它依靠反射實(shí)現(xiàn)的依賴(lài)注入工具不同的是,wire 能在編譯期(準(zhǔn)確地說(shuō)是代碼生成時(shí))如果依賴(lài)注入有問(wèn)題,在代碼生成時(shí)即可報(bào)出來(lái),不會(huì)拖到運(yùn)行時(shí)才報(bào),更便于 debug。

      • Install:
      go install github.com/google/wire/cmd/wire@latest
      
      • provider: a function that can produce a value

      以上面手動(dòng)實(shí)現(xiàn)依賴(lài)注入為基礎(chǔ),wire 做的工作是幫助開(kāi)發(fā)者完成如下組裝過(guò)程

      client := NewMySQLClient("mysql://blabla")
      app := NewApp(client)
      

      而其中用到的 NewMySQLClient、NewApp 在 wire 定義為一個(gè)個(gè)的 provider,是需要提前由開(kāi)發(fā)者實(shí)現(xiàn)的。

      func NewMySQLClient(url string) *MySQLClient {
      	return &MySQLClient{url: url}
      }
      
      func NewApp(client *MySQLClient) *App {
      	return &App{client: client}
      }
      

      假設(shè)系統(tǒng)中的資源很多,配置很多,出現(xiàn)了如下復(fù)雜的初始化流程,人工完成依賴(lài)注入則變得復(fù)雜:

      a := NewA(xxx, yyy) error
      b := NewB(ctx, a) error
      c := NewC(zzz, a, b) error
      d := NewD(www, kkk, a) error
      e := NewD(ctx, b, d) error
      
      • injector: a function that calls providers in dependency order

      如下是名為 wire.go 的依賴(lài)注入配置文件,是一個(gè)只會(huì)被 wire 命令行工具處理的 injector 文件,用于聲明依賴(lài)注入流程。

      wire.go:

      //go:build wireinject
      // +build wireinject
      
      // The build tag makes sure the stub is not built in the final build.
      
      package main
      
      import "github.com/google/wire"
      
      // wireApp init application.
      func wireApp(url string) *App {
      	wire.Build(NewMySQLClient, NewApp)
      	return nil
      }
      
      

      執(zhí)行 wire 命令,則在當(dāng)前目錄下生成 wire_gen.go 文件,此時(shí)的 wireApp 函數(shù),就等價(jià)于最初手動(dòng)編寫(xiě)的依賴(lài)注入流程,可以在真正需要初始化的引入。

      wire_gen.go:

      // Code generated by Wire. DO NOT EDIT.
      
      //go:generate go run -mod=mod github.com/google/wire/cmd/wire
      //go:build !wireinject
      // +build !wireinject
      
      package main
      
      // Injectors from wire.go:
      
      // wireApp init application.
      func wireApp(url string) *App {
         mySQLClient := NewMySQLClient(url)
         app := NewApp(mySQLClient)
         return app
      }
      

      4.2 針對(duì)復(fù)雜項(xiàng)目的依賴(lài)注入設(shè)計(jì)哲學(xué)

      這里以 go-kratos 的模版項(xiàng)目為例講解,是一個(gè) helloworld 服務(wù),我們著重分析其借助 wire 進(jìn)行依賴(lài)注入的部分。

      以下 helloworld 模板服務(wù)的 interanl 目錄的內(nèi)容:

      .
      ├── biz
      │   ├── README.md
      │   ├── biz.go
      │   └── greeter.go
      ├── conf
      │   ├── conf.pb.go
      │   └── conf.proto
      ├── data
      │   ├── README.md
      │   ├── data.go
      │   └── greeter.go
      ├── server
      │   ├── grpc.go
      │   ├── http.go
      │   └── server.go
      └── service
          ├── README.md
          ├── greeter.go
          └── service.go
      

      各個(gè)目錄的關(guān)系如圖:

      image-20240702235735708

      • data:業(yè)務(wù)數(shù)據(jù)訪(fǎng)問(wèn),包含 cache、db 等封裝,實(shí)現(xiàn)了 biz 的 repo 接口,data 偏重業(yè)務(wù)的含義,它所要做的是將領(lǐng)域?qū)ο笾匦履贸鰜?lái)。

      • biz:業(yè)務(wù)邏輯的組裝層,類(lèi)似 DDD 的 domain 層,data 類(lèi)似 DDD 的 repo,repo 接口在這里定義,使用依賴(lài)倒置的原則。

      • service:實(shí)現(xiàn)了 api 定義的服務(wù)層,類(lèi)似 DDD 的 application 層,處理 DTO 到 biz 領(lǐng)域?qū)嶓w的轉(zhuǎn)換(DTO -> DO),同時(shí)協(xié)同各類(lèi) biz 交互,但是不應(yīng)處理復(fù)雜邏輯。

      • server:為http和grpc實(shí)例的創(chuàng)建和配置,以及注冊(cè)對(duì)應(yīng)的 service 。

      ??上圖右側(cè)部分,表示了模塊之間的依賴(lài)關(guān)系,可以看到,依賴(lài)的注入是逆向的,資源往往被業(yè)務(wù)模塊持有,業(yè)務(wù)模塊則被負(fù)責(zé)編排業(yè)務(wù)的應(yīng)用持有,應(yīng)用則被負(fù)責(zé)對(duì)外通信的模塊持有。

      此時(shí)在服務(wù)啟動(dòng)前的實(shí)例化階段,provider 的定義和注入,本質(zhì)是這樣一種狀態(tài):

      func main() {
          dbClient := NewDBClient()
          dataN := NewDataN(dbClient)
          dataM := NewDataM(dbClient)
          bizA := NewBizA(dataN)
          bizB := NewBizB(dataM)
          bizC := NewBizC(dataN, dataM)
          serviceX := NewService(bizA, bizB, bizC)
          server := NewServer(serviceX)
          server.httpXXX // 提供 http 服務(wù)
          server.grpcXXX // 提供 grpc 服務(wù)
      }
      

      在 helloworld 這個(gè) demo 當(dāng)中,則是這樣定義 provider 的:

      // biz 目錄
      var ProviderSet = wire.NewSet(NewGreeterUsecase)
      
      type GreeterUsecase struct {
      	repo GreeterRepo
      	log  *log.Helper
      }
      
      func NewGreeterUsecase(repo GreeterRepo, logger log.Logger) *GreeterUsecase {
      	return &GreeterUsecase{repo: repo, log: log.NewHelper(logger)}
      }
      
      func (uc *GreeterUsecase) CreateGreeter(ctx context.Context, g *Greeter) (*Greeter, error) {
      	uc.log.WithContext(ctx).Infof("CreateGreeter: %v", g.Hello)
      	return uc.repo.Save(ctx, g)
      }
      
      // data 目錄
      var ProviderSet = wire.NewSet(NewData, NewGreeterRepo)
      
      type Data struct {
      	// TODO wrapped database client
      }
      
      func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) {
      	cleanup := func() {
      		log.NewHelper(logger).Info("closing the data resources")
      	}
      	return &Data{}, cleanup, nil
      }
      
      type greeterRepo struct {
      	data *Data
      	log  *log.Helper
      }
      
      func NewGreeterRepo(data *Data, logger log.Logger) biz.GreeterRepo {
      	return &greeterRepo{
      		data: data,
      		log:  log.NewHelper(logger),
      	}
      }
      // service 目錄
      var ProviderSet = wire.NewSet(NewGreeterService)
      
      type GreeterService struct {
      	v1.UnimplementedGreeterServer
      
      	uc *biz.GreeterUsecase
      }
      
      func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
      	return &GreeterService{uc: uc}
      }
      
      func (s *GreeterService) SayHello(ctx context.Context, in *v1.HelloRequest) (*v1.HelloReply, error) {
      	g, err := s.uc.CreateGreeter(ctx, &biz.Greeter{Hello: in.Name})
      	if err != nil {
      		return nil, err
      	}
      	return &v1.HelloReply{Message: "Hello " + g.Hello}, nil
      }
      
      // server 目錄
      var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer)
      
      func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, logger log.Logger) *grpc.Server {
      	var opts = []grpc.ServerOption{
      		grpc.Middleware(
      			recovery.Recovery(),
      		),
      	}
      	if c.Grpc.Network != "" {
      		opts = append(opts, grpc.Network(c.Grpc.Network))
      	}
      	if c.Grpc.Addr != "" {
      		opts = append(opts, grpc.Address(c.Grpc.Addr))
      	}
      	if c.Grpc.Timeout != nil {
      		opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration()))
      	}
      	srv := grpc.NewServer(opts...)
      	v1.RegisterGreeterServer(srv, greeter)
      	return srv
      }
      

      在 helloworld 這個(gè) demo 當(dāng)中,則是這樣定義 injector 的:

      // wire.go
      func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
         panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
      }
      

      最后運(yùn)行 wire 的到的完成注入的文件如下:

      // wire_gen.go
      func wireApp(confServer *conf.Server, confData *conf.Data, logger log.Logger) (*kratos.App, func(), error) {
      	dataData, cleanup, err := data.NewData(confData, logger)
      	if err != nil {
      		return nil, nil, err
      	}
      	greeterRepo := data.NewGreeterRepo(dataData, logger)
      	greeterUsecase := biz.NewGreeterUsecase(greeterRepo, logger)
      	greeterService := service.NewGreeterService(greeterUsecase)
      	grpcServer := server.NewGRPCServer(confServer, greeterService, logger)
      	httpServer := server.NewHTTPServer(confServer, greeterService, logger)
      	app := newApp(logger, grpcServer, httpServer)
      	return app, func() {
      		cleanup()
      	}, nil
      }
      

      生成代碼之后,則可以像使用普通的 golang 函數(shù)一樣,使用這個(gè) wire_gen.go 文件內(nèi)的 wireApp 函數(shù)實(shí)例化一個(gè) helloworld 服務(wù)

      func main() {
      	flag.Parse()
      	logger := log.With(log.NewStdLogger(os.Stdout),
      		// ...
      	)
      	c := config.New(
              // ...
      	)
      	defer c.Close()
      	// ...
      
      	app, cleanup, err := wireApp(bc.Server, bc.Data, logger)
      	if err != nil {
      		panic(err)
      	}
      	defer cleanup()
      
      	// start and wait for stop signal
      	if err := app.Run(); err != nil {
      		panic(err)
      	}
      }
      

      4.3 wire 的更多用法

      參見(jiàn) wire 的文檔,自己用幾遍就明白了,這里舉幾個(gè)例子:

      • 定義攜帶 error 返回值的 provider
      func ProvideBaz(ctx context.Context, bar Bar) (Baz, error) {
          if bar.X == 0 {
              return Baz{}, errors.New("cannot provide baz when bar is zero")
          }
          return Baz{X: bar.X}, nil
      }
      
      • provider 集合:方便組織多個(gè) provider
      var SuperSet = wire.NewSet(ProvideFoo, ProvideBar, ProvideBaz)
      
      • 接口綁定:
      type Fooer interface {
          Foo() string
      }
      
      type MyFooer string
      
      func (b *MyFooer) Foo() string {
          return string(*b)
      }
      
      func provideMyFooer() *MyFooer {
          b := new(MyFooer)
          *b = "Hello, World!"
          return b
      }
      
      type Bar string
      
      func provideBar(f Fooer) string {
          // f will be a *MyFooer.
          return f.Foo()
      }
      
      var Set = wire.NewSet(
          provideMyFooer,
          wire.Bind(new(Fooer), new(*MyFooer)),
          provideBar)
      

      五、對(duì)比 Spring Boot 的依賴(lài)注入

      Spring Boot的依賴(lài)注入(DI)和Golang開(kāi)源庫(kù)Wire的依賴(lài)注入在設(shè)計(jì)思路上存在一些相同點(diǎn)和不同點(diǎn)。以下是對(duì)這些相同點(diǎn)和不同點(diǎn)的分析:

      相同點(diǎn)

      1. 降低耦合度:兩者都通過(guò)依賴(lài)注入的方式實(shí)現(xiàn)了代碼的松耦合。這意味著,一個(gè)對(duì)象不需要顯式地創(chuàng)建或查找它所依賴(lài)的其他對(duì)象,這些依賴(lài)項(xiàng)會(huì)由外部容器(如Spring容器)或工具(如Wire)自動(dòng)提供。
      2. 提高可測(cè)試性:由于依賴(lài)關(guān)系被解耦,可以更容易地替換依賴(lài)項(xiàng)以進(jìn)行單元測(cè)試。無(wú)論是Spring Boot還是使用Wire的Golang應(yīng)用,都可以輕松地為組件提供模擬或存根的依賴(lài)項(xiàng)以進(jìn)行測(cè)試。
      3. 靈活性:兩者都允許在不修改組件代碼的情況下替換依賴(lài)項(xiàng)。這使得應(yīng)用程序在維護(hù)和擴(kuò)展時(shí)更加靈活。

      不同點(diǎn)

      1. 實(shí)現(xiàn)方式
        • Spring Boot的依賴(lài)注入是基于Java的反射機(jī)制和Spring框架的容器管理功能實(shí)現(xiàn)的。Spring容器負(fù)責(zé)創(chuàng)建和管理Bean的生命周期,并在需要時(shí)自動(dòng)注入依賴(lài)項(xiàng),核心在于運(yùn)行時(shí)
        • Wire是一個(gè)Golang的代碼生成工具,它通過(guò)分析代碼中的構(gòu)造函數(shù)和結(jié)構(gòu)體標(biāo)簽,自動(dòng)生成依賴(lài)注入的代碼(減少人工工作量),在開(kāi)發(fā)階段已經(jīng)通過(guò)工具生成好了依賴(lài)注入的代碼,程序編譯時(shí),資源之間的依賴(lài)關(guān)系已經(jīng)固定。
      2. 配置方式
        • Spring Boot的依賴(lài)注入通常通過(guò)配置文件(如application.properties或application.yml)和注解(如@Autowired)進(jìn)行配置。開(kāi)發(fā)者可以在配置文件中定義Bean的屬性,并通過(guò)注解在需要注入的地方指明依賴(lài)關(guān)系。
        • Wire則通過(guò)特殊的Go文件(通常是wire.go文件)來(lái)定義類(lèi)型之間的依賴(lài)關(guān)系。這些文件包含了用于生成依賴(lài)注入代碼的指令和元數(shù)據(jù)。
      3. 運(yùn)行時(shí)開(kāi)銷(xiāo)
        • Spring Boot的依賴(lài)注入在運(yùn)行時(shí)需要依賴(lài)Spring容器來(lái)管理Bean的生命周期和依賴(lài)關(guān)系。這可能會(huì)引入一些額外的運(yùn)行時(shí)開(kāi)銷(xiāo),特別是在大型應(yīng)用程序中。
        • Wire在編譯時(shí)生成依賴(lài)注入的代碼,因此它在運(yùn)行時(shí)沒(méi)有額外的開(kāi)銷(xiāo)。這使得使用Wire的Golang應(yīng)用程序通常具有更好的性能。

      六、參考資料

      kratos:https://go-kratos.dev/en/docs/getting-started/start/

      wire:https://github.com/google/wire/blob/main/_tutorial/README.md

      posted on 2024-07-03 09:17  白澤talk  閱讀(1154)  評(píng)論(0)    收藏  舉報(bào)

      主站蜘蛛池模板: 国产乱码精品一区二区三| 国产在线精品欧美日韩电影| 成人免费无遮挡在线播放| 青青青青久久精品国产| 精品亚洲精品日韩精品| xxxxbbbb欧美残疾人| AV免费网址在线观看| 精品日韩人妻中文字幕| 精品91在线| 免费VA国产高清大片在线| 哈巴河县| 99精品国产成人一区二区| 久久96热在精品国产高清| 国产伦码精品一区二区| 久热色视频精品在线观看| 国产成人精品无码免费看| 中文字幕无码专区一VA亚洲V专| 国产91麻豆精品成人区| 一本色道国产在线观看二区| 亚洲人成色99999在线观看| 国模在线视频一区二区三区| 国产人妻人伦精品婷婷| 精品国产成人国产在线视| 亚洲国产青草衣衣一二三区| 亚洲欧洲色图片网站| 麻豆国产成人AV在线播放| 色偷偷女人的天堂亚洲网| 免费人成视频在线| 国产成人午夜在线视频极速观看| 国产精品亚洲精品日韩已满十八小 | 2021国产成人精品久久| 国产精品人成视频免费国产| 国产亚洲精品成人aa片新蒲金| 伊人久久久av老熟妇色| 国产成人a∨激情视频厨房| 亚洲卡1卡2卡3精品| 东京热人妻丝袜无码AV一二三区观 | 亚洲av综合色一区二区| 国产一区二区三区免费观看| 男女18禁啪啪无遮挡激烈网站| 九九热免费精品视频在线|