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

7 回复

我不会使用全局配置,它会限制测试,尤其是并行测试。

更多关于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,
    )
}

依赖注入的优势:

  1. 可测试性:可以轻松注入mock配置进行单元测试
  2. 明确依赖:函数签名清晰展示了所有依赖
  3. 并发安全:避免全局状态在多goroutine中的竞争条件
  4. 松耦合:组件不依赖具体的全局变量实现

全局配置的问题:

  1. 测试困难:需要重置全局状态或使用全局锁
  2. 隐藏依赖:函数内部依赖不明确
  3. 竞争条件:多goroutine访问时需要额外同步
  4. 紧耦合:组件与具体实现绑定

你的第一种方式(依赖注入)更符合Go语言的惯用做法和工程最佳实践。

回到顶部