Mastering Dependency Injection in Go with Uber’s Dig
This article explains why and when to use dependency injection in Go, introduces the most popular DI libraries, and provides step‑by‑step examples of Dig’s core features such as container creation, providing and invoking dependencies, parameter and result objects, optional and named dependencies, groups, and a complete multi‑service HTTP demo.
Introduction
Whether Go needs a dependency‑injection (DI) library has been debated; the answer depends on the programming style. DI is a pattern suited to object‑oriented code, while functional code often does not require it. Go supports multiple paradigms, so for large OOP‑style projects you should decide case‑by‑case.
Popular Go DI Libraries
Many libraries implement DI, but they are libraries rather than frameworks. Notable open‑source options include Uber’s dig, Elliotchance’s dingo, Sarulabs’ di, Google’s wire, and Facebook’s inject. The most widely adopted are dig and wire; this guide focuses on dig.
Basic Usage
Creating a container container := dig.New() The container manages dependencies.
Providing a dependency
type DBClient struct{}
func NewDBClient() *DBClient { return &DBClient{} }
func InitDB() *DBClient { return NewDBClient() }
container.Provide(InitDB)Each type is created once (singleton). Subsequent Provide calls for the same type are ignored.
Invoking a dependency
func UseOption(db *DBClient) { /* use db */ }
container.Invoke(UseOption)Parameter Objects
When a function needs many dependencies, bundle them into a struct. Example:
container.Provide(func InitHttpServer(svcCfg *ServerConfig, mysql *MySQL, redis *Redis, mongodb *Mongodb) *Server {
// business logic
return Server.Run()
})To keep readability, Dig offers dig.In to pack parameters:
type ServerParams struct {
dig.In
SvcCfg *ServerConfig
MySQL *MySQL
Redis *Redis
Mongodb *Mongodb
}
container.Provide(func(p ServerParams) *Server {
// business logic
return Server.Run()
})Result Objects
If a provider returns multiple values, use dig.Out similarly:
type BuildServerDeps struct {
dig.Out
ServerCfg *ServerConfig
MySQL *MySQL
Redis *Redis
Mongodb *Mongodb
}
container.Provide(func() (*ServerConfig, *MySQL, *Redis, *Mongodb) {
// business logic
return svcfg, mysql, redis, mongodb
})
// Equivalent using dig.Out
container.Provide(func() BuildServerDeps {
return BuildServerDeps{ServerCfg: svcfg, MySQL: mysql, Redis: redis, Mongodb: mongodb}
})Optional Dependencies
If a required dependency is missing, Dig panics. Mark a field as optional to receive nil instead:
type InitDBParams struct {
dig.In
MongoConfig *Mongo.Config `optional:"true"`
}
func InitDB(p InitDBParams) *Mongo.Client {
// p.MongoConfig may be nil
return Mongo.NewClient(p.MongoConfig)
}
container.Invoke(InitDB) // continues even if Mongo.Config is absentNamed Dependencies
When you need multiple instances of the same type, give each a name via dig.Name during Provide and retrieve them through struct tags:
type MongodbClient struct{}
func NewMongoClient(cfg *Mongo.Client) *MongodbClient { return ConnectionMongo(cfg) }
container.Provide(NewMongoClient, dig.Name("mgo1"))
container.Provide(NewMongoClient, dig.Name("mgo2"))
type MongodbClients struct {
dig.In
Mgo1 *MongodbClient `name:"mgo1"`
Mgo2 *MongodbClient `name:"mgo2"`
}
container.Invoke(func(c MongodbClients) { /* use c.Mgo1 and c.Mgo2 */ })All fields of a nested dig.In struct must be present in the container; otherwise the invoke will not run.
Groups
Instead of naming, you can group multiple providers of the same type. Use dig.Group when providing and a slice tag when consuming:
container.Provide(NewMongoClient, dig.Group("mgo"))
container.Provide(NewMongoClient, dig.Group("mgo"))
type MongodbClientGroup struct {
dig.In
Clients []*MongodbClient `group:"mgo"`
}
container.Invoke(func(g MongodbClientGroup) {
for _, c := range g.Clients {
// business logic
}
})All grouped fields must have at least one instance in the container; the order of the slice is undefined.
Putting It All Together: Launch Multiple HTTP Services
The following example demonstrates a complete application that uses named configurations, grouped server instances, and concurrent service startup.
package main
import (
"errors"
"fmt"
"net/http"
"strconv"
"go.uber.org/dig"
)
type ServerConfig struct { Host string; Port string; Used bool }
type ServerGroup struct { dig.In; Servers []*Server `group:"server"` }
type ServerConfigNamed struct { dig.In; Config1 *ServerConfig `name:"config1"`; Config2 *ServerConfig `name:"config2"`; Config3 *ServerConfig `name:"config3"` }
type Server struct { Config *ServerConfig }
func (s *Server) Run(i int) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Service %d, port %s", i, s.Config.Port)
})
http.ListenAndServe(s.Config.Host+":"+s.Config.Port, mux)
}
func NewServerConfig(port string) func() *ServerConfig {
return func() *ServerConfig { return &ServerConfig{Host: "127.0.0.1", Port: port, Used: false} }
}
func NewServer(sc ServerConfigNamed) *Server {
if !sc.Config1.Used { sc.Config1.Used = true; return &Server{Config: sc.Config1} }
if !sc.Config2.Used { sc.Config2.Used = true; return &Server{Config: sc.Config2} }
if !sc.Config3.Used { sc.Config3.Used = true; return &Server{Config: sc.Config3} }
panic(errors.New("no free config"))
}
func ServerRun(sg ServerGroup) {
for i, s := range sg.Servers { go s.Run(i) }
}
func main() {
c := dig.New()
c.Provide(NewServerConfig("8199"), dig.Name("config1"))
c.Provide(NewServerConfig("8299"), dig.Name("config2"))
c.Provide(NewServerConfig("8399"), dig.Name("config3"))
c.Provide(NewServer, dig.Group("server"))
c.Provide(NewServer, dig.Group("server"))
c.Provide(NewServer, dig.Group("server"))
serverChan := make(chan int, 1)
c.Invoke(ServerRun)
<-serverChan // block main goroutine
}Running the program starts three HTTP services on ports 8199, 8299, and 8399. The demo shows that no single API is universally best; choose the one that fits your business needs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Go Development Architecture Practice
Daily sharing of Golang-related technical articles, practical resources, language news, tutorials, real-world projects, and more. Looking forward to growing together. Let's go!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
