Is there a better dependency injection pattern in golang?

后端 未结 6 1889
野趣味
野趣味 2021-02-07 09:07

Given this code:

package main

import (
    \"fmt\"
)

type datstr string

type Guy interface {
   SomeDumbGuy() string
}

func (d *datstr) SomeDumbGuy() string          


        
相关标签:
6条回答
  • 2021-02-07 09:25

    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))
    
    }
    
    0 讨论(0)
  • 2021-02-07 09:29

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

    0 讨论(0)
  • 2021-02-07 09:32

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

    0 讨论(0)
  • 2021-02-07 09:34

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

    • Compile-time Dependency Injection With Go Cloud's Wire

    • Go Dependency Injection with Wire

    0 讨论(0)
  • 2021-02-07 09:38

    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.

    0 讨论(0)
  • 2021-02-07 09:49

    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.

    0 讨论(0)
提交回复
热议问题