Mastering Go Dependency Injection: Manual vs Google Wire Explained

This article examines the challenges of tangled initialization in Go's main.go, demonstrates a manual DI container for an order service, and contrasts it with Google’s compile‑time wire tool for a user service, offering practical code examples, step‑by‑step guidance, and recommendations for projects of different sizes.

Code Wrench
Code Wrench
Code Wrench
Mastering Go Dependency Injection: Manual vs Google Wire Explained

When a Go project's main.go becomes a "big mud ball" filled with numerous NewService calls, the initialization order can quickly become unmanageable. This article uses the open‑source easyms.golang project to explore dependency injection (DI) concepts and presents two concrete implementations.

Problem: Over‑grown main.go

A typical Go main function often looks like spaghetti code, directly creating configuration, logger, database, repositories, services, handlers, and routing in one place. As the number of services grows, the file expands and becomes hard to maintain.

func main() {
    // A typical "spaghetti" main function
    cfg := config.Load()
    logger := logger.New(cfg.Log)

    db, err := database.New(cfg.DB)
    if err != nil { /* ... */ }

    userRepo := repository.NewUserRepo(db)
    orderRepo := repository.NewOrderRepo(db)

    authSvc := service.NewAuthService(userRepo)
    orderSvc := service.NewOrderService(orderRepo, logger)

    authHandler := handler.NewAuthHandler(authSvc)
    orderHandler := handler.NewOrderHandler(orderSvc)

    engine := gin.New()
    engine.POST("/login", authHandler.Login)
    engine.POST("/orders", orderHandler.Create)

    engine.Run()
}

Solution 1: Manual DI Container (order‑svc)

The simplest approach extracts the initialization logic into a dedicated file, creating a manual DI container.

1. Define the App struct that holds core components such as the HTTP engine, discovery service, and other services.

// internal/services/order/cmd/ordersvc/app.go
type App struct {
    engine       *gin.Engine
    ds           *discovery.Discovery
    relayService *service.RelayService
    // ... other fields
}

2. Implement InitializeApp to load configuration, create the database, instantiate services, and assemble the App instance. The function also returns a cleanup closure that shuts down resources in reverse order.

func InitializeApp(serverName string, env string) (*App, func(), error) {
    // --- 1. Load config ---
    appConfig, err := provideAppConfig(...)
    if err != nil { return nil, nil, err }

    // --- 2. Init basic components ---
    dbase, err := provideDatabase(appConfig)
    // ...

    // --- 3. Init services ---
    orderService := service.NewOrderService(dbase)
    // ...

    // --- 4. Build App ---
    app := &App{ /* fields */ }

    // --- 5. Cleanup function ---
    cleanup := func() {
        relayService.Stop()
        // ...
    }
    return app, cleanup, nil
}

3. Simplify main.go by calling the initializer and deferring cleanup.

func main() {
    app, cleanup, err := InitializeApp("order-svc", "dev")
    if err != nil { panic(err) }
    defer cleanup()
    app.engine.Run(...)
}

Evaluation : This manual container is clear, fully controllable, and requires no external tooling, making it ideal for small‑to‑medium projects.

Solution 2: Automatic DI with Google Wire (user‑svc)

For larger codebases, writing the initialization by hand becomes tedious. Google’s wire generates the wiring code at compile time.

1. Create a wire.go file that declares a ProviderSet describing how each component should be constructed.

// internal/services/user/cmd/usersvc/wire.go
package main

import "github.com/google/wire"

var providerSet = wire.NewSet(
    config.InitAppConfigStore,
    provideDiscovery,
    provideAppConfig,
    provideDatabase,
    service.NewUserService,
    handles.NewUserHandler,
    NewApp,
)

func InitializeApp(inputs ConfigInputs) (*App, func(), error) {
    // wire will fill this body automatically
    wire.Build(providerSet)
    return nil, nil, nil
}

2. Run the wire command to generate wire_gen.go. The generated file contains the same initialization logic that was manually written for the order service.

3. Use the generated InitializeApp in main.go exactly as with the manual approach.

func main() {
    app, cleanup, err := InitializeApp(userConfigInputs)
    if err != nil { panic(err) }
    defer cleanup()
    app.engine.Run(...)
}

Evaluation : The Wire solution offers high automation and compile‑time dependency checking. Initial setup (vendor handling, build tags) has a learning curve, but once configured it dramatically reduces boilerplate for complex projects.

Practical Recommendations

Embrace interfaces : Depend on abstractions (e.g., db.Database) rather than concrete implementations.

Use constructor functions : Provide a New... function for every struct to make dependencies explicit.

Choose the DI approach based on project size :

Small/medium projects : Manual DI container (as shown for order‑svc) – simple, no extra tooling.

Large/complex projects : Google Wire (as shown for user‑svc) – static analysis and code generation keep the wiring maintainable.

The ultimate goal of any DI strategy is to keep main.go clean and make the dependency graph obvious.

Source repository URLs (kept for reference): https://github.com/louis-xie-programmer/easyms.golang https://gitee.com/louis_xie/easyms.golang

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.

GoDependency InjectionGoogle WireManual DI
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

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.