web-dev-qa-db-fra.com

Existe-t-il un meilleur modèle d’injection de dépendance dans le golang?

Étant donné ce 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)
}

Est-ce que le câblage des composants ensemble qui est fait dans main est la bonne façon de câbler une dépendance ensemble? Il semble que j'utilise un peu trop cela dans mon code. Y at-il un modèle commun meilleur que ceci, ou est-ce que je réfléchis trop?

9
Justin Thomas

Oui, la bibliothèque facebookgo inject vous permet de prendre vos membres injectés et câble le graphique pour vous.

Code: https://github.com/facebookgo/inject

Documentation: https://godoc.org/github.com/facebookgo/inject

Voici un exemple de code tiré de la 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))

}
7
Wilbert

Vous devriez également essayer Dargo , qui est nouveau mais qui possède des fonctionnalités que l’on ne possède pas sur facebook. Le code est ici .

Voici un exemple:

Dans cet exemple, un service appelé SimpleService injectera un enregistreur. L'enregistreur lui-même est un service dargo lié à une méthode de création. Cette méthode de création ressemble à ceci:

func newLogger(ioc.ServiceLocator, ioc.Descriptor) (interface{}, error) {
    return logrus.New(), nil
}

La liaison de SimpleService fournira la structure à utiliser pour implémenter l'interface. La structure a un champ annoté avec inject suivi du nom du service à injecter. Voici l'interface et la structure utilisée pour l'implémenter:

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!")
}

Le service de journalisation et SimpleService sont liés dans ServiceLocator. Cela se fait normalement au début de votre programme:

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

Le localisateur renvoyé peut être utilisé pour rechercher le service SimpleService. SimpleService est lié à la portée Singleton (la portée par défaut), ce qui signifie qu'il ne sera créé que lors de la première recherche ou injection, et jamais plus. LoggerService, quant à lui, se trouve dans l'étendue PerLookup, ce qui signifie que chaque fois qu'il est injecté ou consulté, un nouveau est créé.

C'est le code qui utilise le service recherché:

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

Toute profondeur d'injection est prise en charge (ServiceA peut dépendre de ServiceB qui dépend de ServiceC, etc.). Un service peut également dépendre du nombre de services souhaité (ServiceA peut dépendre des services D, E et F, etc.). Cependant, les services ne peuvent pas avoir de dépendances circulaires.

1
jwells131313

Uber's Dig est assez génial. Voici un excellent article de blog à ce sujet: Dependency Injection in Go

0
yndolok