偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

使用 Google Wire 在 Go 中進(jìn)行依賴注入

開發(fā) 后端
本文我們將介紹使用 Wire 的簡(jiǎn)單示例,演示了它如何幫助我們構(gòu)建具有依賴注入的初始化代碼。

關(guān)注點(diǎn)分離、松耦合系統(tǒng)和依賴反轉(zhuǎn)原則等概念在軟件工程中是眾所周知的,并且在創(chuàng)建良好的計(jì)算機(jī)程序過程中至關(guān)重要。在本文中,我們將討論一個(gè)同時(shí)應(yīng)用了這三個(gè)原則的技術(shù),稱為依賴注入。我們將盡可能地實(shí)踐,更加重點(diǎn)地討論如何在 Go 應(yīng)用程序中實(shí)現(xiàn)依賴注入。但在進(jìn)一步討論之前,讓我們重新審視一下,究竟什么是依賴注入?

如前所述,依賴注入是一種技術(shù),其關(guān)注點(diǎn)在于確保想要使用特定服務(wù)的對(duì)象或函數(shù)不必知道如何構(gòu)造這樣的服務(wù),從而將構(gòu)造對(duì)象和使用對(duì)象的關(guān)注點(diǎn)分離,導(dǎo)致松耦合的程序。接收對(duì)象或函數(shù)由外部注入器提供其依賴項(xiàng),而它并不知道這些依賴項(xiàng)。依賴注入還鼓勵(lì)使用依賴反轉(zhuǎn)原則,即接收對(duì)象只需要聲明其使用的服務(wù)的接口,而不需要它們的具體實(shí)現(xiàn)。

Robert van Gen 在他的一篇文章中指出,雖然依賴注入在小規(guī)模上運(yùn)行良好,但對(duì)于具有復(fù)雜依賴關(guān)系圖的更大型應(yīng)用程序,它將導(dǎo)致大量的初始化代碼塊。這就是 Wire 等工具非常有用的地方。Wire 是 Go 中用于依賴注入的代碼生成器。是的,你猜對(duì)了,Wire 將為我們生成必要的初始化代碼。我們只需要定義提供程序和注入器。提供程序是普通的 Go 函數(shù),根據(jù)它們的依賴項(xiàng)提供值,而注入器是按依賴順序調(diào)用提供程序的函數(shù)。為了更好地說明這一點(diǎn),將呈現(xiàn)一個(gè)示例。

設(shè)置游樂場(chǎng)

假設(shè)我們正在開發(fā)一個(gè) HTTP 服務(wù)器,為用戶注冊(cè)提供端點(diǎn)。盡管只有一個(gè)端點(diǎn),但它是使用通常出現(xiàn)在更復(fù)雜應(yīng)用程序中的三層設(shè)計(jì):存儲(chǔ)庫(kù)、用例和控制器。出于不那么重要的原因,讓我們假設(shè)它具有以下目錄結(jié)構(gòu),

.
├── go.mod
├── go.sum
├── internal
│   ├── domain
│   │   ├── model
│   │   │   └── user.go
│   │   └── repository
│   │       └── user.go
│   ├── handler
│   │   └── handler.go
│   ├── interface
│   │   └── datastore
│   │       └── user.go
│   └── usecase
│       ├── request
│       │   └── user.go
│       ├── user
│       │   └── user.go
│       └── user.go
└── main.go

現(xiàn)在,讓我們?cè)?nbsp;internal/interface/datastore/user.go 中定義我們的第一個(gè)提供程序。在以下代碼片段中,New 是一個(gè)提供者函數(shù),它以 *sql.DB 為依賴項(xiàng),并返回 Repository 的具體實(shí)現(xiàn)。

// internal/interface/datastore/user.go
package datastore

import (
    "context"
    "database/sql"
    "inject/internal/domain/model"
)

type Repository struct {
    db *sql.DB
}

func New(db *sql.DB) *Repository {
    return &Repository{db: db}
}

func (r Repository) Create(ctx context.Context, user model.User) error {
    // TODO: implement me
    return nil
}

這個(gè) Repository 的具體實(shí)現(xiàn)將通過抽象或接口由 UseCase 層使用。換句話說,我們的 UseCase 層的提供者函數(shù)依賴于接口,而不是 Repository 的具體實(shí)現(xiàn)。從技術(shù)上講,這個(gè)接口應(yīng)該由消費(fèi)層擁有,但是 — 我個(gè)人認(rèn)為 — 這并不意味著它們兩者必須位于同一個(gè)包中。在我們的示例中,Usecase 的提供者和 Repository 的接口分別在 internal/usecase/user/user.go 和 internal/domain/repository/user.go 中定義。

// internal/usecase/user/user.go
package user

import (
    "context"
    "inject/internal/domain/repository"
    "inject/internal/usecase/request"
)

type Usecase struct {
    repository repository.Repository
}

func New(repository repository.Repository) *Usecase {
    return &Usecase{repository: repository}
}

func (u Usecase) Create(ctx context.Context, req request.CreateUserRequest) error {
    // TODO: implement me
    return nil
}

就像之前 Repository 的提供者一樣,這里我們 UseCase 的提供者也返回一個(gè)具體的實(shí)現(xiàn)。

// internal/domain/repository/user.go
package repository

import (
    "context"
    "inject/internal/domain/model"
)

type Repository interface {
    Create(ctx context.Context, user model.User) error
}

最后,具體的 UseCase 實(shí)現(xiàn)將被 Controller 使用,同樣通過抽象或接口。Controller 的提供程序和 UseCase 的接口在 internal/handler/handler.go 和 internal/usecase/user.go 中定義,如下所示:

// internal/interface/datastore/user.go
package handler

import (
    "inject/internal/usecase"
    "net/http"
)

type Handler struct {
    usecase usecase.Usecase
}

func New(usecase usecase.U

secase) *Handler {
    return &Handler{usecase: usecase}
}

func (h Handler) Create() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // TODO: implement me
        w.WriteHeader(http.StatusOK)
    }
}
// internal/usecase/user.go
package usecase

import (
    "context"
    "inject/internal/usecase/request"
)

type Usecase interface {
    Create(ctx context.Context, req request.CreateUserRequest) error
}

現(xiàn)在,所有必要的提供者都完成了,我們可以在我們的 main.go 中手動(dòng)執(zhí)行依賴注入,就像這樣:

// main.go
package main

import (
    "database/sql"
    "log"
    "net/http"

    "inject/internal/handler"
    "inject/internal/interface/datastore"
    "inject/internal/usecase/user"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "dataSourceName")
    if err != nil {
        log.Fatalf("sql.Open: %v", err)
    }

    repository := datastore.New(db)
    usecase := user.New(repository)
    handler := handler.New(usecase)

    mux := http.NewServeMux()
    mux.HandleFunc("POST /users", handler.Create())

    log.Fatalf("http.ListenAndServe: %v", http.ListenAndServe(":8000", mux))
}

接下來,如何使用 Wire 生成像上面那樣的初始化代碼呢?

使用 Wire

通過 Wire,我們打算使我們最終的 main.go 看起來更簡(jiǎn)化,像這樣:

// main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    handler, err := InitializeHandler()
    if err != nil {
        log.Fatal(err)
    }

    log.Fatal(http.ListenAndServe(":8000", handler))
}

我們可以開始創(chuàng)建一個(gè)文件,通常命名為 wire.go。它可以在一個(gè)單獨(dú)的包中定義,但在這個(gè)示例中,我們將其定義在項(xiàng)目的根目錄。但在繼續(xù)創(chuàng)建 wire.go 之前,最好重構(gòu)我們之前的一些代碼,特別是創(chuàng)建數(shù)據(jù)庫(kù)連接實(shí)例和注冊(cè) API 路由的部分。以下新提供者將起到這個(gè)目的,

// pkg/mysql/mysql.go
package mysql

import (
    "database/sql"

    _ "github.com/go-sql-driver/mysql"
)

func New() (*sql.DB, error) {
    db, err := sql.Open("mysql", "dataSourceName")
    if err != nil {
        return nil, err
    }
    return db, nil
}
// internal/handler/route.go
package handler

import "net/http"

func Register(handler *Handler) *http.ServeMux {
    mux := http.NewServeMux()
    mux.HandleFunc("POST /users", handler.Create())
    re

提供程序函數(shù) Register 上面接受 Handler 的具體實(shí)現(xiàn)。當(dāng)然,也可以使用抽象或接口。但我們將其保留,就像我們讓 Repository 的提供程序函數(shù)接受類型 *sql.DB 的具體實(shí)現(xiàn)一樣。這不違反我們之前提到的依賴反轉(zhuǎn)原則。實(shí)際上,這可能是一個(gè)很好的例子,如果沒有立即需要的情況,我們不必在我們的代碼中創(chuàng)建抽象。

好的,現(xiàn)在讓我們回到我們的 wire.go。根據(jù)我們簡(jiǎn)化的 main.go 文件,你可能已經(jīng)意識(shí)到 InitializeHandler 函數(shù)可能是由 Wire 生成的 — 是的,你說對(duì)了!要正確生成這樣的函數(shù),我們可以將我們的 wire.go 編寫如下:

//go:build wireinject
// +build wireinject

package main

import (
    "net/http"
  
    "inject/internal/domain/repository"
    "inject/internal/handler"
    "inject/internal/interface/datastore"
    "inject/internal/usecase"
    "inject/internal/usecase/user"
    "inject/pkg/mysql"

    "github.com/google/wire"
)

func InitializeHandler() (*http.ServeMux, error) {
    wire.Build(
        mysql.New,
        datastore.New,
        wire.Bind(new(repository.Repository), new(*datastore.Repository)),
        user.New,
        wire.Bind(new(usecase.Usecase), new(*user.Usecase)),
        handler.New,
        handler.Register,
    )
    return &http.ServeMux{}, nil
}

基本上,在 wire.go 中,我們告訴 Wire 關(guān)于初始化器函數(shù) InitializeHandler 的模板。它返回 *http.ServeMux 和 error。請(qǐng)注意,(&http.ServeMux{}, nil) 的返回值僅用于滿足編譯器。為了正確返回所需的值,我們?cè)?Build 函數(shù)中聲明了所有必要的提供程序:mysql.New、datastore.New、user.New、handler.New 和 handler.Register。

雖然 Wire 足夠聰明,可以識(shí)別依賴圖,但仍然需要明確告訴它某個(gè)具體實(shí)現(xiàn)滿足某個(gè)接口。記住,datastore.New 和 user.New 返回類型為 *datastore.Repository 和 *user.Usecase 的具體實(shí)現(xiàn),滿足 repository.Repository 和 usecase.Usecase 接口的情況。這兩種情況的必要顯式聲明通過 Bind 函數(shù)實(shí)現(xiàn)。

請(qǐng)注意,我們需要將 wire.go 從最終的二進(jìn)制文件中排除。這通過在 wire.go 文件頂部添加一個(gè)構(gòu)建約束來完成。

接下來,我們可以在應(yīng)用程序的根目錄中調(diào)用 wire 命令,

wire

如果之前沒有安裝 Wire,請(qǐng)先運(yùn)行以下命令,

go install github.com/google/wire/cmd/wire@latest

這個(gè) wire 命令將生成一個(gè)名為 wire_gen.go 的文件,其內(nèi)容是 InitializeHandler 函數(shù)的生成代碼,如下所示:

// 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 wire

import (
    "inject/internal/handler"
    "inject/internal/interface/datastore"
    "inject/internal/usecase/user"
    "inject/pkg/mysql"
    "net/http"
)

// Injectors from wire.go:

//go:generate wire
func InitializeHandler() (*http.ServeMux, error) {
    db, err := mysql.New()
    if err != nil {
        return nil, err
    }
    repository := datastore.New(db)


    usecase := user.New(repository)
    handlerHandler := handler.New(usecase)
    serveMux := handler.Register(handlerHandler)
    return serveMux, nil
}

生成的初始化器代碼看起來與我們之前在 main.go 的第一個(gè)版本中編寫的代碼非常相似。

修改依賴項(xiàng)

假設(shè)我們想要修改我們的 mysql.New 提供者,以接受一個(gè)配置結(jié)構(gòu),因?yàn)槲覀儾幌胫苯釉谄渲杏簿幋a數(shù)據(jù)源名稱 — 這通常被認(rèn)為是不好的做法。為了實(shí)現(xiàn)這一點(diǎn),我們創(chuàng)建一個(gè)特殊的目錄來存儲(chǔ)應(yīng)用程序配置文件和一個(gè)新的提供者,該提供者讀取文件并返回一個(gè)配置結(jié)構(gòu)。我們最終的目錄結(jié)構(gòu)將如下所示:

.
├── config
│   ├── config.go
│   └── file
│       └── config.json
├── go.mod
├── go.sum
├── internal
│   ├── domain
│   │   ├── model
│   │   │   └── user.go
│   │   └── repository
│   │       └── user.go
│   ├── handler
│   │   ├── handler.go
│   │   └── route.go
│   ├── interface
│   │   └── datastore
│   │       └── user.go
│   └── usecase
│       ├── request
│       │   └── user.go
│       ├── user
│       │   └── user.go
│       └── user.go
├── main.go
├── pkg
│   └── mysql
│       └── mysql.go
├── wire_gen.go
└── wire.go

在 config/config.go 中,我們定義了 Config 結(jié)構(gòu)以及它的提供者,

package config

type Config struct {
    DatabaseDSN string
    AppPort     string
}

func Load() (Config, error) {
    // TODO: implement me
    return Config{}, nil
}

接下來,我們只需要將這個(gè)新提供者添加到我們的 wire.go 文件中。是的,你說得對(duì),將其作為 Build 函數(shù)的一部分插入即可,

//go:build wireinject
// +build wireinject

package wire

import (
    "net/http"

    "inject/config"
    "inject/internal/domain/repository"
    "inject/internal/handler"
    "inject/internal/interface/datastore"
    "inject/internal/usecase"
    "inject/internal/usecase/user"
    "inject/pkg/mysql"

    "github.com/google/wire"
)

func InitializeHandler() (*http.ServeMux, error) {
    wire.Build(
        config.Load,
        mysql.New,
        datastore.New,
        wire.Bind(new(repository.Repository), new(*datastore.Repository)),
        user.New,
        wire.Bind(new(usecase.Usecase), new(*user.Usecase)),
        handler.New,
        handler.Register,
    )
    return &http.ServeMux{}, nil
}

再次運(yùn)行 wire 命令 — 或者這次我們也可以運(yùn)行 go generate 命令 — 將告訴 Wire 重新生成初始化代碼,結(jié)果如下:

// 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 wire

import (
    "inject/config"
    "inject/internal/handler"
    "inject/internal/interface/datastore"
    "inject/internal/usecase/user"
    "inject/pkg/mysql"
    "net/http"
)

// Injectors from wire.go:

func InitializeHandler() (*http.ServeMux, error) {
    configConfig, err := config.Load()
    if err != nil {
        return nil, err
    }
    db, err := mysql.New(configConfig)
    if err != nil {
        return nil, err
    }
    repository := datastore.New(db)
    usecase := user.New(repository)
    handlerHandler := handler.New(usecase)
    serveMux := handler.Register(handlerHandler)
    return serveMux, nil
}

很簡(jiǎn)單,對(duì)吧?

最后的話

我們已經(jīng)介紹了使用 Wire 的簡(jiǎn)單示例,演示了它如何幫助我們構(gòu)建具有依賴注入的初始化代碼。但這并不是 Wire 的全部故事。實(shí)際上,它仍然有一些其他有用的功能尚未在這里討論。

責(zé)任編輯:趙寧寧 來源: 技術(shù)的游戲
相關(guān)推薦

2024-05-27 00:13:27

Go語言框架

2022-09-30 15:31:21

Golang開發(fā)工具

2024-04-01 00:02:56

Go語言代碼

2023-10-18 18:31:04

SQL查詢數(shù)據(jù)

2023-08-02 08:02:30

Redis數(shù)據(jù)原生方法

2021-08-27 14:36:01

主題建模BerTopic

2020-06-30 08:23:00

JavaScript開發(fā)技術(shù)

2019-04-18 09:15:05

DaskPython計(jì)算

2009-06-22 10:29:11

集成測(cè)試Spring

2021-07-07 10:48:00

DigGoWire

2020-03-07 18:00:17

logzeroPython日志記錄

2009-05-21 16:41:22

GuiceJava依賴注入

2011-08-01 10:41:59

Xcode 條件編譯

2009-12-28 13:59:12

ADO調(diào)用存儲(chǔ)過程

2023-10-28 16:22:21

Go接口

2021-11-29 22:59:34

Go Dockertest集成

2009-03-03 09:00:57

Silverlight數(shù)據(jù)驗(yàn)證UI控件

2021-03-24 09:30:02

Jupyter not單元測(cè)試代碼

2020-08-11 13:00:34

GNU bcLinuxShell

2020-08-06 00:14:16

Spring IoC依賴注入開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)