Golang Go语言中大家有没有见过没有使用依赖注入的复杂开源项目
最近打算把公司的项目重构为使用依赖注入初始化所有服务(现在是每次需要某个依赖就内部 New 一个), 想找一些开源项目作为例子。但是简单找了找都是自动/手动进行依赖注入的,大家有没有见过 没有 使用依赖注入的:
- 有一定复杂度/单元测试完善的 Go 开源项目
- 有一定复杂度/没有单元测试的 Go 开源项目(我同时也在完善单元测试)
Golang Go语言中大家有没有见过没有使用依赖注入的复杂开源项目
一会儿有,一会儿没有,一会儿有没有。理解不了你的问题。
更多关于Golang Go语言中大家有没有见过没有使用依赖注入的复杂开源项目的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
看看这个,用 github.com/google/wire 实现的依赖注入: https://github.com/aquasecurity/trivy
注意我问的是 没有 使用依赖注入,因为使用依赖注入的太容易找。但调研肯定有和没有都包括才比较合适。另外应当是有一定复杂度的项目比如 grafana
第一眼也看成问「有哪些用了依赖注入的项目」
你都知道这种说法容易让人看错,为什么不换成更清晰的描述呢……
no, 我不知道 “这种说法容易让人看错”, 只是可能大部分人的常识是"依赖注入用的很少,所以提问肯定是问哪些项目使用了依赖注入",基于这种常识无论怎么写都很容易看错
都用 go 了, 就别用依赖注入了吧, 这一点都不 go.
大家有没有见过没有使用依赖注入的复杂 Go 开源项目 ×
大家有没有见过不使用依赖注入的复杂 Go 开源项目 √
from GTP-4o
可以尝试改写为更加简洁且易于理解的表述方式,例如:
“大家见过哪些复杂的 Go 开源项目没有使用依赖注入吗?”
(直接以提问方式表达,语气更自然)
“有没有人见过不使用依赖注入的复杂 Go 开源项目?”
(改用“有没有人”开头,显得更口语化)
“大家有没有见过复杂的 Go 开源项目在代码中完全不用依赖注入?”
(强调“完全不用”,让语义更明确)
“复杂的 Go 开源项目中,有没有完全不使用依赖注入的例子?”
(将句子结构调整为陈述+提问,表达更清晰)
“大家知道哪些复杂的 Go 开源项目没有用依赖注入?”
(以“知道哪些”引导,强调寻找具体例子)
以上改写都可以根据具体语境选择使用,重点是让问题更明确直观,同时保持语气自然流畅。
这就没意思了兄弟,来点干货不要输出情绪。不 Go 为啥 wire, fx, samber/do 会存在
就是有人新语言 用旧思想写代码
整个全局指针不就行了吗……
k8s 有依赖注入吗?
还要是理解 go 语言编程思想,Java 有 Java 编程思想没问题,可别把别的语言编程思想套到另一个语言,会很别扭很难受的。
哈哈哈,审错题了,不好意思
我觉得需要重新描述一下,区分依赖注入和依赖注入框架。对 Go 来说,依赖注入几乎无处不在,但没有使用任何依赖注入框架的必要。
一时半会想不到特别合适的开源项目,我就尝试描述一下做法。正文里提到了依赖注入的两个场景,一个是被动初始化,另一个是单元测试。这两个都可以用接口 Interface 来完成,方法也是一样的。
标准库里有非常多传递实例的写法,比如传一个 *http.Client 这样。如果需要多个不同组件按照特定顺序初始化,可以直接用 struct embedding 把各个实例封装起来,然后就可以传递 struct 了。
因为我的组里有很多其他背景的开发者转型而来,对于传递 struct 的做法不认可,主要的理由是他们都有要初始化几十个依赖的经历。我对此的观点是,某个组件依赖几十个其他组件这件事本身就有问题,即便是真的有很多依赖,也只是依赖其中非常小的功能而非全部。我个人认为造成这种结果的原因是用基于 class 实现的继承去套用 Go ,而 Go 的做法是完全不实现继承功能,通过组合来抽象 OO 。
第二个需求单元测试需要对代码做调整,或者说对编程思路做调整。之前我在别的 Go 语言讨论帖子里反复引用过 Accept interfaces 这个说法,如果将调用方的代码依赖从实例改成接口,就可以非常简单地实现 mocking 来做单元测试。
因为调用方无须关心调用的是哪个实例,只要这个实例实现了特定的接口即可。将调用方的方法改写为接收接口,就可以用任意的实现来 mocking 对应的功能。
如果把接口当作继承来用,那自然就会觉得依赖是个复杂的事情,因为这个接口会越来越大,最终变成了模块之间的强耦合。实际上 Go 的思维里,接口越小越好,这里的思想是组合而非继承。一个非常小的接口是很容易 mocking 的。
另外如果观察一下 github 上 Go 的 DI/mocking 相关的项目也能发现一个趋势,相比其他语言这两种框架几乎都不流行。因为真的没有必要。不过反过来说,接口就是依赖注入。
几十个可能不多,但 初始化十几个个依赖 还是很容易出现的,特别是复杂度很高,多人协作,质量管理不太严格的大型项目上。只能接受而没法改变的情况下依赖注入框架就很方便。interface 方便单元测试 mock 是肯定的,我做的第一轮重构就是这个
其实只要你把依赖传入就是 DI ,不用 DI 的话就只能每次需要就 New 一个。在多人协作的情况下看似没用的单元测试有可能会在你意想不到的地方起作用
1.我是 java 开发,曾经写了 2 年多的 java 项目
2.我是 go 开发,目前写了 5 年多的 go 项目
3.我接手过使用依赖注入的 go 项目,(就是楼上提到的 wire )
我们结论是,别总是用老思想写新代码,代码里面写个 new 有啥问题吗?既不影响性能又不影响可读性,搞个依赖注入,查问题的时候一脸懵逼,这函数哪里调用的,那对象哪里创建的。
我除了 java 和 go ,还写了几年 python ,php ,为啥其他语言都没有大面积流行依赖注入这一套?我觉得这足以说明这玩意没那么好。
虽然我以前也是写 java 的,但是我最怕的就是 go 项目打开一股 java 味,都换新语言了,学学新语言的主流编程方式吧,真别守着老一套了
建议你去 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 看看如果不用依赖注该怎么写怎么测
你说了我想说的,搞得我无话可说
点赞 👍
我还真见过,github.com/filecoin-project/lotus ,你看看它的启动步骤 fx 注入的,当时看吐了
traefik 好像没用吧,不知道符不符合你所说的项目 https://github.com/traefik/traefik
我问的恰恰正是[依赖注入],假设 FooService 和 BarService 都依赖 BaseService ,但 NewFooService(…)和 NewBarService(…)都各自调用 NewBaseService()一遍而不是先 NewBaseService 再分别传入,这个应当不能说用了依赖注入。当然这一步可以用依赖注入框架自动完成
OP 有点听不进去…
Go 语言的官方教程里,根本没有提到「依赖注入」「编程思想」
等学会使用 Go 的接口编程,就会觉得这种语言自带的简单特性,能让你彻底远离屎山堆砌、臭不可闻的「依赖注入框架」
你说的这新建 Base 对象和复用 Base 对象是完全不同的,不能直接改成复用的吧
在这问就是个寂寞,自己动手
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
话说回来,go 语言因为有了每个文件的 init 方法,于是可以在 init 方法里对全局变量进行“黑魔法”,
于是对与类似 java 的依赖注入容器的需求就不大了,
其实 go 就是在 init 里干了 java 的依赖注入容器做的事情。
类似的 ruby 因为有了猴子钩子方法,也可以实现各种黑魔法,于是 ruby 对于类似 java 的依赖注入容器需求也不大,因为猴子方法也能实现各种黑魔法。
竟然遇到矿友
给围观群众补个链接 https://github.com/filecoin-project/lotus/blob/master/node/builder_chain.go
看着挺优雅的,但是刚接触的时候真的难受,IDE 跳转完全是废的,根本搞不懂参数究竟是从哪里来的,究竟是哪一个
所以只有语言限制比较大的 java ,才会有纯血的依赖注入容器。
其他的比如 qt cpp 是通过 moc 编译器预处理来实现黑魔法注入。
而 php 、js 的早期的依赖注入比如 symfony1.x 、requirejs 都是通过绑定 字符串 与 对象 来实现的注入,现在确实 php 、js 抄了 java 的依赖注入容器方式。
用了所谓的 gowire 的代码,看起了十分恶心,到现在我都没有去 了解 go-wire 这个项目的具体用法。
依赖注入好东西, 但在 go 里用起来很麻烦…
就我所知绝大部分 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 民工(比如我)不需要关注很多细节,但另一方面微服务模式一定程度上也降低了单体的复杂度,缓解了此类问题
go 可读性这么好的语言手动构造器注入吧
虽然很多人答非所问,但是也侧面说明了:楼主为啥会问这么奇怪的问题,感觉就像熟读技术书籍但没写过一天代码的怪人。
>>> 大家有没有见过 没有 使用依赖注入的
Has anyone seen anything that does not use dependency injection?
感觉我确实答非所问了( x
不过要找一个真正没有使用依赖注入模式的 Go 复杂项目感觉还挺难的
依赖注入对于模块化开发来说非常灵活方便,尤其是微服务架构,具体实践可以参考我的开源项目: https://github.com/moke-game/platform
我也没看懂这东西跟 Java 就没啥强关联,还有把依赖注入说成臭不可闻的
Go 是函数优先的强类型语言,不需要 Java 的正畸辅助道具
还是不要的好。目前在写的项目用的是 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, 写起来舒服一些.
其实这个 NewApplicationService ,完全可以把 struct 扔给任何 AI 来写. 这种极简单场景我估计任何 AI 都能达到几乎 100%准确率
Go 有依赖注入 但我也不喜欢依赖注入
首先依赖注入是非常割裂的 在 java 上就很少看到集中一处统一管理的 搭配注解也更难釐清依赖关系 外加很多东西根本没有善用命名空间
很多第三方库都是乱七八糟 一个命名空间一个 package 负责一项功能更利于管理 整个架构就会很漂亮 也更好做複杂功能 方便掌控全局代码
依赖注入本身就很有阴谋味 职场的效应很明显
不论是挖坑给别人还是代码只有自己能看懂都是暗黑优势 但还是有不少人只是想安静的搬砖
命名空间就是在做模块化这种事了
想吐就看 mattermost , 想看清晰架构 就看 https://github.com/ardanlabs/service. 我 n 年没有写 go 了,不过这两个 repo 还是牛的。
依赖注入和依赖注入框架是两回事,不搞依赖注入,你咋做单测啊?
不用框架,手动注入
设计合理的话问题不大,AI 能写 99%,约等于自动注入了
#35 init 从来不建议做初始化依赖这种事情,因为它的顺序难以保证,理论上 init 里应该只有自己 package 内部的东西
命令行参数透过反射呼叫阿
如果你问的是「依赖注入」,那么基本可以说没有任何一个 Go 项目不使用依赖注入。因为依赖注入遍布了基本所有常用的基础包中。
真要在该方法定义参数其实写在方法内就可以了
可能那些人是先通过 Spring 全家桶接触到的这个概念,所以以为依赖注入是 Java 特有的东西
wire 先不说,fx 已经是我这边准备放进黑名单的东西。除了写 php 的那帮人转 go 用的很舒服外,正常人看到 fx 这玩意都觉得逆天
我们的做法是
1. 基础包参考标准库 http 的实现,在容器初始化前配置,
2. 容器实例化代码手写,除了基础包的以外全部功能都要按依赖注入的模式实例化,
3. 其他更上层的包就直接通过容器拿
4. 用独立包定义 interface ,避免循环依赖
总不能把 MonadT RWST 叫做依赖注入吧
关于依赖关系这个痛点问题,这个依赖注入框架就特别支持了生成依赖拓扑 :-)
跑 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 ,引入类似链路追踪、监控、熔断、限速之类的功能,业务无代码侵入且不局限于某种特定框架
这就过于複杂了 还要多余工具
面向切面编程与拦截器都是
显式流程呼叫更为简单 函式切的够整齐与其效果无异
对,我说的就是这个规则,go 规范里面给出了按照包依赖关系深度优先执行 init ,所以只要负责自己包内部的 init 依赖注入就完事了,
你为什么要去干涉别的包的 init ?
看了一下 fx 还更好点 编译器检查总比打错字不知好
https://github.com/temporalio/temporal
用了 uber/fx ,有完善的测试
没认真审题😂忽略
不喜欢依赖注入就手工管理呗,但评论区里说的通过 init 方法全局单例在大部分时候最终都会变成一坨翔
我都没在用 init 不过 init 确实在某些情况不错
我更常直接
var Abc = func() XXX {
…
return XXX
}()
var Bcd = func() YYY {
…
return YYY
}()
你既然知道,又想知道答案,为什么不描述清晰一些呢?最简单的 不 也行啊
业务复杂了之后还是很适合用依赖注入的,我们公司内部从头开发了一套依赖注入框架,我们小组之前用的是 facebook 的那个 inject
仔细的看了一遍回复(当然有 link 的我还没仔细去看),我作为一个 C 程序员……想问问,依赖注入是啥?单例就全局变量,事务相关就 *alloc 创建一个,也类似写个 NewXxx 方法。其实主要还是看业务导向,和实现复杂度的匹配。
我觉得 OP 想清楚匹配度,作为评估依据就可以了,市面上用的多的方案未必是适合你们项目的。
十几年来研究了好几次什么是依赖注入, 今天又研究了下, 至今没搞明白到底什么是依赖注入…
依赖注入其实是符合直觉的,因此专门尝试去理解反而比较困难。前面有人说的在 init 里初始化服务就属于非依赖注入的做法,但这么做的开源项目肯定是极少数,因为复杂的大型开源项目肯定是多人协同开发,那项目结构也要达成共识,而多人共识最有可能的就是使用认可度最高的依赖注入模式
#36 哈哈,缘分呐,不过我刚已经从 FIL 矿商离职了,入交易所了,不得不说挖矿确实好,躺着靠机器赚钱,可惜啊,都没有矿能一直好好挖下去,filecoin 已经算是比较好的了
这个跟 init 有什么本质区别
AI 肯定没问题,我不太习惯用 AI ,会和我的 IDE 使用习惯冲突,代码在迭代和维护的过程需要不断的新增依赖才是最烦的,非常容易漏掉。
这个恰好也是我正在试图避免的问题,初始化 service 肯定是有个 NewXxxService 或者 ProvideXxxService 之类的方法,如果单元测试初始化 service 时也使用这些方法就可以覆盖到。但如果 test 里手动构建 struct: testService := &UserServiceImpl{db: db, …} 这样就检测不到了
当然是 miniflux/v2 啦。
可以多个 init 函数可以四处飘 这个一个对象初始化自己的 意义不同 当然可以滥用 但问题清晰多了 可以避免翔
java 转 go 的新手提问下,为啥 go 不推崇依赖注入呀
#19 意思是不是现用现 New 的都算依赖注入? func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
#91 抱歉,莫名奇妙发送出去了,想请问一下像上面这个写法也算依赖注入吗?
不存在 “go 不推崇依赖注入” 的。
使用 wire 的一个完整性 project: https://github.com/qloog/go-wire-example 示例说清楚了 wire 的大概用法
作为IT领域Go语言方面的专家,对于Go语言中是否使用依赖注入的开源项目有着一定的了解。在Go语言社区和生态系统中,确实存在许多复杂且没有使用依赖注入的开源项目。
Go语言的设计哲学强调简单性和明确性,其内置的接口机制可以实现松耦合,编译时检查和静态类型系统增强了代码的可维护性。这些特性使得Go语言能够在不使用依赖注入框架的情况下,实现组件之间的解耦和代码的灵活性。
例如,Docker、Kubernetes、Etcd、Deis、Flynn等都是知名的Go语言开源项目,它们在没有使用依赖注入的情况下,依然保持了代码的复杂性和高可维护性。这些项目通过Go语言的接口机制和显式依赖管理,实现了组件之间的松耦合和高度可扩展性。
此外,Uber的开源项目如Jaeger和Kraken,以及Google的Go项目等也都没有使用依赖注入框架。这些项目证明了在Go语言中,即使不使用依赖注入,也可以通过其他方式实现代码的解耦和可维护性。
因此,对于Go语言开发者来说,不必过分依赖注入框架,而应充分利用Go语言的接口机制和显式依赖管理的优势,编写出简洁、清晰和易于维护的代码。