惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
S
Securelist
J
Java Code Geeks
Recorded Future
Recorded Future
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
S
Secure Thoughts
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
Martin Fowler
Martin Fowler
The Last Watchdog
The Last Watchdog
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
O
OpenAI News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
PCI Perspectives
PCI Perspectives
N
News and Events Feed by Topic
H
Heimdal Security Blog
SecWiki News
SecWiki News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 【当耐特】
T
Troy Hunt's Blog
L
LINUX DO - 最新话题
Hacker News: Ask HN
Hacker News: Ask HN
Hacker News - Newest:
Hacker News - Newest: "LLM"
N
Netflix TechBlog - Medium
A
Arctic Wolf
The Hacker News
The Hacker News
I
Intezer
S
Schneier on Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Apple Machine Learning Research
Apple Machine Learning Research
L
Lohrmann on Cybersecurity
宝玉的分享
宝玉的分享
P
Privacy & Cybersecurity Law Blog
Stack Overflow Blog
Stack Overflow Blog
T
Tor Project blog
小众软件
小众软件
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
Jina AI
Jina AI

Mohuishou

如何实现支持多集群的 Kubernetes Operator? 第三方应用如何调用我们 kubebuilder 生成的自定义资源? Kubernetes 简明教程 k8s job 为何迟迟不能结束? Go 工程化(十一) 如何优雅的写出 repo 层代码 Go 工程化(十) 如何在整洁架构中使用事务? 给博客添加章节目录 使用 Notion Database 管理静态博客文章 一个普通 Go 开发的三年 4. localhost 就一定是 localhost 么? Go可用性(七) 总结: 一张图串联可用性知识点 Go可用性(六) 熔断 10. 总结 9. kubebuilder 进阶: 源码分析 8. kubebuilder 进阶: webhook 7. kubebuilder 进阶: 测试 6. kubebuilder 实战: status & event 5. kubebuilder 实战: CRUD 4. kustomize 简明教程 3. KubeBuilder 简明教程 2. Kind: 如何快速搭建本地 K8s 开发环境? 1. Operator概述: 如何对 Kubernetes 进行扩展 Go可用性(五) 自适应限流 Go可用性(四) 漏桶算法 Go可用性(三) 令牌桶的实现 rate/limt Go可用性(二) 令牌桶原理及使用 Go可用性(一) 隔离设计 Go并发编程(十二) Singleflight Go工程化(九) 项目重构实践 Go工程化(八) 单元测试 Go工程化(七) Go Module Go工程化(六) 配置管理 Go工程化(五) API 设计下: 基于 protobuf 自动生成 gin 代码 Go工程化(四) API 设计上: 项目结构 & 设计 Go工程化(二) 项目目录结构 Go工程化(一) 架构整洁之道阅读笔记 Go并发编程(十一) 总结 Go并发编程(十) 深入理解 Channel Go并发编程(九) 深入理解 Context Go并发编程(八) 深入理解 sync.Once Go并发编程(七) 深入理解 errgroup Go并发编程(六) 深入理解 WaitGroup Go并发编程(五) 深入理解 sync/atomic Go并发编程(四) 深入理解 Mutex Go并发编程(三) data race Go并发编程(二) Go 内存模型 Go并发编程(一) goroutine Go错误处理最佳实践 微服务(二) 服务发现&多租户 微服务(一) 微服务概览 5. 栈下: 深入理解 defer 4. 栈上: 如何实现一个计算器 Go Struct 初始化风格的抉择 3. 数组下: 使用 GDB 调试 Golang 代码 2. 数组上: 深入理解 slice 1. 链表: 深入理解container/list&LRU缓存的实现 Go设计模式24-总结(更新完毕) Go设计模式23-中介模式 Go设计模式22-解释器模式 Go设计模式21-命令模式 Go设计模式20-备忘录模式 Go设计模式19-访问者模式 Go设计模式18-迭代器模式 Go设计模式17-状态模式 Go设计模式16-职责链模式(Gin的中间件实现) Go设计模式15-策略模式 Go模板模式14-模板模式 Go设计模式13-观察者模式(实现简单的EventBus) Go设计模式12-享元模式 Go设计模式11-组合模式 Go设计模式10-门面模式 Go设计模式09-适配器模式 Go设计模式08-装饰器模式 Go设计模式07-桥接模式 Go设计模式06-代理模式(generate实现类似动态代理) Go设计模式05-创建型模式总结 Go设计模式04-原型模式 Go设计模式03-建造者模式 Go设计模式02-工厂模式&DI容器 笔记-让你最快速地改善代码质量的20条编程规范 Go设计模式01-单例模式 一点拙见-如何写好一个技术预研报告? Go Web小技巧(四)在单个仓库中支持多个 go mod 模块 Go Web 小技巧(三)Gin 参数绑定 Go Web 小技巧(二)GORM 使用自定义类型 Go Web 小技巧(一)简化Gin接口代码 善用工具之postman高级用法概述 go generate and ast hexo-next-algolia-search全文搜索 docker镜像瘦身&优化 GORM避坑指南之含关联关系的更新 Github Actions介绍&自动构建Github Pages博客 在blog中内嵌在线PPT 记一次net http内存泄漏 使用TravisCI自动部署Blog 使用Goland调试Go程序 一个十分边缘的gorm的bug Httprouter介绍及源码阅读 Gin源码阅读 从0.1开始
Go工程化(三) 依赖注入框架 wire
Mohuishou · 2021-02-22 · via Mohuishou

注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用

本系列为 Go 进阶训练营 笔记,访问 博客: Go进阶训练营, 即可查看当前更新进度,部分文章篇幅较长,使用 PC 大屏浏览体验更佳。

在上一篇文章当中我们讲到了项目的目录结构,大体上水平切分为了四层,然后再根据需要进行垂直切分,然后由于我们大量的使用到了接口和依赖注入的手段,所以在项目初始化的时候如果手动进行依赖关系的初始化会比较麻烦,这时候就需要用到依赖注入的框架了。

在刚开始接触 go 的那一段时间,我是比较排斥使用太多框架的,觉得保持简单更加重要,这种想法在很长一段时间(大概两年左右)都没有问题,直到正式工作一段时间之后发现,随着开发合作的同学的增多以及部门的要求增加,项目启动时的依赖越来越多,依赖之间还有先后顺序,有一些甚至是隐式的顺序,到时 main 函数的代码膨胀的非常迅速并且慢慢的变的不可维护了,这种情况下引入依赖注入框架其实可以省心很多。

Golang 的依赖注入框架有两类,一类是通过反射在运行时进行依赖注入,典型代表是 uber 开源的 dig,另外一类是通过 generate 进行代码生成,典型代表是 Google 开源的 wire。使用 dig 功能会强大一些,但是缺点就是错误只能在运行时才能发现,这样如果不小心的话可能会导致一些隐藏的 bug 出现。使用 wire 的缺点就是功能限制多一些,但是好处就是编译的时候就可以发现问题,并且生成的代码其实和我们自己手写相关代码差不太多,更符合直觉,心智负担更小,所以我更加推荐 wire,如果对 dig 感兴趣可以跳转到文章参考文献处跳转查阅。

本文分为两个部分,首先介绍 wire 的使用方法,然后是结合上一篇文章中的工程目录,我在使用 wire 过程中的一些 “最佳实践” 避免大家重复踩坑。

如果你对 wire 已经比较熟悉可以直接跳过这一部分,阅读完本文之后建议对照看一下官方文档再进行操作。 注:本文基于 wire 0.5.0 进行编写

安装

安装很简单,只要安装了 Go 并且已经把 $GOPATH/bin  加入到了 PATH  当中,终端执行下面的语句即可

1
go get github.com/google/wire/cmd/wire

Provider

正式开始前需要先了解一下 wire 当中的两个概念:provider 和 injector

Provider 是一个普通的函数,这个函数会返回构建依赖关系所需的组件。如下所示,就是一个 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。

1
2
// NewPostRepo 创建文章 Repo
func NewPostRepo() IPostRepo {}

不过需要注意的是在 wire 中不能存在两个 provider 返回相同的组件类型

Injector

injector 也是一个普通函数,我们常常在 wire.go  文件中定义 injector 函数签名,然后通过 wire  命令自动生成一个完整的函数

1
2
3
4
5
//+build wireinject

func GetBlogService() *Blog {
panic(wire.Build(NewBlogService, NewPostUsecase, NewPostRepo))
}

第一行的 //+build wireinject  注释确保了这个文件在我们正常编译的时候不会被引用,而 wire .  生成的文件 wire_gen.go  会包含 //+build !wireinject  注释,正常编译的时候,不指定 tag 的情况下会引用这个文件

wire.Build  在 injector  函数中使用,用于表名这个 injector  由哪些 provider  提供依赖, injector  函数本身只是一个函数签名,所以我们直接在函数中 panic  实际生成代码的时候并不会直接调用 panic

一个完整的 🌰

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package example

// repo

// IPostRepo IPostRepo
type IPostRepo interface{}

// NewPostRepo NewPostRepo
func NewPostRepo() IPostRepo {
return new(IPostRepo)
}

// usecase

// IPostUsecase IPostUsecase
type IPostUsecase interface{}
type postUsecase struct {
repo IPostRepo
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) IPostUsecase {
return postUsecase{repo: repo}
}

// service service

// PostService PostService
type PostService struct {
usecase IPostUsecase
}

// NewPostService NewPostService
func NewPostService(u IPostUsecase) *PostService {
return &PostService{usecase: u}
}

上面的是一个简单的示例, NewPostService NewPostUsecase  这些都是 Provider  函数,下面我们在 wire.go  当中构建 Injector  函数签名

1
2
3
4
5
6
7
8
9
10
11
12
13
//+build wireinject

package example

import "github.com/google/wire"

func GetPostService() *PostService {
panic(wire.Build(
NewPostService,
NewPostUsecase,
NewPostRepo,
))
}

我们在目录下执行 wire .  生成如下文件,可以看到生成的函数和我们自己手写其实差不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package example

// Injectors from wire.go:

func GetPostService() *PostService {
iPostRepo := NewPostRepo()
iPostUsecase := NewPostUsecase(iPostRepo)
postService := NewPostService(iPostUsecase)
return postService
}

缺少 provider

在执行 wire .  的时候,如果我们的缺少某个 Provider  提供依赖,wire 会进行提示,帮助我们快速找到问题并且修改
还是上面的这个例子,我们删除掉一个 Provider 函数试试

1
2
3
4
5
6
func GetPostService() *PostService {
panic(wire.Build(
NewPostService,
NewPostUsecase,
))
}

再次执行 wire  命令,可以发现报错

1
2
3
4
5
6
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/wire.go:7:1: inject GetPostService: no provider found for github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostRepo
needed by github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostUsecase in provider "NewPostUsecase" (/Go-000/Week04/blog/03_wire/01_example/example.go:22:6)
needed by *github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.PostService in provider "NewPostService" (/Go-000/Week04/blog/03_wire/01_example/example.go:34:6)
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure

返回错误

在 go 中如果遇到错误,我们会在最后一个返回值返回 error,wire 同样也支持返回错误的情况,只需要在 injector 的函数签名中加上 error 返回值即可,还是前面的那个例子,我们让 NewPostService  返回 error,并且修改 GetPostService  这个 Injector  函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// example.go
// NewPostService NewPostService
func NewPostService(u IPostUsecase) (*PostService, error) {
return &PostService{usecase: u}, nil
}

// wire.go

func GetPostService() (*PostService, error) {
panic(wire.Build(
NewPostService,
NewPostUsecase,
NewPostRepo,
))
}

生成的代码如下所示,可以发现会像我们自己写代码一样判断一下 if err  然后返回

1
2
3
4
5
6
7
8
9
10
11
// wire_gen.go

func GetPostService() (*PostService, error) {
iPostRepo := NewPostRepo()
iPostUsecase := NewPostUsecase(iPostRepo)
postService, err := NewPostService(iPostUsecase)
if err != nil {
return nil, err
}
return postService, nil
}

清理函数

有时候我们需要打开文件,或者是链接这种需要关闭的资源,这时候 provider 可以返回一个闭包函数 func() ,wire 在进行构建的时候,会在报错的时候调用,并且会将所有的闭包函数聚合返回。
这个特性一般用的不多,但是有需求的时候会十分有用。

还是之前的示例,我们修改一下 NewPostRepo NewPostUsecase  让他们返回一个清理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// example.go

// NewPostRepo NewPostRepo
func NewPostRepo() (IPostRepo, func(), error) {
return new(IPostRepo), nil, nil
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) (IPostUsecase, func(), error) {
return postUsecase{repo: repo}, nil, nil
}

// wire.go

func GetPostService() (*PostService, func(), error) {
panic(wire.Build(
NewPostService,
NewPostUsecase,
NewPostRepo,
))
}

执行 wire .  之后我们可以发现生成的函数当中,当 NewPostUsecase  出现错误的时候会自动帮我们调用 NewPostRepo  返回的 cleanup  函数,而 NewPostService  返回错误,会调用它依赖的所有 provider 的 cleanup 函数,如果都没有问题,就会把所有 cleanup 函数聚合为一个函数返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func GetPostService() (*PostService, func(), error) {
iPostRepo, cleanup, err := NewPostRepo()
if err != nil {
return nil, nil, err
}
iPostUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
if err != nil {
cleanup()
return nil, nil, err
}
postService, err := NewPostService(iPostUsecase)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
return postService, func() {
cleanup2()
cleanup()
}, nil
}

高级方法

接口注入

我们应该依赖接口,而不是实现。返回数据的时候返回实现而不是接口,这是在 Golang 中的最佳实践(当然也不是所有的都是这样),所以如果我们的 provider 返回了实现,但是我们的依赖的是接口,这时候就会报错了,我们先来看一个例子。

我们修改一下 NewPostUsecase  方法,让他返回 *PostUsecase  而不是接口

1
2
3
4
// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) (*PostUsecase, func(), error) {
return &PostUsecase{repo: repo}, nil, nil
}

这时候执行 wire .  生成代码会发现报错,找不到 IPostUsecase  的 provider

1
2
3
4
5
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/wire.go:7:1: inject GetPostService: no provider found for github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostUsecase
needed by *github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.PostService in provider "NewPostService" (/Go-000/Week04/blog/03_wire/01_example/example.go:36:6)
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure

这时候就需要使用 wire.Bind  将 Struct  和接口进行绑定了,表示这个结构体实现了这个接口,我们修改一下 injector  函数

1
2
3
4
5
6
7
8
func GetPostService() (*PostService, func(), error) {
panic(wire.Build(
NewPostService,
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
NewPostUsecase,
NewPostRepo,
))
}

wire.Bind  的使用方法就是 wire.Bind(new(接口), new(实现))

Struct 属性注入

在上面 NewPostService  代码,我们可以发现有很多 Struct  的初始化其实就是填充里面的属性,没有其他的逻辑,这种情况我们可以偷点懒直接使用 wire.Struct  方法直接生成 provider

1
2
3
// structType: 结构体类型
// fieldNames: 需要填充的字段,使用 "*" 表示所有字段都需要填充
Struct(structType interface{}, fieldNames ...string)

我们修改一下 Injector  函数

1
2
3
4
5
6
7
8
9
func GetPostService() (*PostService, func(), error) {
panic(wire.Build(
// 这里由于只有一个字段,所以这两种是等价的 wire.Struct(new(PostService), "*"),
wire.Struct(new(PostService), "usecase"),
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
NewPostUsecase,
NewPostRepo,
))
}

可以看到生成的代码当中自动就生成了一个结构体并且填充数据了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func GetPostService() (*PostService, func(), error) {
iPostRepo, cleanup, err := NewPostRepo()
if err != nil {
return nil, nil, err
}
postUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
if err != nil {
cleanup()
return nil, nil, err
}
// 注意这里
postService := &PostService{
usecase: postUsecase,
}
return postService, func() {
cleanup2()
cleanup()
}, nil
}

值绑定

除了依赖某一个类型之外,有时候我们还会依赖一些具体的值,这时候我们就可以使用 wire.Value  或者是 wire.InterfaceValue ,为某个类型绑定具体的值

1
2
3
4
// wire.Value 为某个类型绑定值,但是不能为接口绑定值
Value(interface{}) ProvidedValue
// wire.InterfaceValue 为接口绑定值
InterfaceValue(typ interface{}, x interface{}) ProvidedValue

我们修改一下 PostService  使他依赖一个 int 和 io.Reader 然后为它直接绑定 a=99 io.Reader = os.Stdin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// example.go

type PostService struct {
usecase IPostUsecase
a int
r io.Reader
}

// wire.go

func GetPostService() (*PostService, func(), error) {
panic(wire.Build(
wire.Struct(new(PostService), "*"),
wire.Value(10),
wire.InterfaceValue(new(io.Reader), os.Stdin),
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
NewPostUsecase,
NewPostRepo,
))
}

可以看到生成的代码当中直接生成了两个全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func GetPostService() (*PostService, func(), error) {
iPostRepo, cleanup, err := NewPostRepo()
if err != nil {
return nil, nil, err
}
postUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
if err != nil {
cleanup()
return nil, nil, err
}
int2 := _wireIntValue
reader := _wireFileValue
postService := &PostService{
usecase: postUsecase,
a: int2,
r: reader,
}
return postService, func() {
cleanup2()
cleanup()
}, nil
}

// 注意这里
var (
_wireIntValue = 10
_wireFileValue = os.Stdin
)

ProviderSet(Provider 集合)

在真实的项目当中依赖往往是一组一组的,就像我们的示例一样,只要依赖 PostService  那么 NewPostUsecase NewPostRepo  这两个就必不可少,所以我们往往会创建一些 ProviderSet  在 Injector  函数中直接依赖 ProviderSet  就可以了

1
2
// 参数是一些 provider
NewSet(...interface{}) ProviderSet

示例如下所示,生成代码和之前一样就不另外贴了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// example.go

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
wire.Struct(new(PostService), "*"),
wire.Value(10),
wire.InterfaceValue(new(io.Reader), os.Stdin),
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
NewPostUsecase,
NewPostRepo,
)

// wire.go

func GetPostService() (*PostService, func(), error) {
panic(wire.Build(
PostServiceSet,
))
}

wire 使用最佳实践

不要使用默认类型

之前有提到过,wire 不支持两个提供两个相同类型的 provider,所以如果我们使用默认类型如 int string  等,只要有两个依赖就会导致报错,解决方案是使用类型别名。
先来看一个报错的示例

1
2
3
4
5
6
type PostService struct {
usecase IPostUsecase
a int
b int
r io.Reader
}

可以看到,wire 在构建依赖关系的时候,并不知道 int 的值该分配给 a 还是 b 所以就会报错

1
2
3
4
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/example.go:40:2: provider struct has multiple fields of type int
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure

我们自定义两个类型就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type A int
type B int

// PostService PostService
type PostService struct {
usecase IPostUsecase
a A
b B
r io.Reader
}

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
wire.Struct(new(PostService), "*"),
wire.Value(A(10)),
wire.Value(B(10)),
wire.InterfaceValue(new(io.Reader), os.Stdin),
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
NewPostUsecase,
NewPostRepo,
)

这种方式在使用上会感觉有点糟心,但是就我目前的使用来看,用到基础类型的情况还是比价少,所以也还好

Option Struct

在实际的业务场景当中我们的 NewXXX  函数的参数列表可能会很长,这个时候就可以直接定义一个 Option Struct 然后使用 wire.Strcut  来构建 Option Strcut 的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// PostUsecaseOption PostUsecaseOption
type PostUsecaseOption struct {
a A
b B
repo IPostRepo
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(opt *PostUsecaseOption) (*PostUsecase, func(), error) {
return &PostUsecase{repo: opt.repo}, nil, nil
}

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
wire.Struct(new(PostService), "*"),
wire.Value(A(10)),
wire.Value(B(10)),
wire.InterfaceValue(new(io.Reader), os.Stdin),

// for usecase
wire.Bind(new(IPostUsecase), new(*PostUsecase)),
wire.Struct(new(PostUsecaseOption), "*"),
NewPostUsecase,

NewPostRepo,
)

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── api
├── cmd
│ └── app
│ ├── main.go
│ ├── wire.go
│ └── wire_gen.go
└── internal
├── domain
│ └── post.go
├── repo
│ └── repo.go
├── service
│ └── service.go
├── usecase
│ └── usecase.go
└── wire_set.go
  • 一般在 cmd/xxx 目录下创建 wire.go  用于构建 injector  函数签名,因为我们一般会在 main  当中构建依赖关系完成服务启动
  • 在 internal 或者是 internal/app 目录下创建 wire_set.go  构建 ProviderSet ,这里要注意
    • 这里的 ProviderSet  中的 Provider  函数只能是当前目录下创建的 Provider 函数
    • 例如可能存在 usecase 和 repo 都依赖 config 如果 repo 创建一个 ProviderSet 包含 NewConfig ,usecase 也来一个,就会导致在 wire .  生成代码的时候报错,因为有冲突,同一个组件有两个 Provider

总结

本文详细的介绍了 wire 的使用方法和一些存在的坑,避免大家重复踩坑,同时结合上一篇文章当中的项目结构给出了一种实践方式。依赖注入这个东西如果只是一个比较简单的应用并且这个应用的开发同学比较少,可以不用引入,引入依赖注入的框架还是会带来一些复杂性和学习成本,但是如果这个项目有很多同学在协作开发,并且部门要求的依赖组件比较多的时候还是需要引入的,随着项目代码的膨胀会导致后面依赖管理的管理越来越复杂,如果想要做一点点重构会带来很多麻烦。

参考文献

  1. Go 进阶训练营-极客时间
  2. GitHub - uber-go/dig: A reflection based dependency injection toolkit for Go.
  3. GitHub - google/wire: Compile-time Dependency Injection for Go
  4. https://medium.com/@dche423/master-wire-cn-d57de86caa1b
  5. Golang 依赖注入框架 wire 全攻略
  6. Compile-time Dependency Injection With Go Cloud’s Wire - The Go Blog
  7. https://github.com/golang/go/wiki/CodeReviewComments#interfaces

关注我获取更新

看到这里了还不关注点赞走一波

  • 博客 可以订阅 RSS,也可以点击首页通过 webpush 订阅浏览器消息通知
  • Github Follow me
  • 知乎 关注账号,顺便点个 👍
  • 开发者头条 订阅订阅号,顺便点个 👍

关注我获取更新

猜你喜欢