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.

Go Development Architecture Practice
Go Development Architecture Practice
Go Development Architecture Practice
Mastering Dependency Injection in Go with Uber’s Dig

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 absent

Named 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backendLibrarydigdependency-injection
Go Development Architecture Practice
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.