最近打算把公司的项目重构为使用依赖注入初始化所有服务(现在是每次需要某个依赖就内部 New 一个), 想找一些开源项目作为例子。但是简单找了找都是自动/手动进行依赖注入的,大家有没有见过 没有 使用依赖注入的:
1
kneo 14 天前 via Android
一会儿有,一会儿没有,一会儿有没有。理解不了你的问题。
|
2
Nitroethane 14 天前
看看这个,用 github.com/google/wire 实现的依赖注入: https://github.com/aquasecurity/trivy
|
3
matrix1010 OP 注意我问的是 没有 使用依赖注入,因为使用依赖注入的太容易找。但调研肯定有和没有都包括才比较合适。另外应当是有一定复杂度的项目比如 grafana
|
4
aloxaf 14 天前
第一眼也看成问「有哪些用了依赖注入的项目」
你都知道这种说法容易让人看错,为什么不换成更清晰的描述呢…… |
5
matrix1010 OP @aloxaf no, 我不知道 "这种说法容易让人看错", 只是可能大部分人的常识是"依赖注入用的很少,所以提问肯定是问哪些项目使用了依赖注入",基于这种常识无论怎么写都很容易看错
|
6
bthulu 14 天前
都用 go 了, 就别用依赖注入了吧, 这一点都不 go.
|
7
V2April 14 天前 7
大家有没有见过没有使用依赖注入的复杂 Go 开源项目 ×
大家有没有见过不使用依赖注入的复杂 Go 开源项目 √ |
8
nikenidage1 14 天前
from GTP-4o
可以尝试改写为更加简洁且易于理解的表述方式,例如: “大家见过哪些复杂的 Go 开源项目没有使用依赖注入吗?” (直接以提问方式表达,语气更自然) “有没有人见过不使用依赖注入的复杂 Go 开源项目?” (改用“有没有人”开头,显得更口语化) “大家有没有见过复杂的 Go 开源项目在代码中完全不用依赖注入?” (强调“完全不用”,让语义更明确) “复杂的 Go 开源项目中,有没有完全不使用依赖注入的例子?” (将句子结构调整为陈述+提问,表达更清晰) “大家知道哪些复杂的 Go 开源项目没有用依赖注入?” (以“知道哪些”引导,强调寻找具体例子) 以上改写都可以根据具体语境选择使用,重点是让问题更明确直观,同时保持语气自然流畅。 |
9
matrix1010 OP @bthulu 这就没意思了兄弟,来点干货不要输出情绪。不 Go 为啥 wire, fx, samber/do 会存在
|
10
xiuming 14 天前
@matrix1010 就是有人新语言 用旧思想写代码
|
11
fgwmlhdkkkw 14 天前
整个全局指针不就行了吗……
|
12
sophos 14 天前
|
13
rrfeng 14 天前 via Android
k8s 有依赖注入吗?
|
14
xiuming 14 天前
还要是理解 go 语言编程思想,Java 有 Java 编程思想没问题,可别把别的语言编程思想套到另一个语言,会很别扭很难受的。
|
15
sophos 14 天前
哈哈哈,审错题了,不好意思
|
16
kuanat 14 天前 8
我觉得需要重新描述一下,区分依赖注入和依赖注入框架。对 Go 来说,依赖注入几乎无处不在,但没有使用任何依赖注入框架的必要。
一时半会想不到特别合适的开源项目,我就尝试描述一下做法。正文里提到了依赖注入的两个场景,一个是被动初始化,另一个是单元测试。这两个都可以用接口 Interface 来完成,方法也是一样的。 标准库里有非常多传递实例的写法,比如传一个 *http.Client 这样。如果需要多个不同组件按照特定顺序初始化,可以直接用 struct embedding 把各个实例封装起来,然后就可以传递 struct 了。 因为我的组里有很多其他背景的开发者转型而来,对于传递 struct 的做法不认可,主要的理由是他们都有要初始化几十个依赖的经历。我对此的观点是,某个组件依赖几十个其他组件这件事本身就有问题,即便是真的有很多依赖,也只是依赖其中非常小的功能而非全部。我个人认为造成这种结果的原因是用基于 class 实现的继承去套用 Go ,而 Go 的做法是完全不实现继承功能,通过组合来抽象 OO 。 第二个需求单元测试需要对代码做调整,或者说对编程思路做调整。之前我在别的 Go 语言讨论帖子里反复引用过 Accept interfaces 这个说法,如果将调用方的代码依赖从实例改成接口,就可以非常简单地实现 mocking 来做单元测试。 因为调用方无须关心调用的是哪个实例,只要这个实例实现了特定的接口即可。将调用方的方法改写为接收接口,就可以用任意的实现来 mocking 对应的功能。 如果把接口当作继承来用,那自然就会觉得依赖是个复杂的事情,因为这个接口会越来越大,最终变成了模块之间的强耦合。实际上 Go 的思维里,接口越小越好,这里的思想是组合而非继承。一个非常小的接口是很容易 mocking 的。 另外如果观察一下 github 上 Go 的 DI/mocking 相关的项目也能发现一个趋势,相比其他语言这两种框架几乎都不流行。因为真的没有必要。不过反过来说,接口就是依赖注入。 |
17
houshuu 14 天前 2
公司内部有不少,但是没有外网链接。体感 用 DI : 不用 DI = 7:3 的样子。
如果业务逻辑非常简单,依赖非常清晰,没有什么分岔的话,其实用不用 dig 写起来差不多的。个人经验来说依赖注入在 Go 大部分情况下都是伪需求或者说提升编码效率的小工具,开始的时候没用的话不必要强上。要跨组件公用的话,直接暴露一个变量或者一个 getter ,内部用 once 啥的初始化一下就行。 单元测试要补强的话,在写的时候就一定要习惯用 interface 来声明组件,方便之后 mock 。(当然另一个方面来说,Interface 写多了,用 DI 自动串起来还是挺方便的。)说实话每次写单元测试都感觉挺不 Go 的,还是 Java 那套思路。但是回头想想,非常多单元测试实际上没有任何作用,只是单纯提升 coverage 而已。现在我比较喜欢把核心逻辑去掉各种 side effect 抽到某个函数里,然后对函数做比较多的 UT ,其他 side effect 直接交给后面的 e2e 什么的来验证。还是要具体情况具体分析,避免无效工作。 |
18
matrix1010 OP @kuanat 几十个可能不多,但 初始化十几个个依赖 还是很容易出现的,特别是复杂度很高,多人协作,质量管理不太严格的大型项目上。只能接受而没法改变的情况下依赖注入框架就很方便。interface 方便单元测试 mock 是肯定的,我做的第一轮重构就是这个
|
19
matrix1010 OP @houshuu 其实只要你把依赖传入就是 DI ,不用 DI 的话就只能每次需要就 New 一个。在多人协作的情况下看似没用的单元测试有可能会在你意想不到的地方起作用
|
20
Jinnrry 14 天前 via iPhone 11
1.我是 java 开发,曾经写了 2 年多的 java 项目
2.我是 go 开发,目前写了 5 年多的 go 项目 3.我接手过使用依赖注入的 go 项目,(就是楼上提到的 wire ) 我们结论是,别总是用老思想写新代码,代码里面写个 new 有啥问题吗?既不影响性能又不影响可读性,搞个依赖注入,查问题的时候一脸懵逼,这函数哪里调用的,那对象哪里创建的。 我除了 java 和 go ,还写了几年 python ,php ,为啥其他语言都没有大面积流行依赖注入这一套?我觉得这足以说明这玩意没那么好。 虽然我以前也是写 java 的,但是我最怕的就是 go 项目打开一股 java 味,都换新语言了,学学新语言的主流编程方式吧,真别守着老一套了 |
21
lifei6671 14 天前
@matrix1010 #5 建议你表达清楚你的需求,我看到你的这段文字也是感觉写的乱七八糟的。
|
22
Orenoid 14 天前
部分评论让我感觉相当迷惑,依赖注入啥时候和 Java 绑定上了,这不是一个很常用的设计思想吗?
用来解耦、提升应用可扩展性或者提升可测试性都是很好用的一个设计规范,甚至在业务层面都可以用。 |
23
matrix1010 OP @Jinnrry 建议你去 https://www.zhihu.com/question/425602814/answer/2930203878 讨论 new 是不是合适,不要武断的说依赖注入==java 味。或者找个 grafana 的 service 比如 https://github.com/grafana/grafana/blob/main/pkg/services/guardian/accesscontrol_guardian.go 看看如果不用依赖注该怎么写怎么测
|
25
FarmerChillax 14 天前 2
纠正一个点,依赖注入个人认为是一种编程思想,它在 Go 内部包中都无处不在,你问的应该是「依赖注入框架」而不是「依赖注入」。
|
26
FarmerChillax 14 天前
|
27
rickiey 14 天前
我还真见过,github.com/filecoin-project/lotus ,你看看它的启动步骤 fx 注入的,当时看吐了
|
28
ryalu 14 天前
traefik 好像没用吧,不知道符不符合你所说的项目 https://github.com/traefik/traefik
|
29
matrix1010 OP @FarmerChillax 我问的恰恰正是[依赖注入],假设 FooService 和 BarService 都依赖 BaseService ,但 NewFooService(...)和 NewBarService(...)都各自调用 NewBaseService()一遍而不是先 NewBaseService 再分别传入,这个应当不能说用了依赖注入。当然这一步可以用依赖注入框架自动完成
|
30
Immortal 14 天前
OP 有点听不进去...
|
31
brookegas 14 天前
Go 语言的官方教程里,根本没有提到「依赖注入」「编程思想」
等学会使用 Go 的接口编程,就会觉得这种语言自带的简单特性,能让你彻底远离屎山堆砌、臭不可闻的「依赖注入框架」 |
32
matrix1010 OP |
33
lysShub 14 天前
@matrix1010 你说的这新建 Base 对象和复用 Base 对象是完全不同的,不能直接改成复用的吧
|
34
panda1001 14 天前
在这问就是个寂寞,自己动手
dependency injection language:Go path:go.mod go.uber.org/fx path:go.mod github.com/google/wire path:go.mod github.com/samber/do |
35
zjsxwc 14 天前 via Android 1
话说回来,go 语言因为有了每个文件的 init 方法,于是可以在 init 方法里对全局变量进行“黑魔法”,
于是对与类似 java 的依赖注入容器的需求就不大了, 其实 go 就是在 init 里干了 java 的依赖注入容器做的事情。 类似的 ruby 因为有了猴子钩子方法,也可以实现各种黑魔法,于是 ruby 对于类似 java 的依赖注入容器需求也不大,因为猴子方法也能实现各种黑魔法。 |
36
aloxaf 14 天前
@rickiey 竟然遇到矿友
给围观群众补个链接 https://github.com/filecoin-project/lotus/blob/master/node/builder_chain.go 看着挺优雅的,但是刚接触的时候真的难受,IDE 跳转完全是废的,根本搞不懂参数究竟是从哪里来的,究竟是哪一个 |
37
zjsxwc 14 天前 via Android
@zjsxwc
所以只有语言限制比较大的 java ,才会有纯血的依赖注入容器。 其他的比如 qt cpp 是通过 moc 编译器预处理来实现黑魔法注入。 而 php 、js 的早期的依赖注入比如 symfony1.x 、requirejs 都是通过绑定 字符串 与 对象 来实现的注入,现在确实 php 、js 抄了 java 的依赖注入容器方式。 |
38
aw2350 14 天前
用了所谓的 gowire 的代码,看起了十分恶心,到现在我都没有去 了解 go-wire 这个项目的具体用法。
|
39
BBCCBB 14 天前
依赖注入好东西, 但在 go 里用起来很麻烦..
|
41
grzhan 14 天前 3
就我所知绝大部分 Golang 开发的偏基础设施与中间件的大型项目( Kubernetes 、VictoriaMetrics……),以及包括 Go 语言 Runtime/Compiler 本身都没有使用 DI 框架。
所有的依赖都是自己手动处理的,通过一系列的 NewXXX 函数以及工厂方法。很多 struct 可能就是全局的,在 go 启动时候就创建起来,然后在 package 范围内可见。在一些场景里可能会用一个比较大的叫做 "xxxCtx" 的上下文 struct (注意这个只是 struct 起了个名字,而与 context 标准库无关),用来维护传进来的 dependencies ,供这个上下文的方法使用,对于内部的 struct 有点像一个 DI 容器吧,但这个 "DI 容器"的依赖本质还是自己维护的。 个人觉得手动管理依赖并不是邪恶的,Go 的隐式接口实现可以做到不少代码较好的解耦(虽然隐式接口实现有单独的槽点),Go 项目里这些 struct 的设计也大部分实现了依赖注入模式(构造函数注入、方法注入),足以支撑复杂的项目。 然后 Go 项目很少使用 DI 框架,这样大型项目手动维护一个 "Unit" 几十上百个依赖是不是太麻烦了?事实在于,Golang 这些项目的开发者不少都认为一个"Unit" 不应该和所有依赖进行交互,而应该专注于具体的事情,去看这些项目的 struct ,他们的依赖基本上也是可控的,大部分都是最最多十几个这样了。如果一个组件依赖过多,可能需要思考是不是该重构了。 当然对于大规模多人的业务需求迭代频繁的大型 Web 应用项目,我觉得依赖注入 DI 框架还是有价值的,因为这种迭代变化频繁的项目可能会有很多业务驱动的 Service ,用 DI 框架能够提升开发效率,让搬砖的 IT 民工(比如我)不需要关注很多细节,但另一方面微服务模式一定程度上也降低了单体的复杂度,缓解了此类问题 |
42
securityCoding 14 天前
go 可读性这么好的语言手动构造器注入吧
|
43
CLMan 14 天前 1
虽然很多人答非所问,但是也侧面说明了:楼主为啥会问这么奇怪的问题,感觉就像熟读技术书籍但没写过一天代码的怪人。
|
44
Meld 14 天前
>>> 大家有没有见过 没有 使用依赖注入的
Has anyone seen anything that does not use dependency injection? |
45
grzhan 14 天前
感觉我确实答非所问了( x
不过要找一个**真正没有使用依赖注入模式**的 Go 复杂项目感觉还挺难的 |
46
gl3081 14 天前
依赖注入对于模块化开发来说非常灵活方便,尤其是微服务架构,具体实践可以参考我的开源项目: https://github.com/moke-game/platform
|
48
RayR 14 天前
@zjsxwc #35 忘记官方是不是有过建议不要用 init (也有可能是某个博客的观点)。实践中多个开发维护一个项目出现 import 顺序被修改造成初始化错误的情况。
|
49
lizy0329 14 天前 1
Go 是函数优先的强类型语言,不需要 Java 的正畸辅助道具
|
50
mrjnamei 14 天前
还是不要的好。目前在写的项目用的是 fx ,new 一个 service 十几个依赖的情况写的非常难受:
注入: fx.Provide(NewApplicationService) 构造: func NewApplicationService ( depend1 SomeService1, depend2 SomeService2, depend3 SomeService3, depend4 SomeService4, depend5 SomeService5, depend6 SomeService6, depend7 SomeService7, depend8 SomeService8, ) *ApplicationService { return &ApplicationService { depend1: depend1, ..... } } 不使用这种传参的注入方法可以直接 New, 写起来舒服一些. |
51
matrix1010 OP @mrjnamei 其实这个 NewApplicationService ,完全可以把 struct 扔给任何 AI 来写. 这种极简单场景我估计任何 AI 都能达到几乎 100%准确率
|
52
xz410236056 14 天前
@matrix1010 #23 你发的这不都是 java 的吗(包括该问题下的其他讨论),层主说的就是你别用 java 的思想写 go 啊
|
53
godlovesxcjtest 14 天前
|
54
james122333 14 天前 via Android
Go 有依赖注入 但我也不喜欢依赖注入
首先依赖注入是非常割裂的 在 java 上就很少看到集中一处统一管理的 搭配注解也更难釐清依赖关系 外加很多东西根本没有善用命名空间 很多第三方库都是乱七八糟 一个命名空间一个 package 负责一项功能更利于管理 整个架构就会很漂亮 也更好做複杂功能 方便掌控全局代码 依赖注入本身就很有阴谋味 职场的效应很明显 不论是挖坑给别人还是代码只有自己能看懂都是暗黑优势 但还是有不少人只是想安静的搬砖 |
55
james122333 14 天前 via Android
|
56
gogogo1203 14 天前
想吐就看 mattermost , 想看清晰架构 就看 https://github.com/ardanlabs/service. 我 n 年没有写 go 了,不过这两个 repo 还是牛的。
|
58
pkoukk 14 天前
不用框架,手动注入
设计合理的话问题不大,AI 能写 99%,约等于自动注入了 |
60
james122333 14 天前 via Android
|
61
FarmerChillax 14 天前
@matrix1010 如果你问的是「依赖注入」,那么基本可以说没有任何一个 Go 项目不使用依赖注入。因为依赖注入遍布了基本所有常用的基础包中。
|
62
james122333 14 天前 via Android
|
64
povsister 13 天前 via iPhone
wire 先不说,fx 已经是我这边准备放进黑名单的东西。除了写 php 的那帮人转 go 用的很舒服外,正常人看到 fx 这玩意都觉得逆天
|
65
yplam 13 天前 via Android
我们的做法是
1. 基础包参考标准库 http 的实现,在容器初始化前配置, 2. 容器实例化代码手写,除了基础包的以外全部功能都要按依赖注入的模式实例化, 3. 其他更上层的包就直接通过容器拿 4. 用独立包定义 interface ,避免循环依赖 |
67
sophos 13 天前
@james122333 关于依赖关系这个痛点问题,这个依赖注入框架就特别支持了生成依赖拓扑 :-)
跑 kod callgraph 命令,就能生成指定二进制程序内部的模块依赖拓扑 https://github.com/go-kod/kod-mono?tab=readme-ov-file#callgraph-auto-generated 顺便补充一下,依赖注入并不局限在某个语言,而是一种编程范式。 依赖注入框架则是按照约定的规则,自动将依赖注入到各个模块,只是局限于 Go 语言的表达力,目前主流的框架还不够好用。 我在 https://github.com/go-kod/kod 中基于代码生成和泛型,简化了自动注入的约定规则,同时规范了 interface 声明和 mock 生成,对于频繁迭代的业务项目来说,应该可以极大提高开发效率 :-) 更进一步,还能支持面向切面编程,很方便自己实现各种 Interceptor ,引入类似链路追踪、监控、熔断、限速之类的功能,业务无代码侵入且不局限于某种特定框架 |
68
james122333 13 天前 via Android
|
69
zjsxwc 13 天前 via Android
@RayR 对,我说的就是这个规则,go 规范里面给出了按照包依赖关系深度优先执行 init ,所以只要负责自己包内部的 init 依赖注入就完事了,
你为什么要去干涉别的包的 init ? |
70
james122333 13 天前 via Android
|
71
lt0136 13 天前 via Android
https://github.com/temporalio/temporal
用了 uber/fx ,有完善的测试 |
73
changz 13 天前 via Android
不喜欢依赖注入就手工管理呗,但评论区里说的通过 init 方法全局单例在大部分时候最终都会变成一坨翔
|
74
james122333 13 天前 via Android
@changz
我都没在用 init 不过 init 确实在某些情况不错 我更常直接 var Abc = func() XXX { ... return XXX }() var Bcd = func() YYY { ... return YYY }() |
75
nuffin 13 天前
@matrix1010 你既然知道,又想知道答案,为什么不描述清晰一些呢?最简单的 *不* 也行啊
|
76
eijnix 13 天前
业务复杂了之后还是很适合用依赖注入的,我们公司内部从头开发了一套依赖注入框架,我们小组之前用的是 facebook 的那个 inject
|
77
nuffin 13 天前
仔细的看了一遍回复(当然有 link 的我还没仔细去看),我作为一个 C 程序员……想问问,依赖注入是啥?单例就全局变量,事务相关就 *alloc 创建一个,也类似写个 NewXxx 方法。其实主要还是看业务导向,和实现复杂度的匹配。
我觉得 OP 想清楚匹配度,作为评估依据就可以了,市面上用的多的方案未必是适合你们项目的。 |
78
lesismal 13 天前
十几年来研究了好几次什么是依赖注入, 今天又研究了下, 至今没搞明白到底什么是依赖注入...
|
79
matrix1010 OP @lesismal 依赖注入其实是符合直觉的,因此专门尝试去理解反而比较困难。前面有人说的在 init 里初始化服务就属于非依赖注入的做法,但这么做的开源项目肯定是极少数,因为复杂的大型开源项目肯定是多人协同开发,那项目结构也要达成共识,而多人共识最有可能的就是使用认可度最高的依赖注入模式
|
80
rickiey 13 天前
@aloxaf #36 哈哈,缘分呐,不过我刚已经从 FIL 矿商离职了,入交易所了,不得不说挖矿确实好,躺着靠机器赚钱,可惜啊,都没有矿能一直好好挖下去,filecoin 已经算是比较好的了
|
81
changz 13 天前 via Android
@james122333 这个跟 init 有什么本质区别
|
82
wupher 13 天前
面向绩效编程哈
|
83
mrjnamei 13 天前
@matrix1010 AI 肯定没问题,我不太习惯用 AI ,会和我的 IDE 使用习惯冲突,代码在迭代和维护的过程需要不断的新增依赖才是最烦的,非常容易漏掉。
|
84
me1onsoda 13 天前
你到底想不想要使用依赖注入?
|
85
matrix1010 OP @mrjnamei 这个恰好也是我正在试图避免的问题,初始化 service 肯定是有个 NewXxxService 或者 ProvideXxxService 之类的方法,如果单元测试初始化 service 时也使用这些方法就可以覆盖到。但如果 test 里手动构建 struct: testService := &UserServiceImpl{db: db, ...} 这样就检测不到了
|
86
qloog 13 天前
|
87
uncat 13 天前
当然是 miniflux/v2 啦。
|
88
james122333 13 天前 via Android
|
89
firstep 11 天前
@zjsxwc #36 ,实在看不惯 wire/fx 等用法。我也喜欢使用 init 特性来做初始化。
当然在测试时 init 顺序确实会有包间依赖顺序问题。所以造了个 initer 能够指定顺序,当然只是实验性的满足当前的项目场景。 |
90
rainbowStay 7 天前
@bthulu java 转 go 的新手提问下,为啥 go 不推崇依赖注入呀
|
91
rainbowStay 7 天前
@matrix1010 #19 意思是不是现用现 New 的都算依赖注入? func NewService(repo Repository) *Service {
return &Service{repo: repo} } |
92
rainbowStay 7 天前
@rainbowStay #91 抱歉,莫名奇妙发送出去了,想请问一下像上面这个写法也算依赖注入吗?
|
93
matrix1010 OP @rainbowStay repo 不是你在 NewService 函数内部创建的,而是传入作为参数。完全符合依赖注入的定义
|
94
Kauruus 7 天前
@rainbowStay 不存在 “go 不推崇依赖注入” 的。
|
95
qloog 2 天前
使用 wire 的一个完整性 project: https://github.com/qloog/go-wire-example 示例说清楚了 wire 的大概用法
|