Golang中依赖注入和导入应用配置哪个更好?
Golang中依赖注入和导入应用配置哪个更好? 你好,
我目前将应用程序配置存储在一个结构体中(在应用程序启动时从 .env 文件创建),并且只将相关部分依赖注入到需要的地方。请看下面一个简单的例子。
// 假设 cnf 是 config.go 中的一个多维结构体。
// cmd/main.go
whoAreYou(cnf.App.Name, cnf.Env, cnf.Build.Ver)
// internal/hello.go
func whoAreYou(name, env string, ver int) string {
return fmt.Sprintf("%s %s %d", name, env, ver)
}
我在想,这真的是 Go 语言惯用的工作方式吗?我的意思是,是否不如将整个配置存储在一个变量中,并在需要的地方直接访问/导入它,就像我们通常使用 log 包那样?那么示例将变为如下所示。
// 假设 Cnf 是 config.go 中存储为变量的多维结构体。
// cmd/main.go
whoAreYou()
// internal/hello.go
inport "config"
func whoAreYou() string {
return fmt.Sprintf("%s %s %d", config.Cnf.App.Name, config.Cnf.Env, config.Cnf.Build.Ver)
}
谢谢
更多关于Golang中依赖注入和导入应用配置哪个更好?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我不会使用全局配置,它会限制测试,尤其是并行测试。
更多关于Golang中依赖注入和导入应用配置哪个更好?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
GoingToGo:
这真的是 Go 语言惯用的工作方式吗?
从技术上讲,在一个包内部,你可以认为这种技术是惯用的。但实际上,如果你的包只对这个应用程序有用,那么使用全局变量来存储配置会更清晰、更容易。
对于应用程序版本、名称这类变量来说确实如此,但对于库/服务依赖则不然。我推测原帖作者想知道如何在函数或包内使用这些依赖。
并且只在需要的地方注入相关的依赖
在阅读了一些帖子以及这个之后,我决定坚持当前的实现,它没有为配置使用全局变量。
配置包含应用程序特定的变量,因此它们仅与应用程序本身相关。应用程序中的所有Go文件都不会与任何其他外部应用程序或包共享。这就像你口袋里的钱,那笔钱只供你自己花。除非有扒手(即bug),否则没有人应该把手伸进你的口袋。
我不会使用全局配置,它会限制测试,特别是并行测试。
假设应用程序遵循12要素原则使用环境变量,并且如果你担心测试问题,你总是可以在测试中手动设置环境变量,并让你的测试用例期望它需要的任何值。因此,我认为测试不足以成为阻止实现全局配置的理由,这完全是应用程序特定的事情。
在Go中,依赖注入通常比全局配置导入更符合惯用做法。依赖注入提供了更好的可测试性、明确的依赖关系和松耦合。以下是两种方式的对比和示例:
依赖注入方式(推荐)
// config/config.go
package config
type Config struct {
App AppConfig
Env string
Build BuildConfig
}
type AppConfig struct {
Name string
}
type BuildConfig struct {
Ver int
}
func Load() (*Config, error) {
// 从.env文件加载配置
return &Config{
App: AppConfig{Name: "MyApp"},
Env: "production",
Build: BuildConfig{Ver: 1},
}, nil
}
// internal/hello.go
package internal
import "fmt"
type HelloService struct {
appName string
env string
version int
}
func NewHelloService(appName, env string, version int) *HelloService {
return &HelloService{
appName: appName,
env: env,
version: version,
}
}
func (h *HelloService) WhoAreYou() string {
return fmt.Sprintf("%s %s %d", h.appName, h.env, h.version)
}
// cmd/main.go
package main
import (
"config"
"internal"
)
func main() {
cnf, err := config.Load()
if err != nil {
panic(err)
}
helloService := internal.NewHelloService(
cnf.App.Name,
cnf.Env,
cnf.Build.Ver,
)
result := helloService.WhoAreYou()
println(result)
}
使用接口增强可测试性
// config/config.go
package config
type Config struct {
App AppConfig
Env string
Build BuildConfig
}
// internal/hello.go
package internal
import "fmt"
type ConfigProvider interface {
GetAppName() string
GetEnv() string
GetVersion() int
}
type HelloService struct {
config ConfigProvider
}
func NewHelloService(cfg ConfigProvider) *HelloService {
return &HelloService{config: cfg}
}
func (h *HelloService) WhoAreYou() string {
return fmt.Sprintf("%s %s %d",
h.config.GetAppName(),
h.config.GetEnv(),
h.config.GetVersion(),
)
}
// 测试时可以使用mock
type MockConfig struct{}
func (m *MockConfig) GetAppName() string { return "TestApp" }
func (m *MockConfig) GetEnv() string { return "test" }
func (m *MockConfig) GetVersion() int { return 999 }
全局配置方式(不推荐)
// config/config.go
package config
import "sync"
var (
once sync.Once
Cnf *Config
)
func Init() error {
var err error
once.Do(func() {
Cnf = &Config{
App: AppConfig{Name: "MyApp"},
Env: "production",
Build: BuildConfig{Ver: 1},
}
})
return err
}
// internal/hello.go
package internal
import (
"config"
"fmt"
)
func WhoAreYou() string {
// 直接访问全局变量
return fmt.Sprintf("%s %s %d",
config.Cnf.App.Name,
config.Cnf.Env,
config.Cnf.Build.Ver,
)
}
依赖注入的优势:
- 可测试性:可以轻松注入mock配置进行单元测试
- 明确依赖:函数签名清晰展示了所有依赖
- 并发安全:避免全局状态在多goroutine中的竞争条件
- 松耦合:组件不依赖具体的全局变量实现
全局配置的问题:
- 测试困难:需要重置全局状态或使用全局锁
- 隐藏依赖:函数内部依赖不明确
- 竞争条件:多goroutine访问时需要额外同步
- 紧耦合:组件与具体实现绑定
你的第一种方式(依赖注入)更符合Go语言的惯用做法和工程最佳实践。

