简体   繁体   中英

Is there a better dependency injection pattern in golang?

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?

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

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

}

Google's Wire looks promising. There're some articles about it:

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.

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

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM