SoFunction
Updated on 2025-03-04

Golang's method to implement dependency injection in less than 30 lines of code

This article introduces the method of implementing dependency injection in golang less than 30 lines of code. I will share it with you. The details are as follows:

Project gallery

go-di-demo

This project depends on

Implemented using standard library without additional dependencies

Advantages of Dependency Injection

People who use Java must be familiar with the spring framework. The core of spring is an IoC (control inversion/dependency injection) container, which brings a big advantage to decoupling. Generally, you only rely on containers, not specific classes. When your class is modified, you need to change the container-related code at most, and the business code will not be affected.

The principle of golang's dependency injection

In general, it is similar to Java. The steps are as follows: (golang does not support dynamic creation of objects, so you need to create objects manually and then inject them. Java can create objects dynamically directly)

  • Read the dependencies of the object through reflection (golang is implemented through tag)
  • Find the object instance in the container
  • If there is an instance of this object or a factory method to create an object, inject the object or use the factory to create an object and inject it
  • If there is no instance of this object, an error will be reported

Code implementation

A typical container implementation is as follows. The dependency type refers to spring's singleton/prototype, which is the object singleton and instance object:

package di

import (
 "sync"
 "reflect"
 "fmt"
 "strings"
 "errors"
)

var (
 ErrFactoryNotFound = ("factory not found")
)

type factory = func() (interface{}, error)
// Containertype Container struct {
 
 singletons map[string]interface{}
 factories map[string]factory
}
// Container instantiationfunc NewContainer() *Container {
 return &Container{
  singletons: make(map[string]interface{}),
  factories: make(map[string]factory),
 }
}

// Register a singleton objectfunc (p *Container) SetSingleton(name string, singleton interface{}) {
 ()
 [name] = singleton
 ()
}

// Get singleton objectfunc (p *Container) GetSingleton(name string) interface{} {
 return [name]
}

// Get the instance objectfunc (p *Container) GetPrototype(name string) (interface{}, error) {
 factory, ok := [name]
 if !ok {
  return nil, ErrFactoryNotFound
 }
 return factory()
}

// Set the instance object factoryfunc (p *Container) SetPrototype(name string, factory factory) {
 ()
 [name] = factory
 ()
}

// Inject dependenciesfunc (p *Container) Ensure(instance interface{}) error {
 elemType := (instance).Elem()
 ele := (instance).Elem()
 for i := 0; i < (); i++ { // traverse fields  fieldType := (i)
  tag := ("di") // Get tag  diName := (tag)
  if diName == "" {
   continue
  }
  var (
   diInstance interface{}
   err  error
  )
  if (tag) {
   diInstance = (diName)
  }
  if (tag) {
   diInstance, err = (diName)
  }
  if err != nil {
   return err
  }
  if diInstance == nil {
   return (diName + " dependency not found")
  }
  (i).Set((diInstance))
 }
 return nil
}

// Get the dependency name that needs to be injectedfunc (p *Container) injectName(tag string) string {
 tags := (tag, ",")
 if len(tags) == 0 {
  return ""
 }
 return tags[0]
}

// Detect whether singleton dependenciesfunc (p *Container) isSingleton(tag string) bool {
 tags := (tag, ",")
 for _, name := range tags {
  if name == "prototype" {
   return false
  }
 }
 return true
}

// Detect whether instance dependenciesfunc (p *Container) isPrototype(tag string) bool {
 tags := (tag, ",")
 for _, name := range tags {
  if name == "prototype" {
   return true
  }
 }
 return false
}

// Print the container internal instancefunc (p *Container) String() string {
 lines := make([]string, 0, len()+len()+2)
 lines = append(lines, "singletons:")
 for name, item := range  {
  line := (" %s: %x %s", name, &item, (item).String())
  lines = append(lines, line)
 }
 lines = append(lines, "factories:")
 for name, item := range  {
  line := (" %s: %x %s", name, &item, (item).String())
  lines = append(lines, line)
 }
 return (lines, "\n")
}
  • The most important thing is the Ensure method, which scans all export fields of the instance and reads the di tag, and if there is that tag, starts injection.
  • Determine the type of the di tag to determine the injection of singleton or prototype object

test

  1. The singleton object has only one instance in the entire container, so no matter where it is injected, the obtained pointer must be the same.
  2. The instance object is created through the same factory method, so the pointer of each instance cannot be the same.

The following is the test entry code. The complete code is in the github repository. If you are interested, you can read it:

package main

import (
 "di"
 "database/sql"
 "fmt"
 "os"
 _ "/go-sql-driver/mysql"
 "demo"
)

func main() {
 container := ()
 db, err := ("mysql", "root:root@tcp(localhost)/sampledb")
 if err != nil {
  ("error: %s\n", ())
  (1)
 }
 ("db", db)
 ("b", func() (interface{}, error) {
  return (), nil
 })

 a := ()
 if err := (a); err != nil {
  (err)
  return
 }
 // Print pointer to ensure the pointer address of singleton and instance ("db: %p\ndb1: %p\nb: %p\nb1: %p\n", , a.Db1, &, &a.B1)
}

The result printed after execution is:

db: 0xc4200b6140
db1: 0xc4200b6140
b: 0xc4200a0330
b1: 0xc4200a0338

You can see that the pointers of two db instances are the same, which means that they are the same instance, while the pointers of two b are different, which means that they are not the same instance.

Written at the end

Dependency injection can be used to manage instantiation and dependencies between multiple objects well. In conjunction with the configuration file, the instances that need to be injected are registered into the container during the application initialization stage. You only need to inject the container during instantiation anywhere in the application. No additional dependencies.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.