Is there a better dependency injection pattern in

2020-07-02 11:32发布

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?

标签: go interface
6条回答
forever°为你锁心
2楼-- · 2020-07-02 11:39
戒情不戒烟
3楼-- · 2020-07-02 11:46

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.

查看更多
冷血范
4楼-- · 2020-07-02 11:51

Best practice is not to use a DI library.
Go is meant to be a simple language which is easy to follow - without you having to look up how things are working under the hood.

A library/framework will abstract that away from you...

What you're doing is pretty much correct.

Source: https://www.youtube.com/watch?v=PTE4VJIdHPg&list=WL&index=87

  • watch from 10:00 for a thorough explanation

This same explanation was also provided in the blog of Peter Bourgon: https://peter.bourgon.org/go-for-industrial-programming/

Lastly Danny explain how he user DI in Go after 2 years working in Industrial Programming: https://medium.com/@madannyaguswahyudi/how-i-structure-go-code-after-2-years-working-in-industrial-programming-17ed5ef83e1e

查看更多
相关推荐>>
5楼-- · 2020-07-02 11:53

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))

}
查看更多
乱世女痞
6楼-- · 2020-07-02 12:00

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楼-- · 2020-07-02 12:01

Uber's Dig is pretty awesome. Here's a great blog post about it: Dependency Injection in Go

查看更多
登录 后发表回答