英文:
Is there a better dependency injection pattern in golang?
问题
给定这段代码:
package main
import (
"fmt"
)
type datstr string
type Guy interface {
SomeDumbGuy() string
}
func (d *datstr) SomeDumbGuy() string {
return "some guy"
}
func someConsumer(g Guy) {
fmt.Println("Hello, " + g.SomeDumbGuy())
}
func main() {
var d datstr
someConsumer(&d)
}
在main
函数中将组件连接在一起的方式是否正确?在我的代码中,似乎有点过度使用this
关键字。是否有比这更好的常见模式,或者我是不是想得太多了?
英文:
Given this code:
package main
import (
"fmt"
)
type datstr string
type Guy interface {
SomeDumbGuy() string
}
func (d *datstr) SomeDumbGuy() string {
return "some guy"
}
func someConsumer(g Guy) {
fmt.Println("Hello, " + g.SomeDumbGuy())
}
func main() {
var d datstr
someConsumer(&d)
}
Is the wiring of components together that's done in main the right way to wire a dependency together? It seems like I'm over using this a bit in my code. Is there a common pattern better than this, or am I overthinking it?
答案1
得分: 51
最佳实践是不使用依赖注入(DI)库。Go语言旨在成为一种简单易懂的语言。使用DI库/框架会将这一过程抽象化(在某种程度上使依赖注入变得“神奇”)。
英文:
The best practice is not to use a DI library. Go is meant to be a simple language that is easy to follow. A DI library/framework will abstract that away from you (and to some extent make DI magical).
答案2
得分: 41
Google的Wire看起来很有前途。有一些关于它的文章:
英文:
Google's Wire looks promising. There're some articles about it:
答案3
得分: 12
是的,facebookgo inject库允许您使用注入的成员,并为您连接图形。
代码:https://github.com/facebookgo/inject
文档:https://godoc.org/github.com/facebookgo/inject
以下是文档中的代码示例:
package main
import (
"fmt"
"net/http"
"os"
"github.com/facebookgo/inject"
)
// 我们的Awesome Application在我们的虚拟世界中使用两个API来渲染消息。
type HomePlanetRenderApp struct {
// 下面的标签告诉inject库这些字段可以进行注入。它们不指定任何选项,并且将为每个API创建一个单例实例。
NameAPI *NameAPI `inject:""`
PlanetAPI *PlanetAPI `inject:""`
}
func (a *HomePlanetRenderApp) Render(id uint64) string {
return fmt.Sprintf(
"%s来自星球%s。",
a.NameAPI.Name(id),
a.PlanetAPI.Planet(id),
)
}
// 我们的虚拟Name API。
type NameAPI struct {
// 在PlanetAPI中,我们在接口值上添加了标签。
// 这个值不能自动创建(根据定义),因此必须显式地提供给图形。
HTTPTransport http.RoundTripper `inject:""`
}
func (n *NameAPI) Name(id uint64) string {
// 在真实世界中,我们将使用f.HTTPTransport并获取名称
return "Spock"
}
// 我们的虚拟Planet API。
type PlanetAPI struct {
HTTPTransport http.RoundTripper `inject:""`
}
func (p *PlanetAPI) Planet(id uint64) string {
// 在真实世界中,我们将使用f.HTTPTransport并获取星球
return "Vulcan"
}
func main() {
// 通常,一个应用程序将只有一个对象图,您将在main函数中创建并使用它:
var g inject.Graph
// 我们为图形提供了两个“种子”对象,一个是我们希望填充的空HomePlanetRenderApp实例,
// 第二个是我们的DefaultTransport,以满足我们的HTTPTransport依赖关系。
// 我们必须提供DefaultTransport,因为依赖关系是根据http.RoundTripper接口定义的,
// 由于它是一个接口,库无法为其创建实例。
// 相反,它将使用给定的DefaultTransport来满足依赖关系,因为它实现了该接口:
var a HomePlanetRenderApp
err := g.Provide(
&inject.Object{Value: &a},
&inject.Object{Value: http.DefaultTransport},
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// 在这里,Populate调用创建了NameAPI和PlanetAPI的实例,并将它们的HTTPTransport都设置为上面提供的http.DefaultTransport:
if err := g.Populate(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// 对于简单情况,有一个简写API将上述三个调用组合在一起,即inject.Populate:
//
// inject.Populate(&a, http.DefaultTransport)
//
// 上述API显示了底层API,它还允许在更复杂的情况下使用命名实例。
fmt.Println(a.Render(42))
}
希望对你有所帮助!
英文:
Yes, the facebookgo inject library allows you to take your injected members and will wire up the graph for you.
Code: https://github.com/facebookgo/inject
Documentation: https://godoc.org/github.com/facebookgo/inject
Here's a code example from the documentation:
package main
import (
"fmt"
"net/http"
"os"
"github.com/facebookgo/inject"
)
// Our Awesome Application renders a message using two APIs in our fake
// world.
type HomePlanetRenderApp struct {
// The tags below indicate to the inject library that these fields are
// eligible for injection. They do not specify any options, and will
// result in a singleton instance created for each of the APIs.
NameAPI *NameAPI `inject:""`
PlanetAPI *PlanetAPI `inject:""`
}
func (a *HomePlanetRenderApp) Render(id uint64) string {
return fmt.Sprintf(
"%s is from the planet %s.",
a.NameAPI.Name(id),
a.PlanetAPI.Planet(id),
)
}
// Our fake Name API.
type NameAPI struct {
// Here and below in PlanetAPI we add the tag to an interface value.
// This value cannot automatically be created (by definition) and
// hence must be explicitly provided to the graph.
HTTPTransport http.RoundTripper `inject:""`
}
func (n *NameAPI) Name(id uint64) string {
// in the real world we would use f.HTTPTransport and fetch the name
return "Spock"
}
// Our fake Planet API.
type PlanetAPI struct {
HTTPTransport http.RoundTripper `inject:""`
}
func (p *PlanetAPI) Planet(id uint64) string {
// in the real world we would use f.HTTPTransport and fetch the planet
return "Vulcan"
}
func main() {
// Typically an application will have exactly one object graph, and
// you will create it and use it within a main function:
var g inject.Graph
// We provide our graph two "seed" objects, one our empty
// HomePlanetRenderApp instance which we're hoping to get filled out,
// and second our DefaultTransport to satisfy our HTTPTransport
// dependency. We have to provide the DefaultTransport because the
// dependency is defined in terms of the http.RoundTripper interface,
// and since it is an interface the library cannot create an instance
// for it. Instead it will use the given DefaultTransport to satisfy
// the dependency since it implements the interface:
var a HomePlanetRenderApp
err := g.Provide(
&inject.Object{Value: &a},
&inject.Object{Value: http.DefaultTransport},
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Here the Populate call is creating instances of NameAPI &
// PlanetAPI, and setting the HTTPTransport on both to the
// http.DefaultTransport provided above:
if err := g.Populate(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// There is a shorthand API for the simple case which combines the
// three calls above is available as inject.Populate:
//
// inject.Populate(&a, http.DefaultTransport)
//
// The above API shows the underlying API which also allows the use of
// named instances for more complex scenarios.
fmt.Println(a.Render(42))
}
答案4
得分: 4
你还可以尝试一下Dargo,它是一个新的库,具有一些Facebook库没有的功能。代码在这里。
以下是一个示例:
在这个示例中,一个名为SimpleService的服务将注入一个日志记录器。日志记录器本身是一个使用创建方法绑定的dargo服务。创建方法如下所示:
func newLogger(ioc.ServiceLocator, ioc.Descriptor) (interface{}, error) {
return logrus.New(), nil
}
SimpleService的绑定将提供用于实现接口的结构体。结构体中有一个字段,注解为inject,后面跟着要注入的服务的名称。以下是接口和实现它的结构体:
type SimpleService interface {
// CallMe logs a message to the logger!
CallMe()
}
// SimpleServiceData is a struct implementing SimpleService
type SimpleServiceData struct {
Log *logrus.Logger `inject:"LoggerService_Name"`
}
// CallMe implements the SimpleService method
func (ssd *SimpleServiceData) CallMe() {
ssd.Log.Info("This logger was injected!")
}
日志记录器服务和SimpleService都被绑定到ServiceLocator中。这通常在程序的开始处完成:
locator, err := ioc.CreateAndBind("InjectionExampleLocator", func(binder ioc.Binder) error {
// 通过提供结构体绑定SimpleService
binder.Bind("SimpleService", SimpleServiceData{})
// 通过提供创建函数绑定日志记录器服务
binder.BindWithCreator("LoggerService_Name", newLogger).InScope(ioc.PerLookup)
return nil
})
返回的locator可以用于查找SimpleService服务。SimpleService被绑定到Singleton作用域(默认作用域),这意味着它只会在第一次查找或注入时创建,之后不会再次创建。另一方面,LoggerService处于PerLookup作用域,这意味着每次注入或查找时都会创建一个新的实例。
以下是使用查找到的服务的代码:
raw, err := locator.GetDService("SimpleService")
if err != nil {
return err
}
ss, ok := raw.(SimpleService)
if !ok {
return fmt.Errorf("Invalid type for simple service %v", ss)
}
ss.CallMe()
支持任意深度的注入(ServiceA可以依赖于ServiceB,ServiceB可以依赖于ServiceC,依此类推)。一个服务也可以依赖于任意多个服务(ServiceA可以依赖于服务D、E和F等)。但是,服务之间不能存在循环依赖关系。
英文:
You should also try Dargo, which is new but has some features that the facebook one doesn't have. The code is here.
Here is an example:
In this example a service called SimpleService will inject a logger. The logger itself is a dargo service that is bound with a creation method. That creation method looks like this:
func newLogger(ioc.ServiceLocator, ioc.Descriptor) (interface{}, error) {
return logrus.New(), nil
}
The binding of SimpleService will provide the struct that should be used to implement the interface. The struct has a field annotated with inject followed by the name of the service to inject. This is the interface and the struct used to implement it:
type SimpleService interface {
// CallMe logs a message to the logger!
CallMe()
}
// SimpleServiceData is a struct implementing SimpleService
type SimpleServiceData struct {
Log *logrus.Logger `inject:"LoggerService_Name"`
}
// CallMe implements the SimpleService method
func (ssd *SimpleServiceData) CallMe() {
ssd.Log.Info("This logger was injected!")
}
Both the logger service and the SimpleService are bound into the ServiceLocator. This is normally done near the start of your program:
locator, err := ioc.CreateAndBind("InjectionExampleLocator", func(binder ioc.Binder) error {
// Binds SimpleService by providing the structure
binder.Bind("SimpleService", SimpleServiceData{})
// Binds the logger service by providing the creation function
binder.BindWithCreator("LoggerService_Name", newLogger).InScope(ioc.PerLookup)
return nil
})
The returned locator can be used to lookup the SimpleService service. The SimpleService is bound into the Singleton scope (the default scope), which means that it will only be created the first time it is looked up or injected, and never again. The LoggerService, on the other hand is in the PerLookup scope, which means that every time it is injected or looked up a new one will be created.
This is the code that uses the looked up service:
raw, err := locator.GetDService("SimpleService")
if err != nil {
return err
}
ss, ok := raw.(SimpleService)
if !ok {
return fmt.Errorf("Invalid type for simple service %v", ss)
}
ss.CallMe()
Any depth of injection is supported (ServiceA can depend on ServiceB which depends on ServiceC and so on). A service can also depend on as many services as it would like (ServiceA can depend on service D, E and F etc). Howerver, services cannot have circular dependencies.
答案5
得分: 3
Uber的Dig(https://github.com/uber-go/dig)非常棒。这是一篇关于它的很棒的博客文章:Go中的依赖注入。
英文:
Uber's Dig is pretty awesome. Here's a great blog post about it: Dependency Injection in Go
答案6
得分: 1
如果你仍然对寻找一个在Go语言中使用非常少的反射的DI库感兴趣,我制作了一个叫做axon的库。它基于Google的Guice,所以如果你来自Java世界(就像我自己),它应该很好地适应你对它的工作方式的期望。
我在Web服务器中使用它,没有遇到任何问题,并且足够快,不会对在命令行界面中使用它产生任何影响。
英文:
If you're still interested in finding a DI library for Go that uses very minimal reflection I made one called axon. It's based on Google's Guice so if you're coming from the Java world (like myself) it should lend itself well to how you expect it to work.
I've used it in web servers and it hasn't had any problems and was fast enough to not have any impact on being used within CLIs.
答案7
得分: 1
你也可以查看Uber的fx,它是一个更通用的应用程序框架。它在底层使用了Uber的dig,正如@yndolok已经提到的那样。
fx的GitHub链接:https://github.com/uber-go/fx
英文:
You can also check Uber's fx for a more general application framework. It uses Uber's dig under the hood as @yndolok has already mentioned.
fx github link: https://github.com/uber-go/fx
答案8
得分: 1
我可能有偏见,因为我是作者,但是https://github.com/muir/nject 是Go语言中最好的依赖注入框架。它易于使用,并提供了非常完整的功能集。
它基于类型。给定一组消费和产生各种类型的函数列表,它会将它们连接在一起,以便调用列表中的最后一个函数,并调用提供给最后一个函数的参数所需的其他函数。
英文:
I may be biased, being the author, but https://github.com/muir/nject is the best DI framework for Go. It is easy to use and provides a very full feature set.
It's based on types. Given a list of functions that consume and produce various types, it will wire them together so that the last function in the list gets called and whatever other functions are necessary to provide the arguments to the last function will also get called.
答案9
得分: 0
Go 1.18 添加了对泛型的支持,我认为可以利用这个语言特性来实现依赖注入,它现在已经内置在语言中,不再是外部依赖。详细信息可以参考这篇文章:https://go.dev/blog/intro-generics
英文:
Go 1.18 adds supports for generics, and I think dependency injection can be achieved by using this language feature, it's built into the language now and not an external dependency https://go.dev/blog/intro-generics
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论