如何在Golang中进行依赖注入的最佳方式?

8
我是Golang的新手,正在开发一个小型库,需要在代码的某个时刻为不同的子包/方法调用获取数据库连接。我想知道如何管理它?
例如,如果我成功地拥有了一个Web服务器,它可以使用处理程序(handler)工作,那么我该如何在此函数内部获取这个连接?它可以与另一个进程一起使用、简单的方法调用或MVC模型吗?
我不想使用全局变量,因为对我来说这是一种糟糕的实践方式,除非这是一种非常特殊的方式(或者以某种方式很棘手)。
我阅读了许多不同网站上的写作,但仍然想听听不同意见和经验。
感谢您的时间!

有许多好的软件包可用。但是一个简单的工厂方法映射也可以胜任(虽然不是所有情况都适用)。 - Kaveh Shahbazian
2
一个库、框架,甚至是工厂地图几乎肯定会使事情过于复杂化。依赖注入只意味着如果Foo需要Bar,Foo不会创建Bar,而是从创建/调用Foo的任何地方接收Bar。 - Adrian
@KavehShahbazian 是的,我尝试使用map[string]interface{}实现了一个简单的版本,但我认为我需要更多的练习来理解一些要求,比如注入器/类型。 - Vincent
@Adrian 我同意你的看法 :) 但是找到一个好的方法/过程来注入它们会很好。目前我找到了一种方法,就像创建一个上下文,将我的实例存储在其中,然后将这个上下文提供给需要它的结构体,但我并不是很喜欢这种方式。 - Vincent
根据我的经验,context.Context.Value() 最多只能算是一种疾病。我们应该避免使用它。(在我看来)context.Context 是实现取消层次结构的最佳方式(与 sync.WaitGroup 结合使用以进行确认)。context.Context 的不可变性质是一件好事。值部分应该仅在请求范围内使用(我也从未将其用于此目的)。 - Kaveh Shahbazian
这个回答是否解答了您的问题?在Golang中有更好的依赖注入模式吗? - Michael Freidgeim
5个回答

3

创建一个结构体,代表资源,我们称之为Cart。在这个结构体中添加get和post方法。这些方法应该是http处理程序。在主函数中使用数据库接口创建该结构体的一个实例。在路由中调用Cart.get。现在,在get方法中你可以访问数据库接口。

这只是为了测试注入的一个想法,并不是一个可工作的示例。

type storage interface {
    PrepareContext(context.Context, string) (*sql.Stmt, error)
}

func main() {
    db, _ := sql.Open("mysql", `queryString`)
    http.HandleFunc("/", Cart{db}.get)
    http.ListenAndServe(":8080", nil)
}

type Cart struct {
    storage
}

func (crt Cart) get(w http.ResponseWriter, r *http.Request) {
    q, _ := crt.PrepareContext(context.Background(), `select *`)
    fmt.Println(q.Exec())
}

/////////Test
type testDB struct{}

func (c testDB) PrepareContext(context.Context, string) (*sql.Stmt, error) {
    return nil, nil
}
func TestGet(t *testing.T) {
    db := testDB{}
    _ = Cart{db}

    //http test here
}

谢谢你的回答,我还没有使用过“Context”,所以即使有文档和示例,我现在仍然感到迷茫。 我也读了这篇文章,它说不要滥用Context来存储值,https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39,但是由于我不熟悉,我想现在我会使用简单的方法,并继续练习这门语言 :) - Vincent
1
应该是crt.storage.PrepareContext,而不是crt.PrepareContext。 - Michael Freidgeim

1
你可以尝试使用Hiboot,它是一个支持开箱即用依赖注入的Web/Cli应用程序框架。

文档

// HelloService is a simple service interface, with interface, we can mock a fake service in unit test
type HelloService interface {
    SayHello(name string) string
}

type helloServiceImpl struct {
}

func init() {
    // Register Rest Controller through constructor newHelloController
    // Register Service through constructor newHelloService
    app.Register(newHelloController, newHelloService)
}

// please note that the return type name of the constructor HelloService,
// hiboot will instantiate a instance named helloService for dependency injection
func newHelloService() HelloService {
    return &helloServiceImpl{}
}

// SayHello is a service method implementation
func (s *helloServiceImpl) SayHello(name string) string {
    return "Hello" + name
}

// PATH: /login
type helloController struct {
    web.Controller
    helloService HelloService
}

// newHelloController inject helloService through the argument helloService HelloService on constructor
func newHelloController(helloService HelloService) *helloController {
    return &helloController{
        helloService: helloService,
    }
}

// Get /
// The first word of method name is the http method GET
func (c *helloController) Get(name string) string {
    return c.helloService.SayHello(name)
}

1
我建议使用 Dargo,它是一种类似于Java CDI和/或JSR-330的注入引擎。它使用结构注释,并使用反射或创建函数执行注入。它支持不同的服务生命周期,包括Singleton(仅创建一次,惰性地),PerLookup(每次注入或查找时创建),Immediate(仅创建一次,急切地)和DargoContext(与context.Context的生命周期绑定)。

0

谢谢,我会看一下这个的 :) - Vincent

0

对于一个IoC容器,您可以尝试使用这个包: https://github.com/golobby/container

单例绑定的示例:

container.Singleton(func() Database {
  return &MySQL{}
})

解决示例:

var db Database
container.Make(&db)

正如您所看到的,它非常容易使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接