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