Why Dependency Injection Matters in Go and How to Leverage Uber’s Fx Framework
This article explains the concept of dependency injection, why it is essential for Go backend services, compares manual object wiring with DI containers, reviews popular Go DI libraries, and demonstrates how to use Uber’s Fx framework (and a custom Viego framework) to simplify application initialization and lifecycle management.
What is Dependency Injection (DI)
Dependency Injection is a software design pattern that separates object creation from object usage, allowing dependencies to be supplied externally rather than hard‑coded inside components.
Why DI is Needed in Go Services
When building a Go HTTP API, the service typically performs many start‑up steps such as reading command‑line flags, loading configuration, connecting to MySQL and Redis, creating loggers, and registering HTTP routes. A naïve implementation manually wires these steps, leading to code like:
configFile := cmdFlags.get("--config")
configObject := readConfig(configFile)
mysqlConn, err := mysql.Connect(configObject.mysql)
userModel := NewUserModel(mysqlConn)
redisConn, err := redis.Connect(configObject.redis)
cache := NewCache(redisConn)
httpServer, err := http.New(configObject.http)
httpServer.Router("/user/any", NewUserControl(userModel, cache))
httpServer.Start()
<-sig
httpServer.Stop()
redisConn.Close()
mysqlConn.Close()As the application grows, the number of parameters passed to constructors (e.g., configuration, logger, HTTP client, RPC client) explodes, encouraging the use of global variables and making the codebase hard to maintain.
DI with Pseudo‑Code
Using a DI container, the same logic can be expressed more declaratively:
ioc := NewContainer()
ioc.Inject(configObject)
ioc.Inject(mysql.Connect, redis.Connect)
ioc.Inject(NewUserModel, NewCache, NewLogger)
ioc.Inject(NewUserControl, NewOtherControl)
ioc.Invoke(func(httpServer *http.Server, userCtrl *UserControl) {
httpServer.Router("/user/any", userCtrl)
})
ioc.Start()
<-sig
ioc.Stop()Benefits include decoupling object creation from usage, automatic resolution of dependency order, and removal of boiler‑plate init code.
Popular Go DI Libraries
Facebook inject – runtime injection using reflection.
Google wire – compile‑time injection based on AST analysis.
Uber dig/fx – runtime injection with reflection and a full application framework.
The author finds inject weak and unmaintained, wire a bit abstract, and focuses on fx for its practicality.
Using Uber’s Fx Framework
Fx provides a container that manages lifecycle, dependency provision, and invocation. A minimal example:
package main
import (
"context"
"log"
"net/http"
"os"
"time"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
)
func NewLogger() *log.Logger {
logger := log.New(os.Stdout, "", 0)
logger.Print("Executing NewLogger.")
return logger
}
func NewHandler(logger *log.Logger) (http.Handler, error) {
logger.Print("Executing NewHandler.")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Print("Got a request.")
}), nil
}
func NewMux(lc fx.Lifecycle, logger *log.Logger) *http.ServeMux {
mux := http.NewServeMux()
server := &http.Server{Addr: ":8080", Handler: mux}
lc.Append(fx.Hook{OnStart: func(context.Context) error { go server.ListenAndServe(); return nil },
OnStop: func(ctx context.Context) error { logger.Print("Stopping HTTP server."); return server.Shutdown(ctx) }})
return mux
}
func Register(mux *http.ServeMux, h http.Handler) { mux.Handle("/", h) }
func main() {
app := fx.New(
fx.Provide(NewLogger, NewHandler, NewMux),
fx.Invoke(Register),
fx.WithLogger(func() fxevent.Logger { return fxevent.NopLogger }),
)
startCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Start(startCtx); err != nil { log.Fatal(err) }
if _, err := http.Get("http://localhost:8080/"); err != nil { log.Fatal(err) }
stopCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := app.Stop(stopCtx); err != nil { log.Fatal(err) }
}Fx eliminates the need for global state and init() functions, automatically resolves dependencies, and manages start‑up and shutdown hooks.
Viego – A Custom Framework Built on Fx
Viego extends Fx with a set of ready‑made modules for common backend needs (command‑line parsing, configuration via Viper, logging via Zap, HTTP/gRPC servers, MySQL, Redis, service discovery, etc.). Users only need to provide a configuration file and invoke Viego’s Run method.
Example configuration for a Zap logger:
logger:
default: |
level: debug
outputPaths: ["stdout"]
encoding: color
encoderConfig:
timeEncoder:
layout: "2006-01-02 15:04:05.000000"Minimal main.go using Viego:
package main
import (
"git.woa.com/yangynyang/viego"
"git.woa.com/yangynyang/viego/module/logger"
"go.uber.org/zap"
"go.uber.org/fx"
)
func main() {
viego.Run(
viego.WithName("_test"),
viego.WithOption(
fx.Invoke(func(lgdefault *zap.Logger, lgget logger.Get) {
// use logger instances here
}),
),
)
}The framework’s design follows a root‑object (static) that other components depend on, forming a directed object graph. Most programs start with command‑line arguments as the first object, and Viego automatically constructs the rest based on the configuration.
Conclusion
Dependency injection simplifies Go backend development by decoupling object creation, reducing boiler‑plate, and improving testability. Uber’s Fx offers a pragmatic runtime DI solution, and the Viego framework demonstrates how Fx can be extended with opinionated modules to accelerate service development.
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.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
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.
