A list,

Dependency injection is involved when an instance is created that requires a dependency. Suppose we need to create a service instance that requires an instance of a configuration item. The first approach is to create an instance automatically when it is initialized, with no awareness of the creator.

package main

import "net/http"

type Config struct {
	address string
	port    string
}

type Server struct {
	config *Config
}

func NewServer(a) *Server {
	return &Server{BuildConfig()}
}

func BuildConfig(a) *Config {
	return &Config{"127.0.0.1"."8080"}}func main(a) {
	svc := NewServer()
	http.HandleFunc("/".func(resp http.ResponseWriter, req *http.Request) {
		resp.Write([]byte("di"))
	})
	http.ListenAndServe(svc.config.address+":"+svc.config.port, nil)}Copy the code

While it is very convenient to create a Config object, there is a problem that is not extensible. If you want to set the Config instance yourself, you need to pass arguments to the BuildConfig function, which may need to be passed at all NewServer locations. The modified code looks like this.

func NewServer(c *Config) *Server {
	return &Server{c}
}

c := Config{"127.0.0.1"."8080"}
svc := NewServer(&c)
Copy the code

This separates the logic for creating Config from creating Server. But to create a Server instance, you must first create a Config instance. This forms a dependency, as shown in the dependency diagram.

In practical applications, dependency diagrams can be more complex.

The FindPerson application

Now let’s look at a more complicated case. Suppose you have a SutraPavilion database in mongodb with a Person collection in the following format:

The next step is to build a Web application from scratch and return the data in JSON format.

The curl http://127.0.0.1:8080/personCopy the code
[{"Id":"5fb9e8c780efe11bf021fd35"."name":"Patriarch Dharma"."age":65535}, {"Id":"5fb9ec1880efe11bf021fd36"."name":"Zhang Sanfeng"."age":1024}]
Copy the code

The directory structure of the file is as follows.

| ____mgo / / mongo connection methods | | ____mgo. Go | ____schema / / define the data structure | | ____Person. Go | ____controllers / / define controller | | ____Person. Go | ____main. Go / / main method | ____services / / define the salesman logical interface | | ____impl / / implement the business logic | | | ____person. Go | | ____person. GoCopy the code

The configuration item structure and connection method of mongodb are defined in MGO.

package mgo

import (
	"context"
	"fmt"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type Config struct {
	Host     string
	Port     string
	Username string
	Password string
	Database string
}

func ConnectDatabase(c *Config) (*mongo.Database, error) {
	uri := fmt.Sprintf("mongodb://%s:%s@%s:%s/%s? authSource=admin", c.Username, c.Password, c.Host, c.Port, c.Database)
	clientOptions := options.Client().ApplyURI(uri)
	client, err := mongo.Connect(context.TODO(), clientOptions)
	iferr ! =nil {
		panic(err)
	}
	db := client.Database(c.Database)
	return db, err
}
Copy the code

Define the structure of person in the schema.

package schema

type Person struct {
	Id   string `bson:"_id"`
	Name string `json:"name"`
	Age  int    `json:"age"`
}
Copy the code

Define service interfaces in services.

package services

import (
	".. /schema"
)

type Person interface {
	FindAll() []*schema.Person
}
Copy the code

Implement the interface in the IMPL.

package impl

import (
	"context"
	"log"

	".. /.. /schema"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

type Person struct {
	Db *mongo.Database
}

func (p Person) FindAll(a)[] *schema.Person {
	var result []*schema.Person
	cur, err := p.Db.Collection("person").Find(context.TODO(), bson.D{{}})
	iferr ! =nil {
		log.Fatal(err)
	}
	for cur.Next(context.TODO()) {
		var elem schema.Person
		err := cur.Decode(&elem)
		iferr ! =nil {
			log.Fatal(err)
		}
		result = append(result, &elem)
	}
	return result
}
Copy the code

Define the FindAll method in controllers.

package controllers

import (
	"encoding/json"
	"net/http"

	".. /services"
)

type Person struct {
	Service services.Person
}

func (p *Person) FindAll(w http.ResponseWriter, r *http.Request) {
	people := p.Service.FindAll()
	bytes, _ := json.Marshal(people)

	w.Header().Set("Content-Type"."application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(bytes)
}
Copy the code

Finally, the Server structure is defined in the main function.

package main

import (
	"net/http"

	"./controllers"
	"./mgo"
	"./services/impl"
)

type ServerConfig struct {
	Host string
	Port string
}
type Server struct {
	serverConfig *ServerConfig
	routes       *map[string]http.HandlerFunc
}

func (svc *Server) Run(a) {
	for path := range *svc.routes {
		http.HandleFunc(path, (*svc.routes)[path])
	}
	http.ListenAndServe(svc.serverConfig.Host+":"+svc.serverConfig.Port, nil)}func main(a) {
	// Create the mongodb configuration
	mgoCfg := mgo.Config{
		Host:     "127.0.0.1",
		Port:     "27017",
		Username: "admin",
		Password: "UgOnvFDYyxZa0PR3jdp2",
		Database: "SutraPavilion",
	}
	Db, _ := mgo.ConnectDatabase(&mgoCfg)
	// Create person Service
	personService := impl.Person{
		Db: Db,
	}
	// Create person Controller
	personController := controllers.Person{
		Service: personService,
	}
	// Create the Routes configuration
	routes := make(map[string]http.HandlerFunc)
	routes["/person"] = personController.FindAll
	// Create server configuration
	svcCfg := ServerConfig{
		Host: "127.0.0.1",
		Port: "8080",
	}
	svc := Server{&svcCfg, &routes}
	// Start the service
	svc.Run()
}
Copy the code

As you can see, a large number of configuration item instances are created in the main function. The dependency graph is as follows:

As the application grows in complexity, these configuration items and their dependencies need to be maintained, and additional components may be added.

Use DI library Dig to optimize the code

Dig is uber’s open source DI library. An instance of DIG is called a container, where all the instances are stored. Contaienr provides two apis, Provide and Invoke. Provide requires passing in a function whose return value, the instance of the dependency, is stored in the Container. Instances of the same type are stored only once. If a dependency needs to depend on other instances when it is injected, the Container will inject the dependency automatically. You only need to declare the dependency in the function’s formal parameters. Invoke needs to pass in a function, similar to Provide, that automatically retrieves dependent instances from Containers. But Invoke does not inject dependencies.

package main

import (
	"net/http"

	"./controllers"
	"./mgo"
	"./services/impl"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/dig"
)

type ServerConfig struct {
	Host string
	Port string
}
type Router struct {
	routes *map[string]http.HandlerFunc
}
type Server struct {
	serverConfig *ServerConfig
	router       *Router
}

func (svc *Server) Run(a) {
	for path := range *svc.router.routes {
		http.HandleFunc(path, (*svc.router.routes)[path])
	}
	http.ListenAndServe(svc.serverConfig.Host+":"+svc.serverConfig.Port, nil)}func NewMogConfig(a) *mgo.Config {
	return &mgo.Config{
		Host:     "127.0.0.1",
		Port:     "27017",
		Username: "admin",
		Password: "UgOnvFDYyxZa0PR3jdp2",
		Database: "SutraPavilion",}}func NewDB(mgoCfg *mgo.Config) *mongo.Database {
	Db, _ := mgo.ConnectDatabase(mgoCfg)
	return Db
}
func NewPersonService(Db *mongo.Database) *impl.Person {
	return &impl.Person{
		Db: Db,
	}
}
func NewPersonController(personService *impl.Person) *controllers.Person {
	return &controllers.Person{
		Service: *personService,
	}
}
func NewRouter(personController *controllers.Person) *Router {
	// Create the Routes configuration
	routes := make(map[string]http.HandlerFunc)
	routes["/person"] = personController.FindAll
	return &Router{&routes}
}
func NewServerConfig(a) *ServerConfig {
	return &ServerConfig{
		Host: "127.0.0.1",
		Port: "8080",}}func NewServer(svcCfg *ServerConfig, router *Router) *Server {
	return &Server{svcCfg, router}
}

func BuildContainer(a) *dig.Container {
	container := dig.New()
	container.Provide(NewMogConfig)
	container.Provide(NewDB)
	container.Provide(NewPersonService)
	container.Provide(NewPersonController)
	container.Provide(NewRouter)
	container.Provide(NewServerConfig)
	container.Provide(NewServer)
	return container
}

func main(a) {
	container := BuildContainer()
	// Start the service
	err := container.Invoke(func(server *Server) {
		server.Run()
	})
	iferr ! =nil {
		panic(err)
	}
}
Copy the code

With dependency injection, you can eliminate many init functions and global variables. Improve code maintainability.

4. Reference links

Dependency Injection in Go software is fun

Dependency injection in GO golangforall