当前位置:首页 > 科技  > 软件

使用 Google Wire 在 Go 中进行依赖注入

来源: 责编: 时间:2024-05-07 09:09:45 223观看
导读关注点分离、松耦合系统和依赖反转原则等概念在软件工程中是众所周知的,并且在创建良好的计算机程序过程中至关重要。在本文中,我们将讨论一个同时应用了这三个原则的技术,称为依赖注入。我们将尽可能地实践,更加重点地讨

关注点分离、松耦合系统和依赖反转原则等概念在软件工程中是众所周知的,并且在创建良好的计算机程序过程中至关重要。在本文中,我们将讨论一个同时应用了这三个原则的技术,称为依赖注入。我们将尽可能地实践,更加重点地讨论如何在 Go 应用程序中实现依赖注入。但在进一步讨论之前,让我们重新审视一下,究竟什么是依赖注入?6n428资讯网——每日最新资讯28at.com

6n428资讯网——每日最新资讯28at.com

如前所述,依赖注入是一种技术,其关注点在于确保想要使用特定服务的对象或函数不必知道如何构造这样的服务,从而将构造对象和使用对象的关注点分离,导致松耦合的程序。接收对象或函数由外部注入器提供其依赖项,而它并不知道这些依赖项。依赖注入还鼓励使用依赖反转原则,即接收对象只需要声明其使用的服务的接口,而不需要它们的具体实现。6n428资讯网——每日最新资讯28at.com

Robert van Gen 在他的一篇文章中指出,虽然依赖注入在小规模上运行良好,但对于具有复杂依赖关系图的更大型应用程序,它将导致大量的初始化代码块。这就是 Wire 等工具非常有用的地方。Wire 是 Go 中用于依赖注入的代码生成器。是的,你猜对了,Wire 将为我们生成必要的初始化代码。我们只需要定义提供程序和注入器。提供程序是普通的 Go 函数,根据它们的依赖项提供值,而注入器是按依赖顺序调用提供程序的函数。为了更好地说明这一点,将呈现一个示例。6n428资讯网——每日最新资讯28at.com

设置游乐场

假设我们正在开发一个 HTTP 服务器,为用户注册提供端点。尽管只有一个端点,但它是使用通常出现在更复杂应用程序中的三层设计:存储库、用例和控制器。出于不那么重要的原因,让我们假设它具有以下目录结构,6n428资讯网——每日最新资讯28at.com

.├── 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

现在,让我们在 internal/interface/datastore/user.go 中定义我们的第一个提供程序。在以下代码片段中,New 是一个提供者函数,它以 *sql.DB 为依赖项,并返回 Repository 的具体实现。6n428资讯网——每日最新资讯28at.com

// internal/interface/datastore/user.gopackage datastoreimport (    "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}

这个 Repository 的具体实现将通过抽象或接口由 UseCase 层使用。换句话说,我们的 UseCase 层的提供者函数依赖于接口,而不是 Repository 的具体实现。从技术上讲,这个接口应该由消费层拥有,但是 — 我个人认为 — 这并不意味着它们两者必须位于同一个包中。在我们的示例中,Usecase 的提供者和 Repository 的接口分别在 internal/usecase/user/user.go 和 internal/domain/repository/user.go 中定义。6n428资讯网——每日最新资讯28at.com

// internal/usecase/user/user.gopackage userimport (    "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 的提供者也返回一个具体的实现。6n428资讯网——每日最新资讯28at.com

// internal/domain/repository/user.gopackage repositoryimport (    "context"    "inject/internal/domain/model")type Repository interface {    Create(ctx context.Context, user model.User) error}

最后,具体的 UseCase 实现将被 Controller 使用,同样通过抽象或接口。Controller 的提供程序和 UseCase 的接口在 internal/handler/handler.go 和 internal/usecase/user.go 中定义,如下所示:6n428资讯网——每日最新资讯28at.com

// internal/interface/datastore/user.gopackage handlerimport (    "inject/internal/usecase"    "net/http")type Handler struct {    usecase usecase.Usecase}func New(usecase usecase.Usecase) *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.gopackage usecaseimport (    "context"    "inject/internal/usecase/request")type Usecase interface {    Create(ctx context.Context, req request.CreateUserRequest) error}

现在,所有必要的提供者都完成了,我们可以在我们的 main.go 中手动执行依赖注入,就像这样:6n428资讯网——每日最新资讯28at.com

// main.gopackage mainimport (    "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 生成像上面那样的初始化代码呢?6n428资讯网——每日最新资讯28at.com

使用 Wire

通过 Wire,我们打算使我们最终的 main.go 看起来更简化,像这样:6n428资讯网——每日最新资讯28at.com

// main.gopackage mainimport (    "log"    "net/http")func main() {    handler, err := InitializeHandler()    if err != nil {        log.Fatal(err)    }    log.Fatal(http.ListenAndServe(":8000", handler))}

我们可以开始创建一个文件,通常命名为 wire.go。它可以在一个单独的包中定义,但在这个示例中,我们将其定义在项目的根目录。但在继续创建 wire.go 之前,最好重构我们之前的一些代码,特别是创建数据库连接实例和注册 API 路由的部分。以下新提供者将起到这个目的,6n428资讯网——每日最新资讯28at.com

// pkg/mysql/mysql.gopackage mysqlimport (    "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.gopackage handlerimport "net/http"func Register(handler *Handler) *http.ServeMux {    mux := http.NewServeMux()    mux.HandleFunc("POST /users", handler.Create())    re

提供程序函数 Register 上面接受 Handler 的具体实现。当然,也可以使用抽象或接口。但我们将其保留,就像我们让 Repository 的提供程序函数接受类型 *sql.DB 的具体实现一样。这不违反我们之前提到的依赖反转原则。实际上,这可能是一个很好的例子,如果没有立即需要的情况,我们不必在我们的代码中创建抽象。6n428资讯网——每日最新资讯28at.com

好的,现在让我们回到我们的 wire.go。根据我们简化的 main.go 文件,你可能已经意识到 InitializeHandler 函数可能是由 Wire 生成的 — 是的,你说对了!要正确生成这样的函数,我们可以将我们的 wire.go 编写如下:6n428资讯网——每日最新资讯28at.com

//go:build wireinject// +build wireinjectpackage mainimport (    "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 关于初始化器函数 InitializeHandler 的模板。它返回 *http.ServeMux 和 error。请注意,(&http.ServeMux{}, nil) 的返回值仅用于满足编译器。为了正确返回所需的值,我们在 Build 函数中声明了所有必要的提供程序:mysql.New、datastore.New、user.New、handler.New 和 handler.Register。6n428资讯网——每日最新资讯28at.com

虽然 Wire 足够聪明,可以识别依赖图,但仍然需要明确告诉它某个具体实现满足某个接口。记住,datastore.New 和 user.New 返回类型为 *datastore.Repository 和 *user.Usecase 的具体实现,满足 repository.Repository 和 usecase.Usecase 接口的情况。这两种情况的必要显式声明通过 Bind 函数实现。6n428资讯网——每日最新资讯28at.com

请注意,我们需要将 wire.go 从最终的二进制文件中排除。这通过在 wire.go 文件顶部添加一个构建约束来完成。6n428资讯网——每日最新资讯28at.com

接下来,我们可以在应用程序的根目录中调用 wire 命令,6n428资讯网——每日最新资讯28at.com

wire

如果之前没有安装 Wire,请先运行以下命令,6n428资讯网——每日最新资讯28at.com

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

这个 wire 命令将生成一个名为 wire_gen.go 的文件,其内容是 InitializeHandler 函数的生成代码,如下所示:6n428资讯网——每日最新资讯28at.com

// Code generated by Wire. DO NOT EDIT.//go:generate go run -mod=mod github.com/google/wire/cmd/wire//go:build !wireinject// +build !wireinjectpackage wireimport (    "inject/internal/handler"    "inject/internal/interface/datastore"    "inject/internal/usecase/user"    "inject/pkg/mysql"    "net/http")// Injectors from wire.go://go:generate wirefunc 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 的第一个版本中编写的代码非常相似。6n428资讯网——每日最新资讯28at.com

修改依赖项

假设我们想要修改我们的 mysql.New 提供者,以接受一个配置结构,因为我们不想直接在其中硬编码数据源名称 — 这通常被认为是不好的做法。为了实现这一点,我们创建一个特殊的目录来存储应用程序配置文件和一个新的提供者,该提供者读取文件并返回一个配置结构。我们最终的目录结构将如下所示:6n428资讯网——每日最新资讯28at.com

.├── 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 结构以及它的提供者,6n428资讯网——每日最新资讯28at.com

package configtype Config struct {    DatabaseDSN string    AppPort     string}func Load() (Config, error) {    // TODO: implement me    return Config{}, nil}

接下来,我们只需要将这个新提供者添加到我们的 wire.go 文件中。是的,你说得对,将其作为 Build 函数的一部分插入即可,6n428资讯网——每日最新资讯28at.com

//go:build wireinject// +build wireinjectpackage wireimport (    "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}

再次运行 wire 命令 — 或者这次我们也可以运行 go generate 命令 — 将告诉 Wire 重新生成初始化代码,结果如下:6n428资讯网——每日最新资讯28at.com

// Code generated by Wire. DO NOT EDIT.//go:generate go run -mod=mod github.com/google/wire/cmd/wire//go:build !wireinject// +build !wireinjectpackage wireimport (    "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}

很简单,对吧?6n428资讯网——每日最新资讯28at.com

最后的话

我们已经介绍了使用 Wire 的简单示例,演示了它如何帮助我们构建具有依赖注入的初始化代码。但这并不是 Wire 的全部故事。实际上,它仍然有一些其他有用的功能尚未在这里讨论。6n428资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-86991-0.html使用 Google Wire 在 Go 中进行依赖注入

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Java中五个鲜为人知的Collections特性

下一篇: Python打包,setuptools打包六步法

标签:
  • 热门焦点
  • vivo TWS Air开箱体验:真轻 臻好听

    在vivo S15系列新机的发布会上,vivo的最新款真无线蓝牙耳机vivo TWS Air也一同发布,本次就这款耳机新品给大家带来一个简单的分享。外包装盒上,vivo TWS Air保持了vivo自家产
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • 在线图片编辑器,支持PSD解析、AI抠图等

    自从我上次分享一个人开发仿造稿定设计的图片编辑器到现在,不知不觉已过去一年时间了,期间我经历了裁员失业、面试找工作碰壁,寒冬下一直没有很好地履行计划.....这些就放在日
  • 10天营收超1亿美元,《星铁》比《原神》差在哪?

    来源:伯虎财经作者:陈平安即便你没玩过《原神》,你一定听说过的它的大名。恨它的人把《原神》开服那天称作是中国游戏史上最黑暗的一天,有粉丝因为索尼在PS平台上线《原神》,怒而
  • 网红炒股不为了赚钱,那就是耍流氓!

    来源:首席商业评论6月26日高调宣布入市,网络名嘴大v胡锡进居然进军了股市。在一次财经媒体峰会上,几个财经圈媒体大佬就“胡锡进炒股是否知道认真报道”展开讨论。有
  • 冯提莫签约抖音公会 前“斗鱼一姐”消失在直播间

    来源:直播观察提起“冯提莫”这个名字,很多网友或许听过,但应该不记得她是哪位主播了。其实,作为曾经的“斗鱼一姐”,冯提莫在游戏直播的年代影响力不输于现
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
  • 与兆芯合作 联想推出全新旗舰版笔记本电脑开天N7系列

    联想与兆芯合作推出全新联想旗舰版笔记本电脑开天 N7系列。这个系列采用兆芯KX-6640MA处理器平台,KX-6640MA 处理器是采用了陆家嘴架构,16nm 工艺,4 核 4 线
  • 北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top