Creational Created: 2026-01-27 Updated: 2026-01-27

Dependency Injection

A pattern where an object's dependencies are provided by an external entity rather than created by the object itself.

Contributors

When to Use

  • When you want to decouple components from their specific implementations
  • When you need to swap implementations for different environments (e.g. production vs. testing)
  • When you want to improve code testability through mocking or stubbing

When NOT to Use

  • For very simple applications where the overhead of interfaces adds unnecessary complexity
  • When the dependencies are small, static, and unlikely to ever change

Dependency Injection (DI) is a design pattern that implements inversion of control for resolving dependencies. In Go, DI is typically achieved by passing interfaces to constructors or structs via main.go, allowing the caller to decide which implementation to provide.

Implementation

In this example, we define a DataStore interface and a UserService that depends on it.

package service

import (
    "context"
    "fmt"
)

// User represents a user entity
type User struct {
    ID   string
    Name string
}

// DataStore defines the interface for data persistence
type DataStore interface {
    User(ctx context.Context, id string) (*User, error)
}

// SQLDataStore is a concrete implementation of DataStore
type SQLDataStore struct {
    ConnectionString string
}

func (s *SQLDataStore) User(ctx context.Context, id string) (*User, error) {
    // In a real scenario, this would query a database
    fmt.Printf("Fetching user %s from SQL database\n", id)
    return &User{ID: id, Name: "John Doe"}, nil
}

// UserService depends on DataStore interface, not a concrete type
type UserService struct {
    store DataStore
}

// NewUserService is a constructor that "injects" the dependency
func NewUserService(store DataStore) *UserService {
    return &UserService{
        store: store,
    }
}

func (s *UserService) UserName(ctx context.Context, id string) (string, error) {
    user, err := s.store.User(ctx, id)
    if err != nil {
        return "", err
    }
    return user.Name, nil
}

Usage

Wiring in Main

In production, we wire up the real implementations in the main function or a DI container.

package main

import (
    "context"
    "fmt"
    "example/service"
)

func main() {
    ctx := context.Background()

    // 1. Create the concrete dependency
    dbStore := &service.SQLDataStore{ConnectionString: "postgres://..."}

    // 2. Inject it into the service
    userService := service.NewUserService(dbStore)

    // 3. Use the service
    name, _ := userService.UserName(ctx, "123")
    fmt.Println("User Name:", name)
}

Mocking for Tests

DI makes it trivial to swap the real database with a mock implementation during testing.

package service_test

import (
    "context"
    "testing"
    "example/service"
)

// MockDataStore is a mock implementation of the DataStore interface
type MockDataStore struct {
    UserFunc func(ctx context.Context, id string) (*service.User, error)
}

func (m *MockDataStore) User(ctx context.Context, id string) (*service.User, error) {
    return m.UserFunc(ctx, id)
}

func TestUserService_UserName(t *testing.T) {
    ctx := context.Background()

    // Create a mock dependency
    mockStore := &MockDataStore{
        UserFunc: func(ctx context.Context, id string) (*service.User, error) {
            return &service.User{ID: "test-id", Name: "Mock User"}, nil
        },
    }

    // Inject the mock
    userService := service.NewUserService(mockStore)

    // Assert behavior
    name, err := userService.UserName(ctx, "test-id")
    if err != nil {
        t.Fatalf("expected no error, got %v", err)
    }
    if name != "Mock User" {
        t.Errorf("expected 'Mock User', got '%s'", name)
    }
}

Benefits

  • Improved Testability: Dependencies can be easily mocked, allowing for isolated unit tests.
  • Loose Coupling: Components don’t need to know about the internal details of their dependencies.
  • Flexibility: Swapping implementations (e.g., from SQL to NoSQL or Mock) requires no changes to the service logic.
  • Cleaner Construction: Logic for assembling the application is separated from the business logic.